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

Project Auth / Olga & Elin #324

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Project Auth API
# Project Auth - Olga & Elin

Replace this readme with your own information about your project.
This is a project to pratice authentication and authorization. The task was to create endpoints and UI for signing up, logging in/out and displaying some authenticated content for the user. The project utilises Express.js, MongoDB and React.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
Our version of the project returns the user a random inspirational coding-related quote from our database.

## 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?
As we're handing this project in really late, the final project is already finished and approved. As most of the needed codes were "already there", we thought this one would've been really straightforward - however, we ran into at least as many errors connecting the backend and frontend of this one as with the final project. Problems especially arose after deploying everything, but many of them were mostly careless mistakes and lack of knowledge.

## 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.
Please note that the deployed backend is quite slow and the initial attempt to sign up or login might take a while!

Deployed site: https://projectauth-olgaelin.netlify.app
Backend: https://project-auth-pjm5.onrender.com/
16 changes: 16 additions & 0 deletions backend/config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import mongoose from "mongoose";
import dotenv from "dotenv";
dotenv.config();
import asyncHandler from "express-async-handler";

export const connectDB = asyncHandler(async () => {
try {
const conn = await mongoose.connect(
process.env.MONGO_URL
)
console.log(`Mongo DB connected: ${conn.connection.host}`)
} catch (error) {
console.log(error);
process.exit(1);
}
})
22 changes: 22 additions & 0 deletions backend/middleware/authenticateUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UserModel } from "../models/userModel.js";

export const authenticateUser = async (req, res, next) => {

// Retrieve the access token from the request header
const accessToken = req.header("Authorization");

try {
// Find a user in the database using the retrieved access token
const user = await UserModel.findOne({ accessToken: accessToken });
if (user) {
// If a user is found, add the user object to the request object
req.user = user; // Add user to the request object
next();
} else {
res.status(401).json({ success: false, response: "Please log in" });
}
} catch (e) {
// Handle any errors that occur during the database query or user authentication
res.status(500).json({ success: false, response: e.message });
}
};
11 changes: 11 additions & 0 deletions backend/models/quoteModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import mongoose from "mongoose";

const { Schema } = mongoose;

export const quoteSchema = new Schema({
quote: {
type: String
}
});

export const QuoteModel = mongoose.model('quote', quoteSchema)
33 changes: 33 additions & 0 deletions backend/models/userModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import mongoose from "mongoose";
import crypto from "crypto";

const { Schema } = mongoose;

export const userSchema = new Schema({
username: {
type: String,
required: true,
minlength: 5,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
minlength: 6
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString('hex')
}
},
{
timestamps: true
}
)

export const UserModel = mongoose.model('user', userSchema)
9 changes: 8 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt": "^5.1.1",
"bcrypt-nodejs": "^0.0.3",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.3.2",
"express": "^4.17.3",
"mongoose": "^8.0.0",
"express-async-handler": "^1.2.0",
"express-list-endpoints": "^6.0.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.4",
"nodemon": "^3.0.1"
}
}
54 changes: 54 additions & 0 deletions backend/routes/quoteRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import express from "express"
import { QuoteModel } from "../models/quoteModel.js";
import { authenticateUser } from "../middleware/authenticateUser.js";
import asyncHandler from "express-async-handler"

const router = express.Router();

router.get(
"/getQuote",
authenticateUser,
asyncHandler(async (req, res) => {
try {
const quotes = await QuoteModel.find();
if (quotes.length > 0) {
const randomIndex = Math.floor(Math.random() * quotes.length);
const randomQuote = quotes[randomIndex];
res.json(randomQuote);
} else {
res.status(404).json({ error: "No quotes found" });
}
} catch (error) {
res.status(500).json({ success: false, response: error.message });
}
})
);


// Get all the quotes in the database
router.get(
"/allQuotes", async (req, res) => {
try {
const quotes = await QuoteModel.find();
res.json(quotes);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
);

router.post(
"/addQuote", async (req, res) => {
try {
const { quote } = req.body;
const newQuote = new QuoteModel({ quote })

await newQuote.save()
res.json(newQuote)
} catch (err) {
res.status(404).json({ success: false, message: "Could not add quote" })
}
}
)

export default router
90 changes: 90 additions & 0 deletions backend/routes/userRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import express from "express";
import { UserModel } from "../models/userModel.js"
import bcrypt, { genSaltSync } from "bcrypt";
import asyncHandler from "express-async-handler";
import dotenv from "dotenv"
dotenv.config()

const router = express.Router()

router.post("/register",
asyncHandler(async (req, res) => {
const { email, username, password } = req.body;

try {
if (!email || !username || !password) {
res.status(400)
throw new Error("Please add all fields")
}

const existingUser = await UserModel.findOne({
$or: [{ email }, { username }]
})
if (existingUser) {
res.status(400)
throw new Error(
`User with ${existingUser.username === username ? "email" : "username"}
already exists.`
)
}

const salt = genSaltSync(10);
const hashedPassword = bcrypt.hashSync(password, salt)

const newUser = new UserModel({
email,
username,
password: hashedPassword
})

await newUser.save();

res.status(201).json({
success: true,
response: {
email: newUser.email,
username: newUser.username,
id: newUser._id,
accessToken: newUser.accessToken
}
})
} catch (e) {
res.status(500).json({ success: false, response: e.message })
}
})
)

router.post("/login",
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: {
user: user.username,
id: user._id,
accessToken: user.accessToken
}
})
} catch (e) {
res.status(500).json({ success: false, response: e.message })
}
})
)

export default router
26 changes: 17 additions & 9 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
import express from "express";
import cors from "cors";
import mongoose from "mongoose";

const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo";
mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.Promise = Promise;
import dotenv from "dotenv";
dotenv.config();
import listEndpoints from "express-list-endpoints";
import userRoutes from "./routes/userRoutes.js"
import quoteRoutes from "./routes/quoteRoutes.js"
import { connectDB } from "./config/db.js"

// 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 port = process.env.PORT || 8080;
const port = process.env.PORT || 3000;
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);
app.use(quoteRoutes);

// Start defining your routes here
// Function to connect to MongoDB
connectDB();

// List all endpoints
app.get("/", (req, res) => {
res.send("Hello Technigo!");
res.send(listEndpoints(app));
});

// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
});
7 changes: 0 additions & 7 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<title>Something about coding...</title>
</head>
<body>
<div id="root"></div>
Expand Down
6 changes: 5 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
"crypto": "^1.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router": "^6.21.2",
"react-router-dom": "^6.21.2",
"zustand": "^4.5.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
Expand Down
18 changes: 17 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
import { BrowserRouter, Routes } from "react-router-dom";
import "./index.css";
import { routes } from "./routes/routes.jsx"

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
return (
<>
<BrowserRouter>
<main>
<Routes>
{routes}
</Routes>
</main>
</BrowserRouter>
</>
);
};

export default App
Loading