Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions .gitignore

This file was deleted.

1 change: 0 additions & 1 deletion Procfile

This file was deleted.

14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Project Auth API

Replace this readme with your own information about your project.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
This weeks assignment was to build a backend API and a React frontend. The backend handles user authentication, while the frontend includes a registration form, token storage, and access to restricted (secret) content after login.

## The problem

Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
Backend:
I followed the codealong and added more specific error messages.

Frontend:
I started by creating separate components for login and signup functionalities. These components communicate with the backend API for user authentication and account creation. Using styled components for styling and React Router for navigation. Error handling was implemented to provide clear feedback to users. You should get errors if you try and create an account that already exists, if you try to log in without creating a user, if you write the wrong username/password, you should get a success message when you've succesfully created a user which will be displayed on the login page ect. If I had more time, I would refine error messages and enhance user experience with features like form validation. I also intended to create a separate component named AccountPage.jsx to handle account-related functions and content. However, due to issues with the login, I the the code intended for the "Account Page" within LoginPage.jsx. This decision was made to resolve the login issues.

## View it live

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
Backend: https://project-auth-0pi0.onrender.com/

Frontend: https://cheerful-froyo-159f59.netlify.app/
4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt-nodejs": "^0.0.3",
"cors": "^2.8.5",
"express": "^4.17.3",
"mongoose": "^8.0.0",
"express-list-endpoints": "^7.1.0",
"mongoose": "^8.4.0",
"nodemon": "^3.0.1"
}
}
98 changes: 89 additions & 9 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,104 @@
import cors from "cors";
import express from "express";
import mongoose from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt-nodejs";
import expressListEndpoints from "express-list-endpoints";

const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo";
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/auth";
mongoose.connect(mongoUrl);
mongoose.Promise = Promise;

// Defines the port the app will run on. Defaults to 8080, but can be overridden
// when starting the server. Example command to overwrite PORT env variable value:
// PORT=9000 npm start
const port = process.env.PORT || 8080;
// Model
const User = mongoose.model("User", {
name: {
type: String,
unique: true,
},
email: {
type: String,
unique: true,
},
password: {
type: String,
required: true,
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex"),
},
});

// Middleware to authenticate user with access token
const authenticateUser = async (req, res, next) => {
const accessToken = req.header("Authorization");
if (!accessToken) {
return res.status(401).json({ error: "Unauthorized: Access token is missing" });
}

const user = await User.findOne({ accessToken });
if (!user) {
return res.status(401).json({ error: "Unauthorized: Access token is invalid" });
}

req.user = user;
next();
};

const port = process.env.PORT || 8082;
const app = express();

// Add middlewares to enable cors and json body parsing
app.use(cors());
app.use(cors({
origin: 'https://cheerful-froyo-159f59.netlify.app'
}));
app.use(express.json());

// Start defining your routes here
// Routes
app.get("/", (req, res) => {
res.send("Hello Technigo!");
const endpoints = expressListEndpoints(app);
res.json(endpoints);
});

// Registration endpoint to create a new user
app.post("/users", async (req, res) => {
try {
const { name, email, password } = req.body;
if (!name) {
return res.status(400).json({ error: "Name is required" });
}
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
if (!password) {
return res.status(400).json({ error: "Password is required" });
}
Comment on lines +66 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice error handling ⭐


const user = new User({ name, email, password: bcrypt.hashSync(password) });
await user.save();
res.status(201).json({ id: user._id, accessToken: user.accessToken });
} catch (err) {
console.error("User creation failed:", err);
res.status(400).json({ error: "Could not create user. Something went wrong." });
}
});

// Login endpoint to authenticate a user
// Find the user by email, if user is found and password is correct, generate and return an access token
app.post("/login", async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be named /sessions to follow REST naming principles

const { email, password } = req.body;

const user = await User.findOne({ email });
if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).json({ message: "Invalid email or password" });
}

const accessToken = user.accessToken;
res.status(200).json({ accessToken });
});

// Route to access secrets after authentication
app.get("/secrets", authenticateUser, (req, res) => {
res.json({ secret: "This is a super secret message" });
});

// Start the server
Expand Down
20 changes: 0 additions & 20 deletions frontend/.eslintrc.cjs

This file was deleted.

26 changes: 0 additions & 26 deletions frontend/.gitignore

This file was deleted.

8 changes: 0 additions & 8 deletions frontend/README.md

This file was deleted.

6 changes: 5 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1",
"styled-components": "^6.1.11"
},
"devDependencies": {
"@types/react": "^18.2.15",
Expand Down
1 change: 0 additions & 1 deletion frontend/public/vite.svg

This file was deleted.

17 changes: 15 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { StartPage } from "./components/StartPage.jsx";
import { LoginPage } from "./components/LoginPage.jsx";
import { SignUpPage } from "./components/SignUpPage.jsx";

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
};
return (
<Router>
<Routes>
<Route path="/" element={<StartPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignUpPage />} />
</Routes>
</Router>
);
};
1 change: 0 additions & 1 deletion frontend/src/assets/react.svg

This file was deleted.

74 changes: 74 additions & 0 deletions frontend/src/components/AuthForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useState } from "react";

import styled from "styled-components";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";

export const FormContainer = styled.div`
text-align: center;
`;

export const Form = styled.form`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 2em;
`;

const InputContainer = styled.div`
position: relative;
`;

const InputField = styled.input`
font-size: 1em;
padding: 0.75em;
padding-right: 2.5em;
margin: 0.25em;
width: 295px;
border: 1.5px solid #ffffff;
border-radius: 0.5em;
background-color: rgba(255, 255, 255, 0.13);
`;

const EyeIcon = styled(FontAwesomeIcon)`
position: absolute;
top: 50%;
right: 0.75em;
transform: translateY(-50%);
cursor: pointer;
color: #fffffff;
`;

export const Input = ({ type, placeholder, value, onChange }) => {
const [showPassword, setShowPassword] = useState(false);

const toggleShowPassword = () => {
setShowPassword((prevShowPassword) => !prevShowPassword);
};

return (
<InputContainer>
<InputField
type={showPassword ? "text" : type}
placeholder={placeholder}
value={value}
onChange={onChange}
required
/>
{type === "password" && (
<EyeIcon
icon={showPassword ? faEyeSlash : faEye}
onClick={toggleShowPassword}
/>
)}
</InputContainer>
);
};

export const AuthForm = ({ onSubmit, children }) => {
return (
<FormContainer>
<Form onSubmit={onSubmit}>{children}</Form>
</FormContainer>
);
};
Loading