diff --git a/README.md b/README.md index dfa05e177..3815c7313 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ # Project Auth API -Replace this readme with your own information about your project. +Developed a backend API and frontend registration & sign-in form. + +API uses mongoose to include endpoints for user registration & sign-in, authenticated endpoint accessible only to logged-in users, storing and removing access tokens. -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? +We had issues storing the access token, but figured it out by parsing the token to JSON. ## 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-kh49.onrender.com/ + +#### Frontend: +https://heroic-beignet-77e068.netlify.app/ diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..a00d732a1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,13 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^5.1.1", "cors": "^2.8.5", + "dotenv": "^16.4.5", "express": "^4.17.3", - "mongoose": "^8.0.0", - "nodemon": "^3.0.1" + "express-list-endpoints": "^7.1.0", + "mongodb": "^6.6.2", + "mongoose": "^8.4.0", + "nodemon": "^3.1.1" } } diff --git a/backend/server.js b/backend/server.js index dfe86fb8e..d3ebfa70f 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,14 +1,59 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; +import bcrypt from "bcrypt"; +import expressListEndpoints from "express-list-endpoints"; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/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 { Schema } = mongoose; + +// Schema +const userSchema = new Schema({ + name: { + type: String, + required: true, + }, + username: { + type: String, + unique: true, + required: true, + }, + email: { + type: String, + unique: true, + required: true, + }, + password: { + type: String, + required: true, + }, + address: { + type: String, + required: true, + }, + accessToken: { + type: String, + default: () => bcrypt.genSaltSync(), + }, +}); + +// Model +const User = mongoose.model("User", userSchema); + +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 }); + } +}; + +// Defines the port the app will run on. Defaults to 8080 const port = process.env.PORT || 8080; const app = express(); @@ -18,7 +63,51 @@ 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.get("/users", async (req, res) => { + const allUsers = await User.find().exec(); + if (allUsers.length > 0) { + res.json(allUsers); + } else { + res.status(404).send("No users found"); + } +}); + +app.post("/users", async (req, res) => { + try { + const { name, username, email, password, address } = req.body; + const salt = bcrypt.genSaltSync(); + const user = new User({ + name, + username, + email, + password: bcrypt.hashSync(password, salt), + address, + }); + await user.save(); + res.status(201).json({ userId: user._id, accessToken: user.accessToken }); + } catch (error) { + res + .status(400) + .json({ message: "Could not create user", errors: error.errors }); + } +}); + +app.get("/logged-in", authenticateUser); +app.get("/logged-in", (req, res) => { + res.json({ message: "You are signed in." }); +}); + +app.post("/sessions", async (req, res) => { + const user = await User.findOne({ username: req.body.username }); + if (user && bcrypt.compareSync(req.body.password, user.password)) { + res.json({ userId: user._id, accessToken: user.accessToken }); + } else { + res.json({ notFound: true }); + } }); // Start the server diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 4dcb43901..94e01b901 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -17,4 +17,6 @@ module.exports = { { allowConstantExport: true }, ], }, + 'react/prop-types': "off" } + diff --git a/frontend/_redirects b/frontend/_redirects new file mode 100644 index 000000000..50a463356 --- /dev/null +++ b/frontend/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 0c589eccd..f66f7ef81 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,10 +1,11 @@ - + - - Vite + React + + + Log in
diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..23e2448e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,9 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1", + "zustand": "^4.5.2" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/public/_redirects b/frontend/public/_redirects new file mode 100644 index 000000000..50a463356 --- /dev/null +++ b/frontend/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..1c7be68f8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,11 @@ +import { BrowserRouter } from "react-router-dom"; +import { Home } from "./components/Home"; +import { RouteList } from "./components/RouteList"; + export const App = () => { - return
Find me in src/app.jsx!
; + return ( + + + + ); }; diff --git a/frontend/src/components/Button.jsx b/frontend/src/components/Button.jsx new file mode 100644 index 000000000..9db3ce541 --- /dev/null +++ b/frontend/src/components/Button.jsx @@ -0,0 +1,7 @@ +export const Button = ({ btnText, type, onClick }) => { + return ( + + ); +}; diff --git a/frontend/src/components/Headline.jsx b/frontend/src/components/Headline.jsx new file mode 100644 index 000000000..917d7025c --- /dev/null +++ b/frontend/src/components/Headline.jsx @@ -0,0 +1,3 @@ +export const Headline = ({ titleText }) => { + return

{titleText}

; +}; diff --git a/frontend/src/components/Home.jsx b/frontend/src/components/Home.jsx new file mode 100644 index 000000000..a18d67df1 --- /dev/null +++ b/frontend/src/components/Home.jsx @@ -0,0 +1,11 @@ +import { LogIn } from "./LogIn"; +import { Register } from "./Register"; + +export const Home = () => { + return ( +
+ + +
+ ); +}; diff --git a/frontend/src/components/LogIn.jsx b/frontend/src/components/LogIn.jsx new file mode 100644 index 000000000..4a81b5bda --- /dev/null +++ b/frontend/src/components/LogIn.jsx @@ -0,0 +1,61 @@ +import { Button } from "./Button"; +import { Headline } from "./Headline"; +import { TextInput } from "./TextInput"; +import { useStore } from "../store/useStore"; +import { useState } from "react"; + +export const LogIn = () => { + const { loginData, handleSubmitLogin, handleLoginChange } = useStore(); + const [isLoading, setIsLoading] = useState(false); + + const handleFormSubmit = async (event) => { + event.preventDefault(); + setIsLoading(true); + + try { + await handleSubmitLogin(event); + // Redirect to /logged-in after successful login + window.location.href = "/logged-in"; + } catch (error) { + console.error("Error logging in", error); + setIsLoading(false); + } + }; + + return ( + <> +
+
+ +
+

Welcome back, let the literary adventures continue!

+
+
+
+ + handleLoginChange("username", event.target.value) + } + /> + + + handleLoginChange("password", event.target.value) + } + /> +
+