Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth app #331

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
799bce2
removed pull-request template and instructions-file
ericamechler May 21, 2024
b503ac2
added registrations-endpoint, sign-in-endpoint & my-pages-endpointas …
ericamechler May 21, 2024
a70e35f
added expresslist-endpoints
ericamechler May 21, 2024
dadc9bb
added components-structure
ericamechler May 22, 2024
3e3e47e
added functioning registration-form
ericamechler May 22, 2024
0a7502b
added signin-form & loading-state
ericamechler May 22, 2024
a9241dd
added isRegistering-state to show register button
ericamechler May 22, 2024
b0d733f
Replaced button with a tag text and added styling.
JohannaBN May 22, 2024
4578185
Updated backend endpoint /sign-in to also include the users name in t…
JohannaBN May 22, 2024
b7f5282
Added a user state to handle user information and showing it on MyPag…
JohannaBN May 22, 2024
ad6ecc2
implemented the authenticated content page
ericamechler May 22, 2024
0a44d15
Added loading state to sign in and register form.
JohannaBN May 22, 2024
230bd99
Added signout button and sign out function.
JohannaBN May 22, 2024
b19ab74
fixed some things with localstorage, can now refresh without being lo…
ericamechler May 23, 2024
19fd5e6
Merge pull request #1 from ericamechler/localstorage
ericamechler May 23, 2024
c51514d
added validation rules in the model and handled logic for password-re…
ericamechler May 23, 2024
f2b85fa
added netlify & render links to readme
ericamechler May 24, 2024
16a0415
Show loader animation when signing in and registering
FridaMari May 24, 2024
c22a34a
fixed so that after a user registers, it comes to the signin-page aut…
ericamechler May 24, 2024
ae51937
Merge pull request #2 from ericamechler/registerbug
ericamechler May 24, 2024
9836c78
Updated Readme.
JohannaBN May 24, 2024
7127caf
Merge pull request #3 from ericamechler/readme
JohannaBN May 24, 2024
e80db81
Added readme
JohannaBN May 24, 2024
14d5ceb
Merge pull request #4 from ericamechler/readme-update
JohannaBN May 24, 2024
5bc32f8
removed comments etc to clean up
ericamechler May 27, 2024
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
1 change: 1 addition & 0 deletions .anima/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# 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 project, completed as part of the Technigo bootcamp, involves developing a full-stack authentication system with a backend API and a React frontend. The project includes user registration and login functionalities, token-based authentication, and protected routes that require valid authentication tokens for access.

## 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?
The goal of this project was to create a secure user authentication system. The system allows users to register and log in, storing their credentials securely. Once logged in, users can access protected content that is only available to authenticated users. The primary challenge was ensuring the security of user data and tokens while providing a seamless user experience.

To create a secure user authentication system with a backend API and a React frontend, we planned the project by outlining the essential components and their interactions. The approach involved building a Node.js and Express backend with MongoDB for data storage, and bcrypt for password hashing. The frontend was developed using React, incorporating forms for user registration and login, with authenticated routes to manage secure content access. If given more time, we would enhance the validation and security features, add more user functionalities, and improve the user experience with better error handling.

## 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.
[Frontend](https://authentication-service.netlify.app/)
[Backend](https://auth-s0og.onrender.com)
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"express": "^4.17.3",
"express-list-endpoints": "^7.1.0",
"mongoose": "^8.0.0",
"nodemon": "^3.0.1"
}
Expand Down
123 changes: 119 additions & 4 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,67 @@
import bcrypt from "bcrypt";
import cors from "cors";
import express from "express";
import expressListEndpoints from "express-list-endpoints";
import mongoose from "mongoose";

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
// Create user object that has access-token. Mongoose-model
// Destructure schema & model
const { Schema, model } = mongoose;

const userSchema = new Schema({
name: {
type: String,
unique: true,
required: [true, "Name is required"],
},
email: {
type: String,
unique: true,
required: [true, "Email is required"],
match: [/.+\@.+\..+/, "Please enter a valid email address"],
},
password: {
type: String,
required: [true, "Password is required"],
minlength: [8, "Password must be at least 8 characters long"],
},
accessToken: {
type: String,
default: () => bcrypt.genSaltSync(),
},
});

const User = model("User", userSchema);

const authenticateUser = async (req, res, next) => {
try {
const user = await User.findOne({
accessToken: req.header("Authorization"),
});
if (user) {
req.user = user;
next();
} else {
res.status(401).json({
message: "Authentication missing or invalid.",
loggedOut: true,
});
}
} catch (err) {
res
.status(500)
.json({ message: "Internal server error", error: err.message });
}
};
Comment on lines +40 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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


// Defines the port the app will run on. Defaults to 8030, 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;
const port = process.env.PORT || 8030;
const app = express();

// Add middlewares to enable cors and json body parsing
Expand All @@ -18,7 +70,70 @@ app.use(express.json());

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

app.post("/register", 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.

Code looks good! Just remember to try to name endpoints after what they return or in this case create. For example: /users

try {
const { name, email, password } = req.body;

// check if password is empty
if (!password) {
return res.status(400).json({ message: "Password is required" });
}

// Check if password meets minimum length req
if (password.length < 8) {
return res.status(400).json({
message: "Password has to be at least 8 characters long",
});
}
// Encrypt the password
const user = await new User({
name,
email,
password: bcrypt.hashSync(password, 10),
}).save();

res.status(201).json({ id: user._id, accessToken: user.accessToken });
} catch (err) {
res
.status(400)
.json({ message: "Could not create user", errors: err.errors });
}
});

