diff --git a/README.md b/README.md index dfa05e177..ce8626bb7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,7 @@ # 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. - -## 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? - ## 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. +Front-end: [![Netlify Status](https://api.netlify.com/api/v1/badges/93c0dec7-42c3-4b60-833d-fc9f955c4a81/deploy-status)](https://app.netlify.com/sites/yifan-project-authentication/deploys) + +Back-end: https://project-authentication-6r12.onrender.com/ diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..3bda8e5d3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,8 +12,11 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", "cors": "^2.8.5", "express": "^4.17.3", + "express-list-endpoints": "^7.1.0", "mongoose": "^8.0.0", "nodemon": "^3.0.1" } diff --git a/backend/server.js b/backend/server.js index dfe86fb8e..59b5f90b9 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,14 +1,35 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; +import crypto from "crypto"; +import bcrypt from "bcryptjs"; +import expressListEndpoints from "express-list-endpoints"; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +const mongoUrl = process.env.MONGO_URL || "mongodb://127.0.0.1/project-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 User = mongoose.model("User", { + name: { type: String, unique: true, required: true }, + email: { type: String, unique: true, required: true }, + password: { type: String, required: true }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), //user gets a string of random numbers as the accessToken + }, +}); + +//a middleware function for looking for the user based on the accessToken saved in "Authorization" in the header +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({ accessToken: req.header("Authorization") }); + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ loggedOut: true }); + } +}; + const port = process.env.PORT || 8080; const app = express(); @@ -18,7 +39,65 @@ app.use(express.json()); // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!"); + const endpoints = expressListEndpoints(app); + const documentation = endpoints.map((endpoint) => ({ + method: endpoint.methods.join(", "), + path: endpoint.path, + })); + res.json(documentation); +}); + +app.post("/registration", async (req, res) => { + try { + const { name, email, password } = req.body; + + //error handling + if (!name || name.trim() === "") { + return res.status(400).json({ message: "Please enter username" }); + } + if (!email || email.trim() === "") { + return res.status(400).json({ message: "Please enter email" }); + } + if (!password || password.trim() === "") { + return res.status(400).json({ message: "Please enter password" }); + } + if (password.length < 6) { + return res + .status(400) + .json({ message: "Password must be at least 6 characters long" }); + } + + const salt = bcrypt.genSaltSync(); + const user = new User({ + name, + email, + password: bcrypt.hashSync(password, salt), + }); + user.save(); + res.status(201).json({ id: user._id, accessToken: user.accessToken }); + } catch (err) { + res + .status(400) + .json({ message: "Could not create the user", errors: err.errors }); + } +}); + +app.get("/dashboard", authenticateUser); +app.get("/dashboard", (req, res) => { + res.json({ message: "You're logged in!" }); +}); + +app.post("/login", async (req, res) => { + const user = await User.findOne({ email: req.body.email }); + if (user && bcrypt.compareSync(req.body.password, user.password)) { + res.json({ + userId: user._id, + name: user.name, + accessToken: user.accessToken, + }); + } else { + return res.status(400).json({ notFound: true }); + } }); // Start the server diff --git a/frontend/index.html b/frontend/index.html index 0c589eccd..69a744d15 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,16 @@ - - - - - Vite + React - - -
- - - + + + + + + Project Auth + + + +
+ + + + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..db1a62a6e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..2eadac160 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,14 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { DashboardPage } from "./pages/DashboardPage"; +import { SignLogInPage } from "./pages/SignLogInPage"; + export const App = () => { - return
Find me in src/app.jsx!
; + return ( + + + } /> + } /> + + + ); }; diff --git a/frontend/src/components/LogInForm.jsx b/frontend/src/components/LogInForm.jsx new file mode 100644 index 000000000..47f458bc9 --- /dev/null +++ b/frontend/src/components/LogInForm.jsx @@ -0,0 +1,81 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +export const LogInForm = () => { + const [logInData, setlogInData] = useState({ + email: "", + password: "", + }); + const [message, setMessage] = useState(""); + + const navigate = useNavigate(); + + const handleChange = (event) => { + setlogInData({ + ...logInData, + [event.target.name]: event.target.value, + }); + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + try { + const response = await fetch( + "https://project-authentication-6r12.onrender.com/login", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(logInData), + } + ); + + if (!response.ok) { + throw new Error("Failed at logging in..."); + } + + const data = await response.json(); + console.log("User logged in:", data); + + //save the accessToken to localStorage + localStorage.setItem("accessToken", data.accessToken); + + setlogInData({ + email: "", + password: "", + }); + + setMessage("Login successful!"); + navigate("/dashboard"); + } catch (error) { + console.error("Login failed:", error); + setMessage("Login failed. Please try again."); + } + }; + + return ( +
+
+ + + +
+ {message &&

{message}

} +
+ ); +}; diff --git a/frontend/src/components/RegistrationForm.jsx b/frontend/src/components/RegistrationForm.jsx new file mode 100644 index 000000000..707e06046 --- /dev/null +++ b/frontend/src/components/RegistrationForm.jsx @@ -0,0 +1,84 @@ +import { useState } from "react"; + +export const RegistrationForm = () => { + const [registrationData, setRegistrationData] = useState({ + name: "", + email: "", + password: "", + }); + const [message, setMessage] = useState(""); + + const handleChange = (event) => { + setRegistrationData({ + ...registrationData, + [event.target.name]: event.target.value, + }); + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + try { + const response = await fetch( + "https://project-authentication-6r12.onrender.com/registration", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(registrationData), + } + ); + + if (!response.ok) { + throw new Error("Failed at creating the account..."); + } + + const data = await response.json(); + console.log("User registered:", data); + + setRegistrationData({ + name: "", + email: "", + password: "", + }); + + setMessage("Registration successful!"); + } catch (error) { + console.error("Registration failed:", error); + setMessage("Registration failed. Please try again."); + } + }; + + return ( +
+
+ + + + +
+ {message &&

{message}

} +
+ ); +}; diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx new file mode 100644 index 000000000..988195dbe --- /dev/null +++ b/frontend/src/pages/DashboardPage.jsx @@ -0,0 +1,56 @@ +import { useState, useEffect } from "react"; + +export const DashboardPage = () => { + const [message, setMessage] = useState(""); + + useEffect(() => { + const fetchAccessToken = async () => { + const accessToken = localStorage.getItem("accessToken"); + + if (!accessToken) { + setMessage("You don't have access to this page. Please log in first."); + return; + } + + try { + const response = await fetch( + "https://project-authentication-6r12.onrender.com/dashboard", + { + headers: { + Authorization: accessToken, + }, + } + ); + + if (!response.ok) { + throw new Error( + "Failed to log into your dashboard. Please try again." + ); + } + + const data = await response.json(); + setMessage(data.message); + } catch (err) { + console.error("Failed to fetch dashboard info:", err); + setMessage("Failed to log into your dashboard. Please try again."); + } + }; + + fetchAccessToken(); + }, []); + + return ( +
+

Dashboard

+

{message}

+ +
+ ); +}; diff --git a/frontend/src/pages/SignLogInPage.jsx b/frontend/src/pages/SignLogInPage.jsx new file mode 100644 index 000000000..63b79c14a --- /dev/null +++ b/frontend/src/pages/SignLogInPage.jsx @@ -0,0 +1,14 @@ +import { RegistrationForm } from "../components/RegistrationForm"; +import { LogInForm } from "../components/LogInForm"; + +export const SignLogInPage = () => { + return ( +
+

Welcome!

+
+ + +
+
+ ); +}; diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 95443a1f3..000000000 --- a/netlify.toml +++ /dev/null @@ -1,6 +0,0 @@ -# This file tells netlify where the code for this project is and -# how it should build the JavaScript assets to deploy from. -[build] - base = "frontend/" - publish = "build/" - command = "npm run build"