From eb9b6d09ef97e563fe756566e6e969a5fb17d034 Mon Sep 17 00:00:00 2001 From: Minadarabi Date: Fri, 8 Dec 2023 10:30:20 -0800 Subject: [PATCH 1/6] first commit --- backend/authenticateUser.js | 26 +++++++++++ backend/models/UserModel.js | 36 ++++++++++++++++ backend/routes/userRoutes.js | 83 ++++++++++++++++++++++++++++++++++++ backend/server.js | 9 +++- 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 backend/authenticateUser.js create mode 100644 backend/models/UserModel.js create mode 100644 backend/routes/userRoutes.js diff --git a/backend/authenticateUser.js b/backend/authenticateUser.js new file mode 100644 index 000000000..4715a956e --- /dev/null +++ b/backend/authenticateUser.js @@ -0,0 +1,26 @@ +import { UserModel } from "./models/userModel" + + + +export const authenticateUser = async(req , res , next) =>{ + const accessToken = req.header("Authorization") + + if ( + req.headers.authorization && + req.headers.authorization.startsWith("Bearer") + ) + try{ + const user = await UserModel.findOne({accessToken: accessToken}) + if(user){ + req.user = user + next() + }else{ + res.status(401).json({success: false , response: "please log in" }) + } + } + catch(e){ + res.status(500).json({success: false , response: e.message }) + } +} + + diff --git a/backend/models/UserModel.js b/backend/models/UserModel.js new file mode 100644 index 000000000..9d2c39660 --- /dev/null +++ b/backend/models/UserModel.js @@ -0,0 +1,36 @@ +import mongoose from "mongoose"; +import crypto from "crypto"; + +const { Schema } = mongoose; + + +const userSchema = new Schema({ + username: { + type: String, + required: true, + unique: true, + minlenght: 2 + }, + password: { + type: String, + required: true, + minlenght: 6 + + }, + emaill: { + type: String, + required: true, + unique: true, + + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex") + } +}, +{ + timestamps: true +} +) + +export const UserModel = mongoose.model('User', userSchema); \ No newline at end of file diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js new file mode 100644 index 000000000..2555a890d --- /dev/null +++ b/backend/routes/userRoutes.js @@ -0,0 +1,83 @@ + +import express from "express"; +import bcrypt from "bcrypt"; +import { Jwt } from "jsonwebtoken"; + +import { UserModel } from "../models/userModel"; +import asyncHandler from "express-async-handler"; +import dotenv from "dotenv"; +import { authenticateUser } from "../authenticateUser"; +dotenv.config() + + + + + +const router = express.Router() + +const geneerateToken = (user) => { + return jwt.sign({id: user._id} , process.env.JWT_SECRET, { + expiresIn: "24h" + }); + +}; + +router.post("/register" , asyncHandler(async(req , res) => { + const { username , password , email } = req.body; + try{ + if(!username || !password || !email){ + res.status(400); + throw new Error("please add all fields") + } + const existingUser = await UserModel.findOne({ + $or: [{username},{email}] + }); + if(existingUser){ + res.status(400) + throw new Error(`user with ${existingUser.username === username ? "username" : "email"} already exists`) + } + const salt = bcrypt.genSaltSync(10); + const hashedPassword = bcrypt.hashSync(password , salt); + const newUser = new UserModel({ + username, + email, + password: hashedPassword + }); + await newUser.save(); + res.status(201).json({ + success: true, + response: { + username: newUser.username, + email: newUser.email, + id: newUser._id, + accessToken: geneerateToken(newUser._id) + + } + }); + }catch(e){ + res.status(500).json({success: false , response: e.message }) + } + +})); + +router.post("/login", authenticateUser , asyncHandler(async(req , res) => { + const { username , password} = req.body; + try { + const user = await UserModel.findOne({username}); + if(!user){ + return res.status(401).json({success: false , response: "user not found"}); + } + const isMatch = await bcrypt.compare(password , user.password); + if(!isMatch){ + return res.status(401).json({success: false , response: "incorrect password"}); + } + res.status(200).json({success: true , response: { + + }}) + } catch (error) { + + } + +})) + +export default router \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 2d7ae8aa1..9ef0b69da 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,6 +1,9 @@ -import express from "express"; +import express, { urlencoded } from "express"; import cors from "cors"; import mongoose from "mongoose"; +import dotenv from "dotenv"; +dotenv.config() +import userRoutes from "./routes/userRoutes" const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); @@ -15,6 +18,10 @@ const app = express(); // Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); +app.use(express.urlencoded({extended: false})) +app.use(userRoutes); + + // Start defining your routes here app.get("/", (req, res) => { From 4403548af23f5b1a73a1e074a83b3b00303b56d9 Mon Sep 17 00:00:00 2001 From: Minadarabi Date: Wed, 13 Dec 2023 11:32:43 -0800 Subject: [PATCH 2/6] frontend done --- frontend/package.json | 6 +- frontend/src/App.jsx | 13 ++- frontend/src/components/LogIn.jsx | 87 +++++++++++++++++++ frontend/src/components/LogOut.jsx | 28 ++++++ frontend/src/components/LogedInComponet.jsx | 59 +++++++++++++ frontend/src/components/SignUp.jsx | 73 ++++++++++++++++ frontend/src/components/UserStore.jsx | 18 ++++ frontend/src/index.css | 96 +++++++++++++++++++++ frontend/src/pages/LandingPage.jsx | 19 ++++ frontend/src/pages/LogedInPage.jsx | 12 +++ frontend/src/routes/RoutesComp.jsx | 10 +++ 11 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/LogIn.jsx create mode 100644 frontend/src/components/LogOut.jsx create mode 100644 frontend/src/components/LogedInComponet.jsx create mode 100644 frontend/src/components/SignUp.jsx create mode 100644 frontend/src/components/UserStore.jsx create mode 100644 frontend/src/pages/LandingPage.jsx create mode 100644 frontend/src/pages/LogedInPage.jsx create mode 100644 frontend/src/routes/RoutesComp.jsx diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..5dba88de0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "lottie-react": "^2.4.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.1", + "styled-components": "^6.1.1", + "zustand": "^4.4.7" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..768d5a05a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,14 @@ +import { BrowserRouter , Routes } from "react-router-dom"; +import {routes} from "./routes/RoutesComp" + + + export const App = () => { - return
Find me in src/app.jsx!
; + return
+ +
+ {routes} +
+
+
; }; diff --git a/frontend/src/components/LogIn.jsx b/frontend/src/components/LogIn.jsx new file mode 100644 index 000000000..ec90cd0cf --- /dev/null +++ b/frontend/src/components/LogIn.jsx @@ -0,0 +1,87 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { userStore } from "./UserStore"; + + +const apiEnv = import.meta.env.VITE_BACKEND_API; + +export const Login = () => { + + const { + username, + setUsername, + password, + setPassword, + user, + setUser, + setAccessToken, + } = userStore(); + const navigate = useNavigate(); + + const handleLogin = async () => { + + if (!username || !password) { + alert("Please enter both username and password"); + return; + } + + try { + + const response = await fetch(`${apiEnv}/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ username, password }), + }); + console.log(response); + + + if (response.ok) { + + const data = await response.json(); + + setAccessToken(data.accessToken); + + setUser(data); + + localStorage.setItem("accessToken", data.accessToken); + setUsername(""); + setPassword(""); + + alert("Login successful!"); + navigate("/logged-in"); + } else { + + const errorData = await response.json(); + alert(`Login failed: ${errorData.error}`); + } + } catch (error) { + + alert("Error during login:", error.message); + } + }; + + + useEffect(() => { + console.log("User:", user); + }, [user]); + + return ( +
+ setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/components/LogOut.jsx b/frontend/src/components/LogOut.jsx new file mode 100644 index 000000000..ecef17a47 --- /dev/null +++ b/frontend/src/components/LogOut.jsx @@ -0,0 +1,28 @@ +import { useNavigate } from "react-router-dom"; +import { userStore } from "./UserStore"; + + +export const Logout = () => { + + const navigate = useNavigate(); + + const { setAccessToken, setIsLoggedIn, setUser } = userStore(); + + + const handleLogOut = () => { + + setUser(null); + setAccessToken(null); + setIsLoggedIn(false); + localStorage.removeItem("accessToken"); + + alert("Log out successful!"); + navigate("/"); + }; + + return ( + <> + + + ); +}; \ No newline at end of file diff --git a/frontend/src/components/LogedInComponet.jsx b/frontend/src/components/LogedInComponet.jsx new file mode 100644 index 000000000..85f947e88 --- /dev/null +++ b/frontend/src/components/LogedInComponet.jsx @@ -0,0 +1,59 @@ +import { useNavigate } from "react-router-dom"; +import { useEffect } from "react"; +import { userStore } from "./UserStore"; + +const apiEnv = import.meta.env.VITE_BACKEND_API; + +export const LogedInComp = () => { + + const { accessToken, isLoggedIn, setIsLoggedIn } = userStore(); + + const navigate = useNavigate(); + + + useEffect(() => { + const fetchLoggedInData = async () => { + try { + + const response = await fetch(`${apiEnv}/logged-in`, { + headers: { + "Content-Type": "application/json", + Authorization: `${accessToken}`, + }, + }); + + + if (response.ok) { + + const data = await response.json(); + console.log(data); + + setIsLoggedIn(true); + } else { + + console.error("Failed to fetch authenticated content"); + navigate("/"); + } + } catch (error) { + + console.error("Error fetching authenticated content:", error); + } + }; + + + fetchLoggedInData(); + }); + + return ( + isLoggedIn && ( +
+ AI Generated Image +

Congratulations! You are logged in. Here is a cute puppy for you!

+
+ ) + ); +}; \ No newline at end of file diff --git a/frontend/src/components/SignUp.jsx b/frontend/src/components/SignUp.jsx new file mode 100644 index 000000000..26b409fd9 --- /dev/null +++ b/frontend/src/components/SignUp.jsx @@ -0,0 +1,73 @@ +import { useState } from "react"; + + +const apiEnv = import.meta.env.VITE_BACKEND_API; + + +export const SignUp = () => { + + const [email, setEmail] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + + + const handleSignup = async () => { + try { + console.log("URL:", `${apiEnv}/register`); + console.log("Request Body:", { email, username, password }); + + const response = await fetch(`${apiEnv}/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + + body: JSON.stringify({ email, username, password }), + + }); + + + if (response.ok) { + alert("Signup was successful, please log in!"); + setEmail("") + setUsername("") + setPassword("") + } else { + const data = await response.json(); + console.log(data) + alert("Signup failed:" + data.message); + } + } catch (error) { + + alert("Error during registration:", error); + } + }; + + + + return ( + <> +
+ setEmail(e.target.value)} + /> + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
+ + ); +}; \ No newline at end of file diff --git a/frontend/src/components/UserStore.jsx b/frontend/src/components/UserStore.jsx new file mode 100644 index 000000000..92c601000 --- /dev/null +++ b/frontend/src/components/UserStore.jsx @@ -0,0 +1,18 @@ +import { create } from "zustand"; + + +export const userStore = create((set) => ({ + + username: "", + password: "", + accessToken: null, + user: null, + isLoggedIn: false, + + + setUsername: (username) => set({ username }), + setPassword: (password) => set({ password }), + setAccessToken: (accessToken) => set({ accessToken }), + setUser: (user) => set({ user }), + setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }), +})); \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 3e560a674..92be80071 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -10,4 +10,100 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + + +body { + display: flex; + justify-content: center; + place-items: center; + min-width: 320px; + min-height: 100vh; + background-color: #a9ddc1; +} + +p { + margin: 5px; +} + +.wrapper { + display: flex; + flex-direction: column; + gap: 3rem; + padding: 2rem; +} + +.logged-in-content img{ + max-width: 100%; +} + +.logged-in-wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + max-width: 250px; + background-color: #d4aeb1; + border-radius: 5px; + padding: 1rem; + text-align: center; +} + +.login-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem; + background-color: #e4c1c3; + border-radius: 5px; +} + +.login-form { + display: flex; + flex-direction: column; + align-items: center; +} + +.signup-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem; + background-color: #e5c1c4; + border-radius: 5px; +} + +.signup-form { + display: flex; + flex-direction: column; + align-items: center; +} + + input { + padding: 10px; + margin-bottom: 10px; + border-radius: 5px; + border: none; + width: 70%; +} + +button { + padding: 10px; + margin-bottom: 10px; + border-radius: 20px; + border: none; + width: 70%; + background-color: rgb(64, 0, 255); + color: white; + font-weight: 700; +} + +button:hover { + background-color: #3a5640; + cursor: pointer; +} + +p { + font-weight: 700; + color: white; } \ No newline at end of file diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx new file mode 100644 index 000000000..183efd131 --- /dev/null +++ b/frontend/src/pages/LandingPage.jsx @@ -0,0 +1,19 @@ +import { Login } from "../components/LogIn"; +import { SignUp } from "../components/SignUp"; + +export const LandingPage = () => { + return ( + <> +
+

Please login:

+ +
+ +
+

No account?

+

Please sign up:

+ +
+ + ); +}; \ No newline at end of file diff --git a/frontend/src/pages/LogedInPage.jsx b/frontend/src/pages/LogedInPage.jsx new file mode 100644 index 000000000..07a998e28 --- /dev/null +++ b/frontend/src/pages/LogedInPage.jsx @@ -0,0 +1,12 @@ + +import { LogedInComp } from "../components/LogedInComponet"; +import { Logout } from "../components/LogOut"; + +export const LogedInPage = () => { + return ( +
+ + +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/routes/RoutesComp.jsx b/frontend/src/routes/RoutesComp.jsx new file mode 100644 index 000000000..bc97c0a90 --- /dev/null +++ b/frontend/src/routes/RoutesComp.jsx @@ -0,0 +1,10 @@ +import { Route } from "react-router-dom"; +import { LandingPage } from "../pages/LandingPage"; +import { LogedInPage } from "../pages/LogedInPage"; + +export const routes = ( + <> + }> + }> + +); \ No newline at end of file From 5bb25613aaceedfcb8b9441ab5e3478f48fce165 Mon Sep 17 00:00:00 2001 From: Minadarabi Date: Thu, 14 Dec 2023 11:50:10 -0800 Subject: [PATCH 3/6] ready for deply --- backend/models/UserModel.js | 5 +++-- backend/package.json | 1 + backend/routes/userRoutes.js | 22 +++++++++++++++++++--- netlify.toml | 6 ------ package.json | 6 ++++++ 5 files changed, 29 insertions(+), 11 deletions(-) delete mode 100644 netlify.toml diff --git a/backend/models/UserModel.js b/backend/models/UserModel.js index 9d2c39660..16fc24840 100644 --- a/backend/models/UserModel.js +++ b/backend/models/UserModel.js @@ -17,7 +17,7 @@ const userSchema = new Schema({ minlenght: 6 }, - emaill: { + email: { type: String, required: true, unique: true, @@ -25,7 +25,8 @@ const userSchema = new Schema({ }, accessToken: { type: String, - default: () => crypto.randomBytes(128).toString("hex") + default: () => crypto.randomBytes(128).toString("hex"), + required: false } }, { diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..da5dd4791 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,6 +14,7 @@ "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", "express": "^4.17.3", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", "nodemon": "^3.0.1" } diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index 2555a890d..9942ad2bf 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -1,7 +1,7 @@ import express from "express"; import bcrypt from "bcrypt"; -import { Jwt } from "jsonwebtoken"; +import jwt from "jsonwebtoken"; import { UserModel } from "../models/userModel"; import asyncHandler from "express-async-handler"; @@ -50,7 +50,7 @@ router.post("/register" , asyncHandler(async(req , res) => { username: newUser.username, email: newUser.email, id: newUser._id, - accessToken: geneerateToken(newUser._id) + accessToken: geneerateToken(newUser) } }); @@ -60,7 +60,7 @@ router.post("/register" , asyncHandler(async(req , res) => { })); -router.post("/login", authenticateUser , asyncHandler(async(req , res) => { +router.post("/login", asyncHandler(async(req , res) => { const { username , password} = req.body; try { const user = await UserModel.findOne({username}); @@ -72,12 +72,28 @@ router.post("/login", authenticateUser , asyncHandler(async(req , res) => { return res.status(401).json({success: false , response: "incorrect password"}); } res.status(200).json({success: true , response: { + + username: user.username, + email: user.email, + id: user._id, + accessToken: geneerateToken(user) + }}) } catch (error) { + res.status(500).json({success: false , response: { + + message: error.message + + }}) } })) +router.get("/logged-in", authenticateUser, asyncHandler(async (req, res) => { + res.status(200).json({success: true , response: { + message: "user is logged in" }}) + })) + export default router \ No newline at end of file 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" diff --git a/package.json b/package.json index d774b8cc3..86c29a43d 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,11 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "dependencies": { + "bcrypt": "^5.1.1", + "crypto": "^1.0.1", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.2" } } From 5294f5bb413e0c9ce9ed8c5d53f5f8713d315638 Mon Sep 17 00:00:00 2001 From: Minadarabi Date: Thu, 14 Dec 2023 12:02:02 -0800 Subject: [PATCH 4/6] fix deps --- backend/package.json | 5 ++++- package.json | 13 ------------- 2 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 package.json diff --git a/backend/package.json b/backend/package.json index da5dd4791..83a8c6f3f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,9 @@ "express": "^4.17.3", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", - "nodemon": "^3.0.1" + "nodemon": "^3.0.1", + "bcrypt": "^5.1.1", + "crypto": "^1.0.1", + "express-async-handler": "^1.2.0" } } diff --git a/package.json b/package.json deleted file mode 100644 index 86c29a43d..000000000 --- a/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "project-auth-parent", - "version": "1.0.0", - "scripts": { - "postinstall": "npm install --prefix backend" - }, - "dependencies": { - "bcrypt": "^5.1.1", - "crypto": "^1.0.1", - "express-async-handler": "^1.2.0", - "jsonwebtoken": "^9.0.2" - } -} From 10bf6d4d50bc9551a906617a74cabb902e3b0106 Mon Sep 17 00:00:00 2001 From: Minadarabi Date: Thu, 14 Dec 2023 12:11:28 -0800 Subject: [PATCH 5/6] add dotenv --- backend/package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/package.json b/backend/package.json index 83a8c6f3f..62595567e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,13 +12,14 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^5.1.1", "cors": "^2.8.5", + "crypto": "^1.0.1", + "dotenv": "^16.3.1", "express": "^4.17.3", + "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", - "nodemon": "^3.0.1", - "bcrypt": "^5.1.1", - "crypto": "^1.0.1", - "express-async-handler": "^1.2.0" + "nodemon": "^3.0.1" } } From bba3c3e38609a0b406e46034032d3aba3eae5903 Mon Sep 17 00:00:00 2001 From: Minadarabi Date: Thu, 14 Dec 2023 12:16:21 -0800 Subject: [PATCH 6/6] fix paths --- backend/authenticateUser.js | 2 +- backend/routes/userRoutes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/authenticateUser.js b/backend/authenticateUser.js index 4715a956e..ce69d6435 100644 --- a/backend/authenticateUser.js +++ b/backend/authenticateUser.js @@ -1,4 +1,4 @@ -import { UserModel } from "./models/userModel" +import { UserModel } from "./models/UserModel" diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index 9942ad2bf..0d0dad52a 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -3,7 +3,7 @@ import express from "express"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; -import { UserModel } from "../models/userModel"; +import { UserModel } from "../models/UserModel"; import asyncHandler from "express-async-handler"; import dotenv from "dotenv"; import { authenticateUser } from "../authenticateUser";