// protect my-pages endpoint
app.get("/my-pages", authenticateUser, (req, res) => {
res.json({ message: "This is your personal page" });
});

// Allow the user to log in, not only register

app.post("/sign-in", async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
res.status(400).json({ message: "Email and password are required" });
return;
}

const user = await User.findOne({ email });
if (user && bcrypt.compareSync(password, user.password)) {
res.json({
userId: user._id,
name: user.name,
accessToken: user.accessToken,
});
} else {
res.status(401).json({ message: "Invalid email or password" });
}
} catch (err) {
res
.status(500)
.json({ message: "Internal server error", error: err.message });
}
});

// Start the server
Expand Down
8 changes: 0 additions & 8 deletions frontend/README.md

This file was deleted.

32 changes: 31 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
import { useEffect, useState } from "react";

import { MyPages } from "./components/MyPages";
import { Registration } from "./components/Registration";
import { SignIn } from "./components/SignIn";

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
const [isRegistering, setIsRegistering] = useState(false);
const [user, setUser] = useState(null);

useEffect(() => {
// Check if user data is available in localStorage on component mount
const accessToken = localStorage.getItem("accessToken");
const userName = localStorage.getItem("userName");
const userId = localStorage.getItem("userId");

if (accessToken && userName && userId) {
setUser({ id: userId, name: userName });
}
}, []);

return (
<div className="app-container">
{user ? (
<MyPages user={user} setUser={setUser} />
) : isRegistering ? (
<Registration setIsRegistering={setIsRegistering} />
) : (
<SignIn setIsRegistering={setIsRegistering} setUser={setUser} />
)}
</div>
);
};
1 change: 0 additions & 1 deletion frontend/src/assets/react.svg

This file was deleted.

17 changes: 17 additions & 0 deletions frontend/src/components/Loader.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.loader {
border: 2px solid #f3f3f3;
border-top: 2px solid #000000;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
5 changes: 5 additions & 0 deletions frontend/src/components/Loader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import "./Loader.css";

export const Loader = () => {
return <div className="loader"></div>;
};
60 changes: 60 additions & 0 deletions frontend/src/components/MyPages.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useEffect, useState } from "react";
import { SignOut } from "./SignOut";

export const MyPages = ({ user, setUser }) => {
const [message, setMessage] = useState("");
const [error, setError] = useState("");

useEffect(() => {
const fetchMyPages = async () => {
const accessToken = localStorage.getItem("accessToken");
if (!accessToken) {
setError("No access token found. Please log in again.");
return;
}
try {
const response = await fetch(
"https://auth-s0og.onrender.com/my-pages",
{
headers: {
Authorization: accessToken,
},
}
);
const data = await response.json();
if (response.ok) {
setMessage(data.message);
} else {
setError("Failed to fetch data. Please log in again");
}
} catch (error) {
setError("An error occurred. Please try again later");
}
};
fetchMyPages();
}, []);

if (error) {
return (
<div className="error-container">
<p>{error}</p>
</div>
);
}

if (!message) {
return (
<div className="loading-container">
<p>Loading...</p>
</div>
);
}

return (
<div className="container">
<h1>Welcome, {user.name}</h1>
<p>{message}</p>
<SignOut setUser={setUser} />
</div>
);
};
61 changes: 61 additions & 0 deletions frontend/src/components/Registration.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
max-width: 400px;
margin: 50px auto;
font-family: Arial, sans-serif;
}

h1 {
margin-bottom: 20px;
}

label {
display: block;
margin-bottom: 5px;
}

input {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}

button {
width: 100%;
padding: 10px;
background-color: #28a745;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}

button:hover {
background-color: #218838;
}

p {
margin-top: 20px;
text-align: center;
}

.login-link {
color: blue;
cursor: pointer;
text-decoration: underline;
}

.register-link:hover {
text-decoration: none;
}

.loading-container {
display: flex;
justify-content: center;
padding: 20px;
}
Loading