diff --git a/README.md b/README.md index dfa05e177..f78797e3c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # 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 is a very simple authentication API built with Node.js and Express. It allows users to sign up, log in, and access protected routes with a valid token. ## 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 main challenge was to implement user authentication and authorization. I started by setting up the server with Node.js and Express, and then used the bcrypt library to hash user passwords for secure storage. + +If I had more time, I would implement a password reset feature, add email verification for sign up, and refine the 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://simple-authentication-app.netlify.app/ + +Backend: https://project-auth-backend.onrender.com/ diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..ddc67cdf0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,9 @@ "@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", + "dotenv": "^16.3.1", "express": "^4.17.3", "mongoose": "^8.0.0", "nodemon": "^3.0.1" diff --git a/backend/server.js b/backend/server.js index 2d7ae8aa1..fe289e842 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,27 +1,194 @@ import express from "express"; +import bodyParser from "body-parser"; import cors from "cors"; import mongoose from "mongoose"; +import crypto from "crypto"; +import bcrypt from "bcrypt-nodejs"; +import dotenv from "dotenv"; +dotenv.config(); -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +// mongoose connection +const mongoUrl = process.env.MONGO_URL +|| "mongodb://localhost/auth"; mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); 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 +// schema mongoose +const UserSchema = new mongoose.Schema({ + name: { + type: String, + required: [true, "Name is required"], + minlength: 2, + maxlength: 20, + }, + email: { + type: String, + required: [true, "Email is required"], + unique: true, + minlength: 5, + }, + password: { + type: String, + required: [true, "Password is required"], + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, +}); + +const SecretSchema = new mongoose.Schema({ + message: { + type: String, + required: [true, "Message is required"], + }, +}); + + +// User model +const User = mongoose.model("User", UserSchema, "users"); +const Secret = mongoose.model('Secret', SecretSchema, 'secrets'); + +// Defines the port the app will run on. Defaults to 8080 const port = process.env.PORT || 8080; const app = express(); -// Add middlewares to enable cors and json body parsing +// Middlewares to enable cors and json body parsing app.use(cors()); -app.use(express.json()); +app.use(bodyParser.json()); + +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({ accessToken: req.header("Authorization") }); + if (user) { + req.user = user; + next(); -// Start defining your routes here + } else if (!user) { + res.status(401).json({ loggedOut: true }); + } else if (user) { + res.status(401).json({ accessDenied: true }); + + } else { + res.status(401).json({ loggedOut: true }); + } +} + +// Routes app.get("/", (req, res) => { - res.send("Hello Technigo!"); + res.json([ + { + // home route + "path": "/", + "methods": ["GET"], + "middlewares": ["anonymous"] + }, + { + // signup route + "path": "/signup", + "methods": ["POST"], + "middlewares": ["anonymous"] + }, + { + // login route + "path": "/login", + "methods": ["POST"], + "middlewares": ["anonymous"] + }, + { + // secrets route + "path": "/secrets", + "methods": ["GET", "POST"], + "middlewares": ["authenticateUser"] + }, + { + // logout route + "path": "/logout", + "methods": ["POST"], + "middlewares": ["authenticateUser"] + } + + + ]); }); +app.post("/signup", async (req, res) => { + try { + const { name, email, password } = req.body; + 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.log(err); + res.status(400).json({ message: "Could not create user", errors: err.errors }); + } + +}); + +app.post("/secrets", async (req, res) => { + const { message } = req.body; + const secret = new Secret({ message }); + + try { + const savedSecret = await secret.save(); + res.status(201).json(savedSecret); + } catch (err) { + res.status(400).json({ message: 'Could not save secret to the Database', error: err.errors }); + } +}); + + +app.get("/secrets", authenticateUser, (req, res) => { + Secret.find().then((secrets) => { + res.json(secrets); + }); +} +); + +// login route +app.post("/login", async (req, res) => { + const { email, password } = req.body; + + try { + const user = await User.findOne({ email }); + + if (!user) { + return res.status(400).json({ message: "Invalid email or password" }); + } + + bcrypt.compare(password, user.password, (err, isMatch) => { + if (err) { + return res.status(500).json({ message: "Something went wrong", error: err }); + } + + if (!isMatch) { + return res.status(400).json({ message: "Invalid email or password" }); + } + + // User is authenticated + res.json({ userId: user._id, accessToken: user.accessToken }); + }); + + } catch (err) { + res.status(500).json({ message: "Something went wrong", error: err }); + } +}); + +// logout route +app.post("/logout", authenticateUser, async (req, res) => { + try { + req.user.accessToken = crypto.randomBytes(128).toString("hex"); + await req.user.save(); + res.status(200).json({ loggedOut: true }); + } + catch (err) { + res.status(400).json({ message: "Could not log out", error: err }); + } +} +); + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); + diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..e59d0b9b4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,13 +5,16 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "start": "vite preview", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0" + }, "dependencies": { "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" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..63423cfee 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,121 @@ -export const App = () => { - return