From c2ad173e28a4bdd4320c2a9d6d88b7508d950dc9 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:49:17 +0100 Subject: [PATCH 01/32] backend start --- backend/server.js | 77 ++++++++++++++++++++++++++++++++++++++++------- package.json | 3 ++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/backend/server.js b/backend/server.js index 2d7ae8aa1..bd41c4c2e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,27 +1,82 @@ -import express from "express"; -import cors from "cors"; -import mongoose from "mongoose"; +import express from 'express'; +import cors from 'cors'; +import mongoose from 'mongoose'; +import crypto from 'crypto'; //Library to create access token +import bcrypt from 'bcrypt-nodejs'; //To hash our password -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +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 +// when starting the server. Example command to overwrite PORT env variable value: PORT=9000 npm start const port = process.env.PORT || 8080; const app = express(); -// Add middlewares to enable cors and json body parsing +// Create mogoose model: +const User = mongoose.model('User', { + name: { + type: String, + unique: true + }, + email: { + type: String, + required: true + }, + password: { + type: String, + required: true + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString('hex') // Convert to hex desimal value to store in our database + } +}); + +// MIDDLEWARE // +// Middleware for authenticating users based on access token +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({ accessToken: req.header('Authorization') }); // Find a user in the database using the access token + if (user) { + // If a user is found, set the user in the request object and proceed to the next middleware + req.user = user; + next(); + } else { + // If no user is found, respond with a 401 Unauthorized status and a JSON indicating logout + res.status(401).json({ loggedOut: true }); + } +} + +// Middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); -// Start defining your routes here -app.get("/", (req, res) => { - res.send("Hello Technigo!"); +// DEFINING ROUTES // +// Endpoint +app.get('/', (req, res) => { + res.send('Hello Technigo!'); +}); + +// Endpoint to create a new user +app.post('/users', async (req, res) => { + try { + const { name, email, password } = req.body // Extract user details from request body + const user = new User({ name, email, password: bcrypt.hashSync(password) }); // Create a new user with hashed(!) password + await user.save(); // Save the user to the database + res.status(201).json({ id: user._id, accessToken: user.accessToken }) // Respond with user details and access token + } catch (err) { + res.status(400).json({ message: 'ERROR! Could not create user', errors: err.errors }) // Handle errors during user creation + } +}) + +app.get('/secrets', (req, res) => { + res.json({ secret: 'Secret message!' }); }); -// Start the server +console.log(crypto.randomBytes(128).toString('hex')) + +// START THE SERVER // app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); + diff --git a/package.json b/package.json index d774b8cc3..fe07c1cda 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,8 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "dependencies": { + "bcrypt-nodejs": "^0.0.3" } } From 832c9ce5ccdf3afa993e5d27353764e1849a8253 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:57:44 +0100 Subject: [PATCH 02/32] small edits --- backend/server.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/server.js b/backend/server.js index bd41c4c2e..434bd24f4 100644 --- a/backend/server.js +++ b/backend/server.js @@ -13,7 +13,7 @@ mongoose.Promise = Promise; const port = process.env.PORT || 8080; const app = express(); -// Create mogoose model: +// CREATE MONGOOSE MODEL // const User = mongoose.model('User', { name: { type: String, @@ -33,6 +33,8 @@ const User = mongoose.model('User', { } }); + + // MIDDLEWARE // // Middleware for authenticating users based on access token const authenticateUser = async (req, res, next) => { @@ -51,8 +53,10 @@ const authenticateUser = async (req, res, next) => { app.use(cors()); app.use(express.json()); + + // DEFINING ROUTES // -// Endpoint +// Endpoint (edit later) app.get('/', (req, res) => { res.send('Hello Technigo!'); }); @@ -69,6 +73,7 @@ app.post('/users', async (req, res) => { } }) +// Endpoint Secret message (remove later) app.get('/secrets', (req, res) => { res.json({ secret: 'Secret message!' }); }); From a73e2f256e27605f865d41db45cf658788256a06 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 6 Dec 2023 23:44:05 +0100 Subject: [PATCH 03/32] backend ok for now --- backend/server.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/backend/server.js b/backend/server.js index 434bd24f4..e2d58264d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -4,15 +4,19 @@ import mongoose from 'mongoose'; import crypto from 'crypto'; //Library to create access token import bcrypt from 'bcrypt-nodejs'; //To hash our password + +// CONNECT TO DATABASE 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 const port = process.env.PORT || 8080; const app = express(); + // CREATE MONGOOSE MODEL // const User = mongoose.model('User', { name: { @@ -34,7 +38,6 @@ const User = mongoose.model('User', { }); - // MIDDLEWARE // // Middleware for authenticating users based on access token const authenticateUser = async (req, res, next) => { @@ -45,7 +48,7 @@ const authenticateUser = async (req, res, next) => { next(); } else { // If no user is found, respond with a 401 Unauthorized status and a JSON indicating logout - res.status(401).json({ loggedOut: true }); + res.status(401).json({ error: 'Unauthorized access. Please log in.' }); } } @@ -54,7 +57,6 @@ app.use(cors()); app.use(express.json()); - // DEFINING ROUTES // // Endpoint (edit later) app.get('/', (req, res) => { @@ -69,15 +71,29 @@ app.post('/users', async (req, res) => { await user.save(); // Save the user to the database res.status(201).json({ id: user._id, accessToken: user.accessToken }) // Respond with user details and access token } catch (err) { - res.status(400).json({ message: 'ERROR! Could not create user', errors: err.errors }) // Handle errors during user creation + res.status(400).json({ message: 'Error creating user. Please check your input and try again.', errors: err.errors }) // Handle errors during user creation } }) -// Endpoint Secret message (remove later) -app.get('/secrets', (req, res) => { +// (!) authenticateUser - Ensures that the '/secrets' endpoint is accessible only to authenticated users by utilizing the authenticateUser middleware. +// Endpoint Secret message +app.get('/secrets', authenticateUser, (req, res) => { res.json({ secret: 'Secret message!' }); }); +// Endpoint for user authentication (to find the user) +app.post('/sessions', async (req, res) => { + // Find a user in the database with the provided email + const user = await User.findOne({ email: req.body.email }); + // Check if a user is found and if the provided password matches the stored hashed password + if (user && bcrypt.compareSync(req.body.password, user.password)) { + res.json({ userId: user._id, accessToken: user.accessToken }); // Respond with the user's ID and access token if authentication is successful + } else { + res.status(401).json({ error: 'Invalid email or password. Please try again.' }); // Respond with a JSON indicating that the user was not found + } +}); + +// NB! comment out later console.log(crypto.randomBytes(128).toString('hex')) // START THE SERVER // From 3e2b42abd8ecce85018920abf2acf57b4655d6ec Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:45:45 +0100 Subject: [PATCH 04/32] component structure --- backend/package.json | 2 +- backend/server.js | 2 +- frontend/components/pagecontent.jsx | 0 frontend/components/registration.jsx | 0 frontend/components/sign-in.jsx | 0 frontend/components/store.jsx | 0 6 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 frontend/components/pagecontent.jsx create mode 100644 frontend/components/registration.jsx create mode 100644 frontend/components/sign-in.jsx create mode 100644 frontend/components/store.jsx diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..4701f6136 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,4 +17,4 @@ "mongoose": "^8.0.0", "nodemon": "^3.0.1" } -} +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index e2d58264d..b2dcfece7 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,7 +2,7 @@ import express from 'express'; import cors from 'cors'; import mongoose from 'mongoose'; import crypto from 'crypto'; //Library to create access token -import bcrypt from 'bcrypt-nodejs'; //To hash our password +import bcrypt from 'bcrypt-nodejs'; //To hash our passwords // CONNECT TO DATABASE diff --git a/frontend/components/pagecontent.jsx b/frontend/components/pagecontent.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/components/registration.jsx b/frontend/components/registration.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/components/sign-in.jsx b/frontend/components/sign-in.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/components/store.jsx b/frontend/components/store.jsx new file mode 100644 index 000000000..e69de29bb From 3ea052b78a1e1ef7f39edd9a57644f83d0d2273a Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 13 Dec 2023 20:13:19 +0100 Subject: [PATCH 05/32] registration form --- backend/server.js | 2 +- frontend/components/store.jsx | 0 frontend/src/App.jsx | 10 ++- frontend/src/components/mainWrapper.jsx | 10 +++ frontend/{ => src}/components/pagecontent.jsx | 0 frontend/src/components/registrationForm.jsx | 70 +++++++++++++++++++ frontend/{ => src}/components/sign-in.jsx | 0 frontend/src/components/store.jsx | 9 +++ .../routes/routes.jsx} | 0 package.json | 4 +- 10 files changed, 102 insertions(+), 3 deletions(-) delete mode 100644 frontend/components/store.jsx create mode 100644 frontend/src/components/mainWrapper.jsx rename frontend/{ => src}/components/pagecontent.jsx (100%) create mode 100644 frontend/src/components/registrationForm.jsx rename frontend/{ => src}/components/sign-in.jsx (100%) create mode 100644 frontend/src/components/store.jsx rename frontend/{components/registration.jsx => src/routes/routes.jsx} (100%) diff --git a/backend/server.js b/backend/server.js index b2dcfece7..362abb959 100644 --- a/backend/server.js +++ b/backend/server.js @@ -13,7 +13,7 @@ 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 port = process.env.PORT || 8080; +const port = process.env.PORT || 8081; const app = express(); diff --git a/frontend/components/store.jsx b/frontend/components/store.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..8db7c840c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,11 @@ +//import { Provider } from 'zustand'; +//import authStore from './components/authStore'; +import { MainWrapper } from '/src/components/mainWrapper'; + export const App = () => { - return
Find me in src/app.jsx!
; + return ( + + ); }; + + diff --git a/frontend/src/components/mainWrapper.jsx b/frontend/src/components/mainWrapper.jsx new file mode 100644 index 000000000..537eea40f --- /dev/null +++ b/frontend/src/components/mainWrapper.jsx @@ -0,0 +1,10 @@ +import { RegistrationForm } from './registrationForm'; // Adjust the path accordingly + +export const MainWrapper = () => { + return ( +
+

My App

+ +
+ ); +}; diff --git a/frontend/components/pagecontent.jsx b/frontend/src/components/pagecontent.jsx similarity index 100% rename from frontend/components/pagecontent.jsx rename to frontend/src/components/pagecontent.jsx diff --git a/frontend/src/components/registrationForm.jsx b/frontend/src/components/registrationForm.jsx new file mode 100644 index 000000000..36f085a75 --- /dev/null +++ b/frontend/src/components/registrationForm.jsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; + +export const RegistrationForm = () => { + // State variables for form inputs and errors + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [errors, setErrors] = useState({}); + + // Function to validate form fields + const validateForm = () => { + const errors = {}; + // Check if any field is empty + if (!name.trim() || !email.trim() || !password.trim()) { + errors.general = 'All fields are required'; + } + setErrors(errors); // Update errors state + return Object.keys(errors).length === 0;// Return true if no errors + }; + + // Function to handle form submission + const handleSubmit = (e) => { + e.preventDefault(); + if (validateForm()) { + // If the form is valid, perform form submission logic + console.log('Form submitted:', { name, email, password }); + } else { + // If the form is not valid, log a message indicating validation failure + console.log('Form validation failed'); + } + }; + + return ( +
+

Registration Form

+
+ +
+ +
+ +
+ {/* Display a general error message */} + {errors.general &&

{errors.general}

} + +
+
+ ); +}; \ No newline at end of file diff --git a/frontend/components/sign-in.jsx b/frontend/src/components/sign-in.jsx similarity index 100% rename from frontend/components/sign-in.jsx rename to frontend/src/components/sign-in.jsx diff --git a/frontend/src/components/store.jsx b/frontend/src/components/store.jsx new file mode 100644 index 000000000..e85a430a3 --- /dev/null +++ b/frontend/src/components/store.jsx @@ -0,0 +1,9 @@ +import create from 'zustand'; + +/* +export const useAuthStore = create((set) => ({ + user: null, + setUser: (user) => set({ user }), +})); + +*/ \ No newline at end of file diff --git a/frontend/components/registration.jsx b/frontend/src/routes/routes.jsx similarity index 100% rename from frontend/components/registration.jsx rename to frontend/src/routes/routes.jsx diff --git a/package.json b/package.json index fe07c1cda..82ee362cc 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "postinstall": "npm install --prefix backend" }, "dependencies": { - "bcrypt-nodejs": "^0.0.3" + "bcrypt": "^5.1.1", + "bcrypt-nodejs": "^0.0.3", + "zustand": "^4.4.7" } } From 30232745a7adb0918c8c0e45dcf0ae95fcaec9b4 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Thu, 28 Dec 2023 22:54:20 +0100 Subject: [PATCH 06/32] updated some things --- backend/package.json | 6 ++++- backend/server.js | 11 +++------ frontend/src/components/mainWrapper.jsx | 2 +- frontend/src/components/registrationForm.jsx | 26 +++++++++++++++++--- frontend/src/components/store.jsx | 2 +- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/backend/package.json b/backend/package.json index 4701f6136..6815dbc4d 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", + "crypto": "^1.0.1", "express": "^4.17.3", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", "nodemon": "^3.0.1" } -} \ No newline at end of file +} diff --git a/backend/server.js b/backend/server.js index 362abb959..d15584aa4 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,9 +1,7 @@ import express from 'express'; -import cors from 'cors'; +import cors from 'cors'; // Security mechanism - defines how web pages in one domain interact with resources from another domain import mongoose from 'mongoose'; -import crypto from 'crypto'; //Library to create access token -import bcrypt from 'bcrypt-nodejs'; //To hash our passwords - +import crypto from 'crypto'; // CONNECT TO DATABASE const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost/auth'; @@ -63,6 +61,7 @@ app.get('/', (req, res) => { res.send('Hello Technigo!'); }); + // Endpoint to create a new user app.post('/users', async (req, res) => { try { @@ -93,11 +92,9 @@ app.post('/sessions', async (req, res) => { } }); -// NB! comment out later -console.log(crypto.randomBytes(128).toString('hex')) + // START THE SERVER // app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); - diff --git a/frontend/src/components/mainWrapper.jsx b/frontend/src/components/mainWrapper.jsx index 537eea40f..ff058b460 100644 --- a/frontend/src/components/mainWrapper.jsx +++ b/frontend/src/components/mainWrapper.jsx @@ -1,4 +1,4 @@ -import { RegistrationForm } from './registrationForm'; // Adjust the path accordingly +import { RegistrationForm } from './registrationForm'; export const MainWrapper = () => { return ( diff --git a/frontend/src/components/registrationForm.jsx b/frontend/src/components/registrationForm.jsx index 36f085a75..f2dcd5a65 100644 --- a/frontend/src/components/registrationForm.jsx +++ b/frontend/src/components/registrationForm.jsx @@ -19,17 +19,35 @@ export const RegistrationForm = () => { }; // Function to handle form submission - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); + if (validateForm()) { - // If the form is valid, perform form submission logic - console.log('Form submitted:', { name, email, password }); + try { + // Make a POST request + const response = await fetch('http://localhost:8081/users', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name, + email, + password, + }), + }); + // Parse the JSON response + const data = await response.json(); + console.log('Form submitted successfully:', data); + } catch (error) { + console.error('Error submitting form:', error.message); + } } else { - // If the form is not valid, log a message indicating validation failure console.log('Form validation failed'); } }; + return (

Registration Form

diff --git a/frontend/src/components/store.jsx b/frontend/src/components/store.jsx index e85a430a3..9adaca7ee 100644 --- a/frontend/src/components/store.jsx +++ b/frontend/src/components/store.jsx @@ -1,7 +1,7 @@ import create from 'zustand'; /* -export const useAuthStore = create((set) => ({ +export const authStore = create((set) => ({ user: null, setUser: (user) => set({ user }), })); From 85ce1466c685ec68663a72871b31ccc38d062e5b Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:48:52 +0100 Subject: [PATCH 07/32] frontend --- backend/server.js | 1 + frontend/src/App.jsx | 2 - frontend/src/components/authStore.jsx | 12 +++ frontend/src/components/loginForm.jsx | 87 ++++++++++++++++++++ frontend/src/components/logoutButton.jsx | 18 ++++ frontend/src/components/mainWrapper.jsx | 4 + frontend/src/components/registrationForm.jsx | 7 +- frontend/src/components/sign-in.jsx | 0 frontend/src/components/store.jsx | 9 -- frontend/src/main.jsx | 8 +- 10 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/authStore.jsx create mode 100644 frontend/src/components/loginForm.jsx create mode 100644 frontend/src/components/logoutButton.jsx delete mode 100644 frontend/src/components/sign-in.jsx delete mode 100644 frontend/src/components/store.jsx diff --git a/backend/server.js b/backend/server.js index d15584aa4..51f799e2c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,6 +2,7 @@ import express from 'express'; import cors from 'cors'; // Security mechanism - defines how web pages in one domain interact with resources from another domain import mongoose from 'mongoose'; import crypto from 'crypto'; +import bcrypt from 'bcrypt'; // CONNECT TO DATABASE const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost/auth'; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8db7c840c..91660f1c6 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,3 @@ -//import { Provider } from 'zustand'; -//import authStore from './components/authStore'; import { MainWrapper } from '/src/components/mainWrapper'; export const App = () => { diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx new file mode 100644 index 000000000..1fa5a4869 --- /dev/null +++ b/frontend/src/components/authStore.jsx @@ -0,0 +1,12 @@ +import { create } from 'zustand'; + +export const useAuthStore = create((set) => ({ + user: null, + isAuthenticated: false, + accessToken: null, + setUser: (user, accessToken) => + set({ user, accessToken, isAuthenticated: true }), + logout: () => set({ user: null, accessToken: null, isAuthenticated: false }), +})); + + diff --git a/frontend/src/components/loginForm.jsx b/frontend/src/components/loginForm.jsx new file mode 100644 index 000000000..1fa4714e1 --- /dev/null +++ b/frontend/src/components/loginForm.jsx @@ -0,0 +1,87 @@ +import { useState } from 'react'; +import useAuthStore from './authStore'; + +export const LoginForm = () => { + const { setUser } = useAuthStore(); + // State variables for form inputs and errors + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [errors, setErrors] = useState({}); + + // Function to validate form fields + const validateForm = () => { + const errors = {}; + // Check if any field is empty + if (!email.trim() || !password.trim()) { + errors.general = 'All fields are required'; + } + setErrors(errors); // Update errors state + return Object.keys(errors).length === 0; // Return true if no errors + }; + + // Function to handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); + + if (validateForm()) { + try { + // Make a POST request for user login + const response = await fetch('http://localhost:8081/sessions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + password, + }), + }); + // Parse the JSON response + const data = await response.json(); + + //NB DELETE LATER // + console.log('API Response:', data); + + const { userId, accessToken, name, email } = data; + + setUser({ id: userId, name, email }, accessToken); + console.log('Login successful:', data); + // Handle successful login (update global state, redirect, etc.) + } catch (error) { + console.error('Error during login:', error.message); + // Handle login error (display error message, etc.) + } + } else { + console.log('Form validation failed'); + } + }; + + return ( +
+

Login Form

+
+ +
+ +
+ {/* Display a general error message */} + {errors.general &&

{errors.general}

} + +
+
+ ); +}; diff --git a/frontend/src/components/logoutButton.jsx b/frontend/src/components/logoutButton.jsx new file mode 100644 index 000000000..d11624036 --- /dev/null +++ b/frontend/src/components/logoutButton.jsx @@ -0,0 +1,18 @@ +import useAuthStore from './authStore'; + +export const LogoutButton = () => { + const logout = useAuthStore((state) => state.logout); + + + const handleLogout = () => { + logout(); + // Redirect to login form after signing out + window.location.href = '/login'; + }; + + return ( + + ); +}; diff --git a/frontend/src/components/mainWrapper.jsx b/frontend/src/components/mainWrapper.jsx index ff058b460..a7e4670de 100644 --- a/frontend/src/components/mainWrapper.jsx +++ b/frontend/src/components/mainWrapper.jsx @@ -1,10 +1,14 @@ import { RegistrationForm } from './registrationForm'; +import { LoginForm } from './loginForm'; +import { LogoutButton } from './logoutButton'; export const MainWrapper = () => { return (

My App

+ +
); }; diff --git a/frontend/src/components/registrationForm.jsx b/frontend/src/components/registrationForm.jsx index f2dcd5a65..4f32fd4a3 100644 --- a/frontend/src/components/registrationForm.jsx +++ b/frontend/src/components/registrationForm.jsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import useAuthStore from './authStore'; export const RegistrationForm = () => { // State variables for form inputs and errors @@ -24,7 +25,7 @@ export const RegistrationForm = () => { if (validateForm()) { try { - // Make a POST request + // Make a POST request for user registration const response = await fetch('http://localhost:8081/users', { method: 'POST', headers: { @@ -38,9 +39,13 @@ export const RegistrationForm = () => { }); // Parse the JSON response const data = await response.json(); + // Update user and authentication state on successful registration + useAuthStore.setState({ user: data.user, accessToken: data.accessToken, isAuthenticated: true }); console.log('Form submitted successfully:', data); + // Handle successful registration } catch (error) { console.error('Error submitting form:', error.message); + // Handle registration error (display error message, etc.) } } else { console.log('Form validation failed'); diff --git a/frontend/src/components/sign-in.jsx b/frontend/src/components/sign-in.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/components/store.jsx b/frontend/src/components/store.jsx deleted file mode 100644 index 9adaca7ee..000000000 --- a/frontend/src/components/store.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import create from 'zustand'; - -/* -export const authStore = create((set) => ({ - user: null, - setUser: (user) => set({ user }), -})); - -*/ \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 51294f399..16db3b0ab 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -2,9 +2,13 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "./App.jsx"; import "./index.css"; +import { Provider } from 'zustand'; +import useAuthStore from './authStore'; // -ReactDOM.createRoot(document.getElementById("root")).render( +ReactDOM.createRoot(document.getElementById('root')).render( - + + + ); From 03971e6b267b7e2fcaa517676a2814a8e6cb248a Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:06:08 +0100 Subject: [PATCH 08/32] more frontend --- frontend/package.json | 6 +- frontend/src/App.jsx | 6 +- frontend/src/components/authPageContent.jsx | 62 ++++++++++++++++++++ frontend/src/components/loginForm.jsx | 15 ++++- frontend/src/components/logoutButton.jsx | 7 ++- frontend/src/components/pagecontent.jsx | 0 frontend/src/components/registrationForm.jsx | 15 ++++- frontend/src/main.jsx | 2 +- frontend/src/routes/routes.jsx | 17 ++++++ 9 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/authPageContent.jsx delete mode 100644 frontend/src/components/pagecontent.jsx diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..faeb930be 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.21.1", + "zustand": "^4.4.7" }, "devDependencies": { "@types/react": "^18.2.15", @@ -21,6 +23,6 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.5" + "vite": "^5.0.10" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 91660f1c6..360e852cd 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,12 @@ import { MainWrapper } from '/src/components/mainWrapper'; +import { Routes } from './routes/routes'; export const App = () => { return ( - +
+ + +
); }; diff --git a/frontend/src/components/authPageContent.jsx b/frontend/src/components/authPageContent.jsx new file mode 100644 index 000000000..fff9c8c14 --- /dev/null +++ b/frontend/src/components/authPageContent.jsx @@ -0,0 +1,62 @@ +// components/AuthPageContent.jsx +import { useEffect, useState } from 'react'; +import { useAuthStore } from './authStore'; + +export const AuthPageContent = () => { + // Access the accessToken from Zustand state + const { accessToken } = useAuthStore(); + + // State to store the data fetched from the authenticated endpoint + const [contentData, setContentData] = useState(null); + + // useEffect to run the fetch operation when the component mounts or accessToken changes + useEffect(() => { + // Define an asynchronous function to fetch authenticated content + const fetchAuthenticatedContent = async () => { + try { + // Make a GET request to the authenticated endpoint + const response = await fetch('http://localhost:8081/secrets', { + headers: { + Authorization: accessToken, + }, + }); + + // Check if the response is successful + if (response.ok) { + // Parse the JSON response + const data = await response.json(); + // Update the state with the fetched data + setContentData(data); + } else { + // Handle errors if the response is not successful + console.error('Error fetching authenticated content'); + } + } catch (error) { + // Handle network or other errors + console.error('Error:', error.message); + } + }; + + // Check if there is a valid accessToken before making the request + if (accessToken) { + // Call the fetchAuthenticatedContent function + fetchAuthenticatedContent(); + } + }, [accessToken]); // useEffect will re-run if accessToken changes + + return ( +
+

Authenticated Content Page

+ {contentData === null ? ( + // Loading state +

Loading...

+ ) : contentData ? ( + // If contentData is available, display the page content +

{contentData.secret}

+ ) : ( + // If contentData is not available, display an error message +

Content not found

+ )} +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/components/loginForm.jsx b/frontend/src/components/loginForm.jsx index 1fa4714e1..a922e0d1c 100644 --- a/frontend/src/components/loginForm.jsx +++ b/frontend/src/components/loginForm.jsx @@ -1,8 +1,12 @@ +import { useHistory } from 'react-router-dom'; import { useState } from 'react'; -import useAuthStore from './authStore'; +import { useAuthStore } from './authStore'; export const LoginForm = () => { + const history = useHistory(); // Initialize useHistory + const { setUser } = useAuthStore(); + // State variables for form inputs and errors const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -36,6 +40,10 @@ export const LoginForm = () => { password, }), }); + // Check if the login was successful + if (!response.ok) { + throw new Error('Login failed. Please try again.'); + } // Parse the JSON response const data = await response.json(); @@ -47,8 +55,13 @@ export const LoginForm = () => { setUser({ id: userId, name, email }, accessToken); console.log('Login successful:', data); // Handle successful login (update global state, redirect, etc.) + + history.push('/secrets'); // // Redirect to /secrets after successful login) + } catch (error) { console.error('Error during login:', error.message); + setErrors({ general: 'Login failed. Please try again.' }); + // Handle login error (display error message, etc.) } } else { diff --git a/frontend/src/components/logoutButton.jsx b/frontend/src/components/logoutButton.jsx index d11624036..49984c50a 100644 --- a/frontend/src/components/logoutButton.jsx +++ b/frontend/src/components/logoutButton.jsx @@ -1,13 +1,16 @@ -import useAuthStore from './authStore'; +import { useHistory } from 'react-router-dom'; +import { useAuthStore } from './authStore'; export const LogoutButton = () => { + const history = useHistory(); // Initialize useHistory + const logout = useAuthStore((state) => state.logout); const handleLogout = () => { logout(); // Redirect to login form after signing out - window.location.href = '/login'; + history.push('/login'); }; return ( diff --git a/frontend/src/components/pagecontent.jsx b/frontend/src/components/pagecontent.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/components/registrationForm.jsx b/frontend/src/components/registrationForm.jsx index 4f32fd4a3..80bf89cae 100644 --- a/frontend/src/components/registrationForm.jsx +++ b/frontend/src/components/registrationForm.jsx @@ -1,7 +1,11 @@ import { useState } from 'react'; -import useAuthStore from './authStore'; +import { useHistory } from 'react-router-dom'; +import { useAuthStore } from './authStore'; export const RegistrationForm = () => { + + const history = useHistory(); // Initialize useHistory + // State variables for form inputs and errors const [name, setName] = useState(''); const [email, setEmail] = useState(''); @@ -37,14 +41,23 @@ export const RegistrationForm = () => { password, }), }); + // Check if the registration was successful + if (!response.ok) { + throw new Error('Registration failed. Please try again.'); + } // Parse the JSON response const data = await response.json(); // Update user and authentication state on successful registration useAuthStore.setState({ user: data.user, accessToken: data.accessToken, isAuthenticated: true }); console.log('Form submitted successfully:', data); // Handle successful registration + + history.push('/login'); + // Redirect to /login after successful registration + } catch (error) { console.error('Error submitting form:', error.message); + setErrors({ general: 'Registration failed. Please try again.' }); // Handle registration error (display error message, etc.) } } else { diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 16db3b0ab..72066602b 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client"; import { App } from "./App.jsx"; import "./index.css"; import { Provider } from 'zustand'; -import useAuthStore from './authStore'; // +import { useAuthStore } from './components/authStore'; ReactDOM.createRoot(document.getElementById('root')).render( diff --git a/frontend/src/routes/routes.jsx b/frontend/src/routes/routes.jsx index e69de29bb..732e79a68 100644 --- a/frontend/src/routes/routes.jsx +++ b/frontend/src/routes/routes.jsx @@ -0,0 +1,17 @@ +import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; +import { RegistrationForm } from '../components/registrationForm'; +import { LoginForm } from '../components/loginForm'; +import { AuthPageContent } from '../components/authPageContent'; + + +export const Routes = () => { + return ( + + + + + + + + ); +}; From 52728d022e3fa8a3a5f4b982027bcf203b88e2d3 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Thu, 18 Jan 2024 23:14:37 +0100 Subject: [PATCH 09/32] backend fixed --- backend/.gitignore | 3 +- backend/db.js | 64 ++++++++++++++++++ backend/package.json | 5 +- backend/server.js | 110 +++++++------------------------ backend/userLogin/controller.js | 113 ++++++++++++++++++++++++++++++++ backend/userLogin/middleware.js | 30 +++++++++ backend/userLogin/model.js | 52 +++++++++++++++ backend/userLogin/routes.js | 30 +++++++++ 8 files changed, 320 insertions(+), 87 deletions(-) create mode 100644 backend/db.js create mode 100644 backend/userLogin/controller.js create mode 100644 backend/userLogin/middleware.js create mode 100644 backend/userLogin/model.js create mode 100644 backend/userLogin/routes.js diff --git a/backend/.gitignore b/backend/.gitignore index 25c8fdbab..8f5e467c8 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ node_modules -package-lock.json \ No newline at end of file +package-lock.json +.env \ No newline at end of file diff --git a/backend/db.js b/backend/db.js new file mode 100644 index 000000000..54049cb20 --- /dev/null +++ b/backend/db.js @@ -0,0 +1,64 @@ +// Configuration file for connecting to MongoDB using Mongoose + +// Importing the Mongoose library for MongoDB interactions +import mongoose from "mongoose"; + +// Importing the dotenv library to manage environment variables +import dotenv from "dotenv"; +// Execute the config function to load variables from .env file into process.env +dotenv.config(); + +// Configuration options for Mongoose (with deprecated options removed) +const mongooseOptions = { + useNewUrlParser: true, // Use the new URL string parser + useUnifiedTopology: true // Use the new Server Discover and Monitoring engine +}; + +/** + * Asynchronous function to connect to MongoDB. + * This function tries to establish a connection with the MongoDB server + * using the connection string provided in the .env file. + */ + +// CONNECT TO LOCAL MONGO DB +export const connectDB = async () => { + // Check if the MONGO_URL environment variable is set + if (!process.env.MONGO_URL) { + console.error("MONGO_URL is not set in .env"); + // Exit the application if MONGO_URL is not set + process.exit(1); + } + + try { + // Attempting to connect to MongoDB using the provided URL and options + const conn = await mongoose.connect(process.env.MONGO_URL, mongooseOptions); + // Logging a success message with the connected database host + console.log(`Local MongoDB Connected: ${conn.connection.host}`); + } catch (error) { + // Logging the error if the connection fails + console.error("Local MongoDB connection error:", error); + // Exiting the application in case of connection failure + process.exit(1); + } +}; + +// CONNECT TO ATLAS DB +export const connectAtlasDB = async () => { + // Check if the ATLAS_MONGO_URL environment variable is set + if (!process.env.ATLAS_MONGO_URL) { + console.error("ATLAS_MONGO_URL is not set in .env"); + process.exit(1); + } + + try { + // Attempting to connect to MongoDB Atlas using the provided URL and options + const conn = await mongoose.connect(process.env.ATLAS_MONGO_URL, mongooseOptions); + // Logging a success message with the connected database host + console.log(`MongoDB Atlas Connected: ${conn.connection.host}`); + } catch (error) { + // Logging the error if the connection fails + console.error("MongoDB Atlas connection error:", error); + // Exiting the application in case of connection failure + process.exit(1); + } +}; diff --git a/backend/package.json b/backend/package.json index 6815dbc4d..a82dfd352 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,12 +13,15 @@ "@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.1", "express": "^4.17.3", "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", + "node.js": "^0.0.1-security", "nodemon": "^3.0.1" } -} +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 51f799e2c..5bbe63fa0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,101 +1,41 @@ -import express from 'express'; -import cors from 'cors'; // Security mechanism - defines how web pages in one domain interact with resources from another domain -import mongoose from 'mongoose'; -import crypto from 'crypto'; -import bcrypt from 'bcrypt'; +// Importing necessary libraries and modules +import express from "express"; +import cors from "cors"; +import dotenv from "dotenv"; +import userRoutes from "./userLogin/routes"; -// CONNECT TO DATABASE -const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost/auth'; -mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.Promise = Promise; +// Import database connection functions +import { connectDB } from "./db"; +dotenv.config(); // Load and parse environment variables from the .env file -// 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 || 8081; -const app = express(); - - -// CREATE MONGOOSE MODEL // -const User = mongoose.model('User', { - name: { - type: String, - unique: true - }, - email: { - type: String, - required: true - }, - password: { - type: String, - required: true - }, - accessToken: { - type: String, - default: () => crypto.randomBytes(128).toString('hex') // Convert to hex desimal value to store in our database - } -}); +// Retrieve the port number from environment variables or set default +const port = process.env.PORT || 3000; +// Create an Express application instance +const app = express(); -// MIDDLEWARE // -// Middleware for authenticating users based on access token -const authenticateUser = async (req, res, next) => { - const user = await User.findOne({ accessToken: req.header('Authorization') }); // Find a user in the database using the access token - if (user) { - // If a user is found, set the user in the request object and proceed to the next middleware - req.user = user; - next(); - } else { - // If no user is found, respond with a 401 Unauthorized status and a JSON indicating logout - res.status(401).json({ error: 'Unauthorized access. Please log in.' }); - } -} - -// Middlewares to enable cors and json body parsing +// Middlewares setup app.use(cors()); app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +// Registering API routes with the Express application +app.use('/', userRoutes); -// DEFINING ROUTES // -// Endpoint (edit later) -app.get('/', (req, res) => { - res.send('Hello Technigo!'); +// Error handling middleware +app.use(function (err, req, res, next) { + console.error(err.stack); + res.status(500).send('Something broke!'); }); +// Connecting to Mongo DB Atlas Instance +connectDB(); // Connects to MongoDB Atlas -// Endpoint to create a new user -app.post('/users', async (req, res) => { - try { - const { name, email, password } = req.body // Extract user details from request body - const user = new User({ name, email, password: bcrypt.hashSync(password) }); // Create a new user with hashed(!) password - await user.save(); // Save the user to the database - res.status(201).json({ id: user._id, accessToken: user.accessToken }) // Respond with user details and access token - } catch (err) { - res.status(400).json({ message: 'Error creating user. Please check your input and try again.', errors: err.errors }) // Handle errors during user creation - } -}) - -// (!) authenticateUser - Ensures that the '/secrets' endpoint is accessible only to authenticated users by utilizing the authenticateUser middleware. -// Endpoint Secret message -app.get('/secrets', authenticateUser, (req, res) => { - res.json({ secret: 'Secret message!' }); -}); - -// Endpoint for user authentication (to find the user) -app.post('/sessions', async (req, res) => { - // Find a user in the database with the provided email - const user = await User.findOne({ email: req.body.email }); - // Check if a user is found and if the provided password matches the stored hashed password - if (user && bcrypt.compareSync(req.body.password, user.password)) { - res.json({ userId: user._id, accessToken: user.accessToken }); // Respond with the user's ID and access token if authentication is successful - } else { - res.status(401).json({ error: 'Invalid email or password. Please try again.' }); // Respond with a JSON indicating that the user was not found - } +// Start the server and listen for incoming requests +app.listen(port, () => { + console.log(`Server running on http://localhost:${port}`); }); -// START THE SERVER // -app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`); -}); diff --git a/backend/userLogin/controller.js b/backend/userLogin/controller.js new file mode 100644 index 000000000..26c4a6113 --- /dev/null +++ b/backend/userLogin/controller.js @@ -0,0 +1,113 @@ +import { UserModel } from "./model"; +import asyncHandler from "express-async-handler"; +import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; + +const generateToken = (id) => { + return jwt.sign({ id }, process.env.JWT_SECRET, { + expiresIn: '24h' // Token expires in 24 hours + }); +}; + +// FUNCTION FOR USER REGISTRATION +export const registerUserController = asyncHandler(async (req, res) => { + // Extract email, username and password from the request body + const { username, password, email } = req.body; + // In this try section of the try catch we will first do some conditional logic and then generate the newUser with a crypted password within the DB. + try { + // 1st Condition + // Check wether all fields of registration logic are NOT [!email] inputted from the request.body object + if (!username || !email || !password) { + // if so, set http status to a 400code + res.status(400); + // and throw new error with some info + throw new Error("Please add all fields"); + } + // 2nd Condition + // Check if the current user trying to register is using an usernam or email that matches with the same username or email in the database, so they would have to choose something diferent + 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` + ); + } + + // Generate a salt and hash the user's password + const salt = bcrypt.genSaltSync(10); + + const hashedPassword = bcrypt.hashSync(password, salt); + // Create a new user instance with the hashed password + const newUser = new UserModel({ + username, + email, + password: hashedPassword, + }); + + // Description: Save the new user instance to the database + await newUser.save(); + + // Generate a JWT token for the new user + const token = generateToken(newUser._id); + + // Respond with a success message, user details, and the token + res.status(201).json({ + success: true, + response: { + username: newUser.username, + email: newUser.email, + id: newUser._id, + token // Send the token to the user + }, + }); + } catch (e) { + // Handle any errors that occur during the registration process + res.status(500).json({ success: false, response: e.message }); + } +}); + + +// FUNCTION FOR USER LOGIN +export const loginUserController = asyncHandler(async (req, res) => { + // Extract username and password from the request body + const { username, password } = req.body; + + try { + // Find a user with the provided username in the database + const user = await UserModel.findOne({ username }); + if (!user) { + // If no user is found with the provided username, respond with a 401 Unauthorized and a user not found message + return res + .status(401) + .json({ success: false, response: "User not found" }); + } + + // Compare the provided password with the hashed password in the database + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + // If the provided password doesn't match the stored password, respond with a 401 Unauthorized and an incorrect password message + return res + .status(401) + .json({ success: false, response: "Incorrect password" }); + } + // Generate a JWT token for the user + const token = generateToken(user._id); + + // Respond with a success message, user details, and the token + res.status(200).json({ + success: true, + response: { + username: user.username, + id: user._id, + token // token for the user using the acessToken generated from the model, // Use the generated token here + }, + }); + } catch (e) { + // Handle any errors that occur during the login process + res.status(500).json({ success: false, response: e.message }); + } +}); + diff --git a/backend/userLogin/middleware.js b/backend/userLogin/middleware.js new file mode 100644 index 000000000..f8a571557 --- /dev/null +++ b/backend/userLogin/middleware.js @@ -0,0 +1,30 @@ + +import jwt from 'jsonwebtoken'; +import { UserModel } from './model'; +import dotenv from 'dotenv'; +dotenv.config(); + +export const authenticateUser = async (req, res, next) => { + const token = req.header('Authorization')?.split(' ')[1]; + if (!token) { + return res.status(401).json({ success: false, message: 'No token, authorization denied' }); + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + const user = await UserModel.findById(decoded.id).select('-password'); + + if (!user) { + return res.status(401).json({ success: false, message: 'Token is not valid' }); + } + + if (user.role !== 'user') { + return res.status(403).json({ success: false, message: 'Access denied: requires user role' }); + } + + req.user = user; + next(); + } catch (e) { + res.status(401).json({ success: false, message: 'Token is not valid' }); + } +}; diff --git a/backend/userLogin/model.js b/backend/userLogin/model.js new file mode 100644 index 000000000..710a36c94 --- /dev/null +++ b/backend/userLogin/model.js @@ -0,0 +1,52 @@ +import mongoose from "mongoose"; + +// Import the Schema class from the Mongoose library +// Destructures the Schema class from the Mongoose library, allowing us to create a schema. +const { Schema } = mongoose; + +// Creates a new Mongoose schema named userSchema that defines the structure of a user document in the MongoDB collection. It includes fields like username, password, and accessToken, specifying their data types, validation rules, and default values. +const userSchema = new Schema( + { + // Define the 'username' field with a String data type + username: { + type: String, // Specifies that 'username' should be a string + required: true, // Indicates that 'username' is a required field + minlength: 5, // Sets a minimum length + unique: true, // Make sure the username is unique in the database + }, + email: { + type: String, + required: true, + unique: true, + }, + // Define the 'password' field with a String data type + password: { + type: String, + required: true, + minlength: 6, + validate: { + // Password requirements added + validator: function (password) { + const hasNumber = /[0-9]/.test(password); + const hasCapitalLetter = /[A-Z]/.test(password); + const hasSpecialSign = /[!@#\$%\^&\*]/.test(password); + return hasNumber && hasCapitalLetter && hasSpecialSign; + }, + message: 'Password must contain at least one number, one capital letter, and one special character.' + } + }, + role: { + type: String, + default: 'user', + }, + }, + { + timestamps: true, + } +); + +// Create a Mongoose model named 'UserModel' based on the 'userSchema' for the 'users' collection +// This model is used to interact with the "users" collection in the MongoDB database. It allows you to perform CRUD operations on user documents and provides methods for data validation based on the schema. +export const UserModel = mongoose.model("User", userSchema); + + diff --git a/backend/userLogin/routes.js b/backend/userLogin/routes.js new file mode 100644 index 000000000..837972fcd --- /dev/null +++ b/backend/userLogin/routes.js @@ -0,0 +1,30 @@ +// Import the necessary modules and functions +import express from "express"; +import { authenticateUser } from "./middleware"; +import { + registerUserController, + loginUserController, +} from "./controller"; // Import controller functions for user registration and login + +// Create an instance of the Express router +const router = express.Router(); + +// REGISTER ROUTE: Handle user registration +router.post("/register", registerUserController); // When a POST request is made to /register, execute the registerUserController function + +// LOGIN ROUTE: Handle user login +router.post("/login", loginUserController); // When a POST request is made to /login, execute the loginUserController function + +// AUTHENTICATED USER ROUTE: Display a secret message +router.get('/userpage', authenticateUser, (req, res) => { + // Send a secret message to the authenticated user + res.json({ + success: true, + secretMessage: "This is a secret message only for authenticated users!" + }); +}); + +// Export the router for use in the main application +export default router; + +// In summary, this file sets up routes using the Express router for user registration and login operations. It associates each route with the corresponding controller function. These routes define the API endpoints for handling user registration and login within the application. From 742be672325d9ce007c6a50d53085ee7709ae8bd Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Fri, 19 Jan 2024 01:22:16 +0100 Subject: [PATCH 10/32] backend criteria added --- backend/package.json | 5 +-- backend/userLogin/controller.js | 55 +++++++++++++++++---------------- backend/userLogin/model.js | 16 +++------- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/backend/package.json b/backend/package.json index a82dfd352..556d6bf04 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,6 +22,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", "node.js": "^0.0.1-security", - "nodemon": "^3.0.1" + "nodemon": "^3.0.1", + "validator": "^13.11.0" } -} \ No newline at end of file +} diff --git a/backend/userLogin/controller.js b/backend/userLogin/controller.js index 26c4a6113..04c7eff46 100644 --- a/backend/userLogin/controller.js +++ b/backend/userLogin/controller.js @@ -9,46 +9,45 @@ const generateToken = (id) => { }); }; +// Password validation function +const isValidPassword = (password) => { + const hasNumber = /[0-9]/.test(password); + const hasCapitalLetter = /[A-Z]/.test(password); + const hasSpecialSign = /[!@#$%^&*]/.test(password); + return hasNumber && hasCapitalLetter && hasSpecialSign && password.length >= 6; +}; + // FUNCTION FOR USER REGISTRATION export const registerUserController = asyncHandler(async (req, res) => { // Extract email, username and password from the request body const { username, password, email } = req.body; - // In this try section of the try catch we will first do some conditional logic and then generate the newUser with a crypted password within the DB. + try { - // 1st Condition - // Check wether all fields of registration logic are NOT [!email] inputted from the request.body object + // Check whether all fields of registration are inputted if (!username || !email || !password) { - // if so, set http status to a 400code res.status(400); - // and throw new error with some info throw new Error("Please add all fields"); } - // 2nd Condition - // Check if the current user trying to register is using an usernam or email that matches with the same username or email in the database, so they would have to choose something diferent - const existingUser = await UserModel.findOne({ - $or: [{ username }, { email }], - }); + + // Check if password meets criteria + if (!isValidPassword(password)) { + return res.status(400).json({ success: false, message: "Password must contain at least one number, one capital letter, and one special character, and be at least 6 characters long." }); + } + + // Check if the username or email already exists in the database + 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` - ); + throw new Error(`User with ${existingUser.username === username ? "username" : "email"} already exists`); } // Generate a salt and hash the user's password const salt = bcrypt.genSaltSync(10); - const hashedPassword = bcrypt.hashSync(password, salt); - // Create a new user instance with the hashed password - const newUser = new UserModel({ - username, - email, - password: hashedPassword, - }); - // Description: Save the new user instance to the database - await newUser.save(); + // Create a new user instance with the hashed password + const newUser = new UserModel({ username, email, password: hashedPassword }); + await newUser.save(); // Save the new user instance to the database // Generate a JWT token for the new user const token = generateToken(newUser._id); @@ -60,11 +59,15 @@ export const registerUserController = asyncHandler(async (req, res) => { username: newUser.username, email: newUser.email, id: newUser._id, - token // Send the token to the user - }, + token + } }); } catch (e) { - // Handle any errors that occur during the registration process + if (e.name === 'ValidationError') { + // Handle Mongoose validation errors + return res.status(400).json({ success: false, message: e.message }); + } + // Handle other types of errors res.status(500).json({ success: false, response: e.message }); } }); diff --git a/backend/userLogin/model.js b/backend/userLogin/model.js index 710a36c94..9ffbb8442 100644 --- a/backend/userLogin/model.js +++ b/backend/userLogin/model.js @@ -1,4 +1,5 @@ import mongoose from "mongoose"; +import validator from 'validator'; // Import the Schema class from the Mongoose library // Destructures the Schema class from the Mongoose library, allowing us to create a schema. @@ -16,24 +17,16 @@ const userSchema = new Schema( }, email: { type: String, + minlength: 4, required: true, unique: true, + validate: [validator.isEmail, 'Invalid email address'] }, // Define the 'password' field with a String data type password: { type: String, required: true, - minlength: 6, - validate: { - // Password requirements added - validator: function (password) { - const hasNumber = /[0-9]/.test(password); - const hasCapitalLetter = /[A-Z]/.test(password); - const hasSpecialSign = /[!@#\$%\^&\*]/.test(password); - return hasNumber && hasCapitalLetter && hasSpecialSign; - }, - message: 'Password must contain at least one number, one capital letter, and one special character.' - } + minlength: 6 }, role: { type: String, @@ -45,6 +38,7 @@ const userSchema = new Schema( } ); + // Create a Mongoose model named 'UserModel' based on the 'userSchema' for the 'users' collection // This model is used to interact with the "users" collection in the MongoDB database. It allows you to perform CRUD operations on user documents and provides methods for data validation based on the schema. export const UserModel = mongoose.model("User", userSchema); From ea8b406bd2b01a173ba14825619ec5e3d3a4b0c9 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Fri, 19 Jan 2024 01:22:41 +0100 Subject: [PATCH 11/32] frontend set up --- frontend/src/App.jsx | 7 +- frontend/src/components/authPageContent.jsx | 62 ----------- frontend/src/components/authStore.jsx | 66 ++++++++++-- frontend/src/components/homePage.jsx | 66 ++++++++++++ frontend/src/components/loginForm.jsx | 100 ----------------- frontend/src/components/logoutButton.jsx | 21 ---- frontend/src/components/mainWrapper.jsx | 14 --- frontend/src/components/registerPage.jsx | 58 ++++++++++ frontend/src/components/registrationForm.jsx | 106 ------------------- frontend/src/components/userPage.jsx | 26 +++++ frontend/src/main.jsx | 25 ++++- frontend/src/routes/protectRoute.jsx | 15 +++ frontend/src/routes/routes.jsx | 24 ++--- 13 files changed, 258 insertions(+), 332 deletions(-) delete mode 100644 frontend/src/components/authPageContent.jsx create mode 100644 frontend/src/components/homePage.jsx delete mode 100644 frontend/src/components/loginForm.jsx delete mode 100644 frontend/src/components/logoutButton.jsx delete mode 100644 frontend/src/components/mainWrapper.jsx create mode 100644 frontend/src/components/registerPage.jsx delete mode 100644 frontend/src/components/registrationForm.jsx create mode 100644 frontend/src/components/userPage.jsx create mode 100644 frontend/src/routes/protectRoute.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 360e852cd..168be5d41 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,13 +1,10 @@ -import { MainWrapper } from '/src/components/mainWrapper'; -import { Routes } from './routes/routes'; +import { AppRoutes } from './routes/routes'; export const App = () => { return (
- - +
); }; - diff --git a/frontend/src/components/authPageContent.jsx b/frontend/src/components/authPageContent.jsx deleted file mode 100644 index fff9c8c14..000000000 --- a/frontend/src/components/authPageContent.jsx +++ /dev/null @@ -1,62 +0,0 @@ -// components/AuthPageContent.jsx -import { useEffect, useState } from 'react'; -import { useAuthStore } from './authStore'; - -export const AuthPageContent = () => { - // Access the accessToken from Zustand state - const { accessToken } = useAuthStore(); - - // State to store the data fetched from the authenticated endpoint - const [contentData, setContentData] = useState(null); - - // useEffect to run the fetch operation when the component mounts or accessToken changes - useEffect(() => { - // Define an asynchronous function to fetch authenticated content - const fetchAuthenticatedContent = async () => { - try { - // Make a GET request to the authenticated endpoint - const response = await fetch('http://localhost:8081/secrets', { - headers: { - Authorization: accessToken, - }, - }); - - // Check if the response is successful - if (response.ok) { - // Parse the JSON response - const data = await response.json(); - // Update the state with the fetched data - setContentData(data); - } else { - // Handle errors if the response is not successful - console.error('Error fetching authenticated content'); - } - } catch (error) { - // Handle network or other errors - console.error('Error:', error.message); - } - }; - - // Check if there is a valid accessToken before making the request - if (accessToken) { - // Call the fetchAuthenticatedContent function - fetchAuthenticatedContent(); - } - }, [accessToken]); // useEffect will re-run if accessToken changes - - return ( -
-

Authenticated Content Page

- {contentData === null ? ( - // Loading state -

Loading...

- ) : contentData ? ( - // If contentData is available, display the page content -

{contentData.secret}

- ) : ( - // If contentData is not available, display an error message -

Content not found

- )} -
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index 1fa5a4869..97f592e83 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -1,12 +1,64 @@ import { create } from 'zustand'; -export const useAuthStore = create((set) => ({ +// Authentication +const useAuthStore = create((set) => ({ + token: localStorage.getItem('token') || null, user: null, - isAuthenticated: false, - accessToken: null, - setUser: (user, accessToken) => - set({ user, accessToken, isAuthenticated: true }), - logout: () => set({ user: null, accessToken: null, isAuthenticated: false }), -})); + error: null, + + setToken: (token) => { + localStorage.setItem('token', token); + set({ token }); + }, + + clearToken: () => { + localStorage.removeItem('token'); + set({ token: null, user: null }); + }, + // User Login + login: async (username, password) => { + try { + const response = await fetch('http://localhost:3000/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + const data = await response.json(); + if (!response.ok) { + throw new Error(data.response || 'Login failed'); + } + set({ user: data.response, token: data.response.token, error: null }); + localStorage.setItem('token', data.response.token); + } catch (error) { + set({ error: error.message }); + } + }, + + // User registration + register: async (username, password, email) => { + try { + const response = await fetch('http://localhost:3000/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password, email }) + }); + const data = await response.json(); + if (!response.ok) { + throw new Error(data.response || 'Registration failed'); + } + set({ user: data.response, token: data.response.token, error: null }); + localStorage.setItem('token', data.response.token); + } catch (error) { + set({ error: error.message }); + } + }, + + // User Logout + logout: () => { + set({ user: null, token: null, error: null }); + localStorage.removeItem('token'); + } +})); +export default useAuthStore; diff --git a/frontend/src/components/homePage.jsx b/frontend/src/components/homePage.jsx new file mode 100644 index 000000000..5317c982b --- /dev/null +++ b/frontend/src/components/homePage.jsx @@ -0,0 +1,66 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import useAuthStore from './authStore'; +import { Link } from 'react-router-dom'; + +export const HomePage = () => { + const navigate = useNavigate(); + const { login, error } = useAuthStore(); + const [credentials, setCredentials] = useState({ username: '', password: '' }); + const [localError, setLocalError] = useState(''); + + const handleChange = (e) => { + setCredentials({ ...credentials, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + + // Validation for empty fields + if (!credentials.username || !credentials.password) { + setLocalError('Please fill in all fields'); + return; + } + + try { + await login(credentials.username, credentials.password); + if (!error) { + navigate('/userpage'); + } else { + // Handle errors from the authStore (server-side) + setLocalError(error); + } + } catch (err) { + // Catch any unexpected errors and display a general message + setLocalError('An unexpected error occurred. Please try again.'); + } + }; + + return ( +
+

Welcome to My App

+

This is the home page of the app.

+
+ + + +
+ {localError &&

{localError}

} +
+ Register {/* Link to the registration page */} +
+ ); +}; diff --git a/frontend/src/components/loginForm.jsx b/frontend/src/components/loginForm.jsx deleted file mode 100644 index a922e0d1c..000000000 --- a/frontend/src/components/loginForm.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useHistory } from 'react-router-dom'; -import { useState } from 'react'; -import { useAuthStore } from './authStore'; - -export const LoginForm = () => { - const history = useHistory(); // Initialize useHistory - - const { setUser } = useAuthStore(); - - // State variables for form inputs and errors - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [errors, setErrors] = useState({}); - - // Function to validate form fields - const validateForm = () => { - const errors = {}; - // Check if any field is empty - if (!email.trim() || !password.trim()) { - errors.general = 'All fields are required'; - } - setErrors(errors); // Update errors state - return Object.keys(errors).length === 0; // Return true if no errors - }; - - // Function to handle form submission - const handleSubmit = async (e) => { - e.preventDefault(); - - if (validateForm()) { - try { - // Make a POST request for user login - const response = await fetch('http://localhost:8081/sessions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email, - password, - }), - }); - // Check if the login was successful - if (!response.ok) { - throw new Error('Login failed. Please try again.'); - } - // Parse the JSON response - const data = await response.json(); - - //NB DELETE LATER // - console.log('API Response:', data); - - const { userId, accessToken, name, email } = data; - - setUser({ id: userId, name, email }, accessToken); - console.log('Login successful:', data); - // Handle successful login (update global state, redirect, etc.) - - history.push('/secrets'); // // Redirect to /secrets after successful login) - - } catch (error) { - console.error('Error during login:', error.message); - setErrors({ general: 'Login failed. Please try again.' }); - - // Handle login error (display error message, etc.) - } - } else { - console.log('Form validation failed'); - } - }; - - return ( -
-

Login Form

-
- -
- -
- {/* Display a general error message */} - {errors.general &&

{errors.general}

} - -
-
- ); -}; diff --git a/frontend/src/components/logoutButton.jsx b/frontend/src/components/logoutButton.jsx deleted file mode 100644 index 49984c50a..000000000 --- a/frontend/src/components/logoutButton.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useHistory } from 'react-router-dom'; -import { useAuthStore } from './authStore'; - -export const LogoutButton = () => { - const history = useHistory(); // Initialize useHistory - - const logout = useAuthStore((state) => state.logout); - - - const handleLogout = () => { - logout(); - // Redirect to login form after signing out - history.push('/login'); - }; - - return ( - - ); -}; diff --git a/frontend/src/components/mainWrapper.jsx b/frontend/src/components/mainWrapper.jsx deleted file mode 100644 index a7e4670de..000000000 --- a/frontend/src/components/mainWrapper.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { RegistrationForm } from './registrationForm'; -import { LoginForm } from './loginForm'; -import { LogoutButton } from './logoutButton'; - -export const MainWrapper = () => { - return ( -
-

My App

- - - -
- ); -}; diff --git a/frontend/src/components/registerPage.jsx b/frontend/src/components/registerPage.jsx new file mode 100644 index 000000000..b41f11d14 --- /dev/null +++ b/frontend/src/components/registerPage.jsx @@ -0,0 +1,58 @@ +import { useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import useAuthStore from './authStore'; + + +// User registration +export const RegisterPage = () => { + const navigate = useNavigate(); + const { register, error } = useAuthStore(); + const [formData, setFormData] = useState({ username: '', email: '', password: '' }); + const [registrationSuccess, setRegistrationSuccess] = useState(false); + const [localError, setLocalError] = useState(''); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + + // Simple client-side validation + if (!formData.username || !formData.email || !formData.password) { + setLocalError('Please fill in all fields'); + return; + } + + try { + await register(formData.username, formData.password, formData.email); + if (!error) { + setRegistrationSuccess(true); + setTimeout(() => navigate('/'), 3000); // Redirect to login after 3 seconds + } else { + setLocalError(error); + } + } catch (err) { + setLocalError('An unexpected error occurred. Please try again.'); + } + }; + + return ( +
+

Register

+ {!registrationSuccess ? ( +
+ + + + + {localError &&

{localError}

} +
+ ) : ( +

Registration successful! Redirecting to login...

+ )} +
+ Back to Home +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/components/registrationForm.jsx b/frontend/src/components/registrationForm.jsx deleted file mode 100644 index 80bf89cae..000000000 --- a/frontend/src/components/registrationForm.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import { useAuthStore } from './authStore'; - -export const RegistrationForm = () => { - - const history = useHistory(); // Initialize useHistory - - // State variables for form inputs and errors - const [name, setName] = useState(''); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [errors, setErrors] = useState({}); - - // Function to validate form fields - const validateForm = () => { - const errors = {}; - // Check if any field is empty - if (!name.trim() || !email.trim() || !password.trim()) { - errors.general = 'All fields are required'; - } - setErrors(errors); // Update errors state - return Object.keys(errors).length === 0;// Return true if no errors - }; - - // Function to handle form submission - const handleSubmit = async (e) => { - e.preventDefault(); - - if (validateForm()) { - try { - // Make a POST request for user registration - const response = await fetch('http://localhost:8081/users', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name, - email, - password, - }), - }); - // Check if the registration was successful - if (!response.ok) { - throw new Error('Registration failed. Please try again.'); - } - // Parse the JSON response - const data = await response.json(); - // Update user and authentication state on successful registration - useAuthStore.setState({ user: data.user, accessToken: data.accessToken, isAuthenticated: true }); - console.log('Form submitted successfully:', data); - // Handle successful registration - - history.push('/login'); - // Redirect to /login after successful registration - - } catch (error) { - console.error('Error submitting form:', error.message); - setErrors({ general: 'Registration failed. Please try again.' }); - // Handle registration error (display error message, etc.) - } - } else { - console.log('Form validation failed'); - } - }; - - - return ( -
-

Registration Form

-
- -
- -
- -
- {/* Display a general error message */} - {errors.general &&

{errors.general}

} - -
-
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/userPage.jsx b/frontend/src/components/userPage.jsx new file mode 100644 index 000000000..71f07067b --- /dev/null +++ b/frontend/src/components/userPage.jsx @@ -0,0 +1,26 @@ +import { useNavigate } from 'react-router-dom'; +import useAuthStore from './authStore'; // Path to your authStore + +export const UserPage = () => { + const { user, clearToken } = useAuthStore(); + const navigate = useNavigate(); + + const handleLogout = () => { + clearToken(); // Clear the token and user info + navigate('/'); // Navigate back to the home page + }; + + return ( +
+

User Page

+ {user && ( +
+

Welcome, {user.username}!

+

This is a page that only registered users can see!

+ +
+ )} +
+ ); +}; + diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 72066602b..2f3dc3866 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,14 +1,31 @@ +//import React from "react"; +//import ReactDOM from "react-dom/client"; +//import { App } from "./App.jsx"; +//import "./index.css"; +//import { Provider } from 'zustand'; +//import { useAuthStore } from './components/authStore'; + +//ReactDOM.createRoot(document.getElementById('root')).render( +// +// +// +// +// +//); + import React from "react"; import ReactDOM from "react-dom/client"; +import { BrowserRouter as Router } from 'react-router-dom'; import { App } from "./App.jsx"; import "./index.css"; -import { Provider } from 'zustand'; -import { useAuthStore } from './components/authStore'; + +// No need to import Provider from 'zustand' ReactDOM.createRoot(document.getElementById('root')).render( - + - + ); + diff --git a/frontend/src/routes/protectRoute.jsx b/frontend/src/routes/protectRoute.jsx new file mode 100644 index 000000000..a7f53d68e --- /dev/null +++ b/frontend/src/routes/protectRoute.jsx @@ -0,0 +1,15 @@ +import { Navigate } from 'react-router-dom'; +import useAuthStore from '../components/authStore'; + +export const ProtectedRoute = ({ children }) => { + const { token } = useAuthStore(); + + // Check if there's a token, if not, redirect to the login page + if (!token) { + return ; + } + + // If there is a token, render the children (protected component) + return children; +}; + diff --git a/frontend/src/routes/routes.jsx b/frontend/src/routes/routes.jsx index 732e79a68..a07db4c2b 100644 --- a/frontend/src/routes/routes.jsx +++ b/frontend/src/routes/routes.jsx @@ -1,17 +1,15 @@ -import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; -import { RegistrationForm } from '../components/registrationForm'; -import { LoginForm } from '../components/loginForm'; -import { AuthPageContent } from '../components/authPageContent'; +import { Routes, Route } from "react-router-dom"; +import { HomePage } from '../components/homePage'; +import { RegisterPage } from '../components/registerPage'; +import { UserPage } from '../components/userPage'; +import { ProtectedRoute } from './protectRoute'; - -export const Routes = () => { +export const AppRoutes = () => { return ( - - - - - - - + + } /> + } /> + } /> + ); }; From ae6a365d357b6aca1dd09f69cfc6b68af06a6480 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:10:34 +0100 Subject: [PATCH 12/32] styling --- frontend/index.html | 25 ++++---- frontend/src/components/homePage.jsx | 13 ++-- frontend/src/components/homePage.module.css | 60 +++++++++++++++++++ frontend/src/components/registerPage.jsx | 9 +-- .../src/components/registerPage.module.css | 60 +++++++++++++++++++ frontend/src/components/userPage.jsx | 10 ++-- frontend/src/components/userPage.module.css | 34 +++++++++++ frontend/src/main.jsx | 17 ------ 8 files changed, 186 insertions(+), 42 deletions(-) create mode 100644 frontend/src/components/homePage.module.css create mode 100644 frontend/src/components/registerPage.module.css create mode 100644 frontend/src/components/userPage.module.css diff --git a/frontend/index.html b/frontend/index.html index 0c589eccd..f68c2e0f8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,16 @@ - - - - - Vite + React - - -
- - - + + + + + + ME auth project + + + +
+ + + + \ No newline at end of file diff --git a/frontend/src/components/homePage.jsx b/frontend/src/components/homePage.jsx index 5317c982b..c7fa378d9 100644 --- a/frontend/src/components/homePage.jsx +++ b/frontend/src/components/homePage.jsx @@ -1,7 +1,8 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import useAuthStore from './authStore'; import { Link } from 'react-router-dom'; +import useAuthStore from './authStore'; +import styles from './homePage.module.css' export const HomePage = () => { const navigate = useNavigate(); @@ -37,10 +38,10 @@ export const HomePage = () => { }; return ( -
-

Welcome to My App

-

This is the home page of the app.

-
+
+

Welcome to My App

+

Please sign in!

+ { {localError &&

{localError}

}
- Register {/* Link to the registration page */} + Register {/* Link to the registration page */}
); }; diff --git a/frontend/src/components/homePage.module.css b/frontend/src/components/homePage.module.css new file mode 100644 index 000000000..d4d6e1fbd --- /dev/null +++ b/frontend/src/components/homePage.module.css @@ -0,0 +1,60 @@ +.wrapper { + max-width: 400px; + margin: auto; + padding: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 8px; + background-color: #f5f1f1; +} + +.h1 { + text-align: center; + color: #333; + margin-bottom: 20px; +} + +.form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.form input { + padding: 10px; + border: 1px solid #490909; + border-radius: 4px; + font-size: 16px; +} + +.form button { + padding: 10px; + border: none; + border-radius: 4px; + background-color: #490909; + color: white; + cursor: pointer; + transition: background-color 0.3s; +} + +.form button:hover { + background-color: #904646; +} + +.link { + display: inline-block; + margin-top: 20px; + padding: 10px 20px; + background-color: #4d4e4d; + color: white; + text-align: center; + text-decoration: none; + font-weight: bold; + border-radius: 4px; + transition: background-color 0.3s, color 0.3s; + + &:hover, + &:focus { + background-color: #787878; + color: #fff; + } +} \ No newline at end of file diff --git a/frontend/src/components/registerPage.jsx b/frontend/src/components/registerPage.jsx index b41f11d14..717b2dd4a 100644 --- a/frontend/src/components/registerPage.jsx +++ b/frontend/src/components/registerPage.jsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import useAuthStore from './authStore'; +import styles from './registerPage.module.css' // User registration @@ -38,10 +39,10 @@ export const RegisterPage = () => { }; return ( -
-

Register

+
+

Register

{!registrationSuccess ? ( -
+ @@ -52,7 +53,7 @@ export const RegisterPage = () => {

Registration successful! Redirecting to login...

)}
- Back to Home + Back to Home
); }; \ No newline at end of file diff --git a/frontend/src/components/registerPage.module.css b/frontend/src/components/registerPage.module.css new file mode 100644 index 000000000..ce0b84b8a --- /dev/null +++ b/frontend/src/components/registerPage.module.css @@ -0,0 +1,60 @@ +.wrapper { + max-width: 400px; + margin: auto; + padding: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 8px; + background-color: #f5f1f1; +} + +.h1 { + text-align: center; + color: #2e2a2a; + margin-bottom: 20px; +} + +.form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.form input { + padding: 10px; + border: 1px solid #490909; + border-radius: 4px; + font-size: 16px; +} + +.form button { + padding: 10px; + border: none; + border-radius: 4px; + background-color: #490909; + color: white; + cursor: pointer; + transition: background-color 0.3s; +} + +.form button:hover { + background-color: #904646; +} + +.link { + display: inline-block; + margin-top: 20px; + padding: 10px 20px; + background-color: #4d4e4d; + color: white; + text-align: center; + text-decoration: none; + font-weight: bold; + border-radius: 4px; + transition: background-color 0.3s, color 0.3s; + + &:hover, + &:focus { + background-color: #787878; + color: #fff; + } +} \ No newline at end of file diff --git a/frontend/src/components/userPage.jsx b/frontend/src/components/userPage.jsx index 71f07067b..870985a70 100644 --- a/frontend/src/components/userPage.jsx +++ b/frontend/src/components/userPage.jsx @@ -1,5 +1,7 @@ import { useNavigate } from 'react-router-dom'; -import useAuthStore from './authStore'; // Path to your authStore +import useAuthStore from './authStore'; +import styles from './userPage.module.css' + export const UserPage = () => { const { user, clearToken } = useAuthStore(); @@ -11,13 +13,13 @@ export const UserPage = () => { }; return ( -
-

User Page

+
+

User Page

{user && (

Welcome, {user.username}!

This is a page that only registered users can see!

- +
)}
diff --git a/frontend/src/components/userPage.module.css b/frontend/src/components/userPage.module.css new file mode 100644 index 000000000..6fb6ceef5 --- /dev/null +++ b/frontend/src/components/userPage.module.css @@ -0,0 +1,34 @@ +.wrapper { + max-width: 400px; + margin: auto; + padding: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 8px; + background-color: #f5f1f1; + text-align: center; +} + +.h1 { + text-align: center; + color: #333; + margin-bottom: 20px; +} + +.link { + display: inline-block; + margin-top: 20px; + padding: 10px 20px; + background-color: #4d4e4d; + color: white; + text-align: center; + text-decoration: none; + font-weight: bold; + border-radius: 4px; + transition: background-color 0.3s, color 0.3s; + + &:hover, + &:focus { + background-color: #787878; + color: #fff; + } +} \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 2f3dc3866..1de1c85ea 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,26 +1,9 @@ -//import React from "react"; -//import ReactDOM from "react-dom/client"; -//import { App } from "./App.jsx"; -//import "./index.css"; -//import { Provider } from 'zustand'; -//import { useAuthStore } from './components/authStore'; - -//ReactDOM.createRoot(document.getElementById('root')).render( -// -// -// -// -// -//); - import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter as Router } from 'react-router-dom'; import { App } from "./App.jsx"; import "./index.css"; -// No need to import Provider from 'zustand' - ReactDOM.createRoot(document.getElementById('root')).render( From 08657466cfa2f6a6f8ee0a8c629bd1216777f19d Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:58:35 +0100 Subject: [PATCH 13/32] database --- backend/db.js | 21 --------------------- package.json | 1 + 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/backend/db.js b/backend/db.js index 54049cb20..659a5c1d6 100644 --- a/backend/db.js +++ b/backend/db.js @@ -33,27 +33,6 @@ export const connectDB = async () => { // Attempting to connect to MongoDB using the provided URL and options const conn = await mongoose.connect(process.env.MONGO_URL, mongooseOptions); // Logging a success message with the connected database host - console.log(`Local MongoDB Connected: ${conn.connection.host}`); - } catch (error) { - // Logging the error if the connection fails - console.error("Local MongoDB connection error:", error); - // Exiting the application in case of connection failure - process.exit(1); - } -}; - -// CONNECT TO ATLAS DB -export const connectAtlasDB = async () => { - // Check if the ATLAS_MONGO_URL environment variable is set - if (!process.env.ATLAS_MONGO_URL) { - console.error("ATLAS_MONGO_URL is not set in .env"); - process.exit(1); - } - - try { - // Attempting to connect to MongoDB Atlas using the provided URL and options - const conn = await mongoose.connect(process.env.ATLAS_MONGO_URL, mongooseOptions); - // Logging a success message with the connected database host console.log(`MongoDB Atlas Connected: ${conn.connection.host}`); } catch (error) { // Logging the error if the connection fails diff --git a/package.json b/package.json index 82ee362cc..239ae0e11 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dependencies": { "bcrypt": "^5.1.1", "bcrypt-nodejs": "^0.0.3", + "mongodb": "^6.3.0", "zustand": "^4.4.7" } } From e1ceaf4f2945be03f0c0b88653a83e1ce56e64cc Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:16:55 +0100 Subject: [PATCH 14/32] dependencies --- backend/.gitignore | 7 ++++++- backend/package.json | 8 +++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/.gitignore b/backend/.gitignore index 8f5e467c8..670e318b9 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,8 @@ node_modules package-lock.json -.env \ No newline at end of file +.env +.env +.env.local +.env.development.local +.env.test.local +.env.production.local diff --git a/backend/package.json b/backend/package.json index 556d6bf04..3ed4cbf91 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,14 +4,16 @@ "description": "Starter project to get up and running with express quickly", "scripts": { "start": "babel-node server.js", - "dev": "nodemon server.js --exec babel-node" + "dev": "nodemon server.js --exec babel-node", + "build": "babel src --out-dir dist" }, "author": "", "license": "ISC", "dependencies": { "@babel/core": "^7.17.9", - "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "@babel/cli": "^7.23.4", + "@babel/node": "^7.22.19", "bcrypt": "^5.1.1", "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", @@ -25,4 +27,4 @@ "nodemon": "^3.0.1", "validator": "^13.11.0" } -} +} \ No newline at end of file From 46e7d4020f7f305ef01149425603ef38de997d00 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:32:50 +0100 Subject: [PATCH 15/32] packagejson - still deploy errors --- backend/package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/package.json b/backend/package.json index 3ed4cbf91..9d64985b8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,8 +4,10 @@ "description": "Starter project to get up and running with express quickly", "scripts": { "start": "babel-node server.js", - "dev": "nodemon server.js --exec babel-node", - "build": "babel src --out-dir dist" + "dev": "nodemon server.js --exec babel-node" + }, + "engines": { + "node": "18.18.0" }, "author": "", "license": "ISC", @@ -23,7 +25,6 @@ "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.0", - "node.js": "^0.0.1-security", "nodemon": "^3.0.1", "validator": "^13.11.0" } From 014f17c1ef62453104f4807937848d53decc396a Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:46:34 +0100 Subject: [PATCH 16/32] deploy test --- backend/package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/package.json b/backend/package.json index 9d64985b8..cbaa71312 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,10 +12,10 @@ "author": "", "license": "ISC", "dependencies": { - "@babel/core": "^7.17.9", - "@babel/preset-env": "^7.16.11", "@babel/cli": "^7.23.4", + "@babel/core": "^7.17.9", "@babel/node": "^7.22.19", + "@babel/preset-env": "^7.16.11", "bcrypt": "^5.1.1", "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", @@ -24,8 +24,9 @@ "express": "^4.17.3", "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", + "mongodb": "^6.3.0", "mongoose": "^8.0.0", "nodemon": "^3.0.1", "validator": "^13.11.0" } -} \ No newline at end of file +} From aef093aa1811646e9a80f1279f97655c1c5bae1c Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:13:07 +0100 Subject: [PATCH 17/32] backend server change --- backend/package.json | 2 +- backend/server.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/package.json b/backend/package.json index cbaa71312..5fbdb3e1a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,4 +29,4 @@ "nodemon": "^3.0.1", "validator": "^13.11.0" } -} +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 5bbe63fa0..690fcd9c3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,13 +2,12 @@ import express from "express"; import cors from "cors"; import dotenv from "dotenv"; +dotenv.config(); // Load and parse environment variables from the .env file import userRoutes from "./userLogin/routes"; // Import database connection functions import { connectDB } from "./db"; -dotenv.config(); // Load and parse environment variables from the .env file - // Retrieve the port number from environment variables or set default const port = process.env.PORT || 3000; From cc52f18960a890c3ca8e29b40681479e34804477 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:16:21 +0100 Subject: [PATCH 18/32] frontend render --- frontend/.gitignore | 10 +++++++++- frontend/package.json | 1 + frontend/src/components/authStore.jsx | 9 +++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/.gitignore b/frontend/.gitignore index 265f50c92..441ed5559 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -23,4 +23,12 @@ dist-ssr *.sln *.sw? -package-lock.json \ No newline at end of file +package-lock.json + +#env +.env +.env +.env.local +.env.development.local +.env.test.local +.env.production.local \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index faeb930be..e3958ede7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "dotenv": "^16.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1", diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index 97f592e83..5b3c9b24b 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -1,4 +1,9 @@ import { create } from 'zustand'; +import dotenv from "dotenv"; +dotenv.config(); + +const API_URL = process.env.REACT_APP_BACKEND_URL || 'http://localhost:3000'; + // Authentication const useAuthStore = create((set) => ({ @@ -19,7 +24,7 @@ const useAuthStore = create((set) => ({ // User Login login: async (username, password) => { try { - const response = await fetch('http://localhost:3000/login', { + const response = await fetch(`${API_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) @@ -38,7 +43,7 @@ const useAuthStore = create((set) => ({ // User registration register: async (username, password, email) => { try { - const response = await fetch('http://localhost:3000/register', { + const response = await fetch(`${API_URL}/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, email }) From ea0ab65b43c6492fa0d60e877720e31f0ef0d0e1 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:23:28 +0100 Subject: [PATCH 19/32] frontend deploy --- frontend/src/components/authStore.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index 5b3c9b24b..5bcaed45e 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -4,7 +4,6 @@ dotenv.config(); const API_URL = process.env.REACT_APP_BACKEND_URL || 'http://localhost:3000'; - // Authentication const useAuthStore = create((set) => ({ token: localStorage.getItem('token') || null, From 06598b8eda7c8f45b45ab4bd89dec3b72c7630a7 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:50:36 +0100 Subject: [PATCH 20/32] frontend deploy 2 --- README.md | 3 ++- netlify.toml | 2 +- package.json | 8 +------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index dfa05e177..ecc5d5b37 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,5 @@ Describe how you approached to problem, and what tools and techniques you used t ## 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 on netlify: +Backend on render: https://em-authorization.onrender.com/ diff --git a/netlify.toml b/netlify.toml index 95443a1f3..1dd5c5cf6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -2,5 +2,5 @@ # how it should build the JavaScript assets to deploy from. [build] base = "frontend/" - publish = "build/" + publish = "frontend/build/" command = "npm run build" diff --git a/package.json b/package.json index 239ae0e11..39f6bda2e 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,5 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" - }, - "dependencies": { - "bcrypt": "^5.1.1", - "bcrypt-nodejs": "^0.0.3", - "mongodb": "^6.3.0", - "zustand": "^4.4.7" } -} +} \ No newline at end of file From 34fad8c6e2d22198375ece2070f3e54583ccad2f Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:54:15 +0100 Subject: [PATCH 21/32] frontend deploy 3 --- netlify.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netlify.toml b/netlify.toml index 1dd5c5cf6..d2e45b403 100644 --- a/netlify.toml +++ b/netlify.toml @@ -2,5 +2,5 @@ # how it should build the JavaScript assets to deploy from. [build] base = "frontend/" - publish = "frontend/build/" - command = "npm run build" + publish = "/dist/" + command = "vite build" From 55d0471a7eb98deb311fe0977c42aec70a46bb36 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 03:09:32 +0100 Subject: [PATCH 22/32] changed vite connection --- frontend/src/components/authStore.jsx | 2 +- netlify.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index 5bcaed45e..d07b6a349 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -2,7 +2,7 @@ import { create } from 'zustand'; import dotenv from "dotenv"; dotenv.config(); -const API_URL = process.env.REACT_APP_BACKEND_URL || 'http://localhost:3000'; +const API_URL = import.meta.env.VITE_BACKEND_API || 'http://localhost:3000'; // Authentication const useAuthStore = create((set) => ({ diff --git a/netlify.toml b/netlify.toml index d2e45b403..01cbf77d1 100644 --- a/netlify.toml +++ b/netlify.toml @@ -3,4 +3,4 @@ [build] base = "frontend/" publish = "/dist/" - command = "vite build" + command = "npm run build" From 0b705fb20999f783a5bc57ba7dc26b5c93276c5c Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 03:16:49 +0100 Subject: [PATCH 23/32] deploy links --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ecc5d5b37..0ed51b128 100644 --- a/README.md +++ b/README.md @@ -1,14 +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. +Create a registration and sign in form, leading to a "secret page" that ony users who are logged in can see +Frontend and backend working together ## 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? +There were some troubles along the way, including tokens and secrets. +The main trouble in this project is deploying which we have really struggled with. +Atlas DB worked well, but render and netlify have been tricky. ## View it live -Frontend on netlify: +Frontend on netlify: https://em-authorization.netlify.app/ Backend on render: https://em-authorization.onrender.com/ From 8ad44251f9c7e9f2b7fd277ea3b3ef4660fc968d Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:51:57 +0100 Subject: [PATCH 24/32] Remove dotenv dependency --- frontend/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index e3958ede7..faeb930be 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,6 @@ "preview": "vite preview" }, "dependencies": { - "dotenv": "^16.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1", From 8e84315c1d3c9be04d89f35d8b26e776b48c9b24 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:52:46 +0100 Subject: [PATCH 25/32] new deploy --- frontend/src/components/authStore.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index d07b6a349..caea1daae 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -1,6 +1,5 @@ import { create } from 'zustand'; -import dotenv from "dotenv"; -dotenv.config(); + const API_URL = import.meta.env.VITE_BACKEND_API || 'http://localhost:3000'; From a235fb9bcfcc416726278e94aea4d91bc7410d8d Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:55:26 +0100 Subject: [PATCH 26/32] syntax fix --- frontend/src/components/authStore.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index caea1daae..fe6260a44 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -1,6 +1,6 @@ import { create } from 'zustand'; - +//Access .env url const API_URL = import.meta.env.VITE_BACKEND_API || 'http://localhost:3000'; // Authentication From 8ae3ef0b125d9dafea70daaf4474ac79c4c9a0a6 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 14:30:37 +0100 Subject: [PATCH 27/32] url link change --- frontend/src/components/authStore.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/authStore.jsx b/frontend/src/components/authStore.jsx index fe6260a44..b2beb3deb 100644 --- a/frontend/src/components/authStore.jsx +++ b/frontend/src/components/authStore.jsx @@ -1,6 +1,6 @@ import { create } from 'zustand'; -//Access .env url +//Access .env render url const API_URL = import.meta.env.VITE_BACKEND_API || 'http://localhost:3000'; // Authentication From 529622288b0e8f01330433f80f4c5735ed1a6ade Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Sun, 21 Jan 2024 15:05:35 +0100 Subject: [PATCH 28/32] change to readme --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0ed51b128..c9c2b7070 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ # Project Auth API -Create a registration and sign in form, leading to a "secret page" that ony users who are logged in can see -Frontend and backend working together +Create a registration and sign in form, leading to a "secret page" that ony users who are logged in can see. Frontend and backend working together. ## The problem There were some troubles along the way, including tokens and secrets. The main trouble in this project is deploying which we have really struggled with. -Atlas DB worked well, but render and netlify have been tricky. +Atlas DB worked well, but render and netlify have been tricky. The frontend deploy seemed to be related to the connection to the backend deploy, including env variables. ## View it live From 01548cd6b03b4f2f5fa230cf2832d5ae9b73fdcb Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:40:25 +0200 Subject: [PATCH 29/32] server update --- backend/db.js | 8 +------- package.json | 5 ++++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/db.js b/backend/db.js index 659a5c1d6..76a01b7dd 100644 --- a/backend/db.js +++ b/backend/db.js @@ -8,12 +8,6 @@ import dotenv from "dotenv"; // Execute the config function to load variables from .env file into process.env dotenv.config(); -// Configuration options for Mongoose (with deprecated options removed) -const mongooseOptions = { - useNewUrlParser: true, // Use the new URL string parser - useUnifiedTopology: true // Use the new Server Discover and Monitoring engine -}; - /** * Asynchronous function to connect to MongoDB. * This function tries to establish a connection with the MongoDB server @@ -31,7 +25,7 @@ export const connectDB = async () => { try { // Attempting to connect to MongoDB using the provided URL and options - const conn = await mongoose.connect(process.env.MONGO_URL, mongooseOptions); + const conn = await mongoose.connect(process.env.MONGO_URL); // Logging a success message with the connected database host console.log(`MongoDB Atlas Connected: ${conn.connection.host}`); } catch (error) { diff --git a/package.json b/package.json index 39f6bda2e..4465fb3c4 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,8 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "dependencies": { + "mongodb": "^6.8.1" } -} \ No newline at end of file +} From c6d887bc303485e8d19de8fb0c5bda838c95d64c Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:07:54 +0200 Subject: [PATCH 30/32] env --- backend/userLogin/controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/userLogin/controller.js b/backend/userLogin/controller.js index 04c7eff46..c2aab2057 100644 --- a/backend/userLogin/controller.js +++ b/backend/userLogin/controller.js @@ -5,7 +5,7 @@ import jwt from "jsonwebtoken"; const generateToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { - expiresIn: '24h' // Token expires in 24 hours + expiresIn: '24h' // Make sure token expires in 24 hours }); }; @@ -19,7 +19,7 @@ const isValidPassword = (password) => { // FUNCTION FOR USER REGISTRATION export const registerUserController = asyncHandler(async (req, res) => { - // Extract email, username and password from the request body + // Extract email, username & password from the request body const { username, password, email } = req.body; try { From fa0a07e3a4036c0234c72555596ad43c49293b89 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:21:56 +0200 Subject: [PATCH 31/32] env edit --- backend/db.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/db.js b/backend/db.js index 76a01b7dd..fe672d7b6 100644 --- a/backend/db.js +++ b/backend/db.js @@ -3,7 +3,7 @@ // Importing the Mongoose library for MongoDB interactions import mongoose from "mongoose"; -// Importing the dotenv library to manage environment variables +// Importing the dotenv library to manage environment (env) variables import dotenv from "dotenv"; // Execute the config function to load variables from .env file into process.env dotenv.config(); From 544973570d65196a02a1858f8adc5a082a050b26 Mon Sep 17 00:00:00 2001 From: mirelcac <141237662+mirelcac@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:36:11 +0200 Subject: [PATCH 32/32] readme --- README.md | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c9c2b7070..2bea47405 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,45 @@ # Project Auth API -Create a registration and sign in form, leading to a "secret page" that ony users who are logged in can see. Frontend and backend working together. +# Overview -## The problem +This project is a full-stack authentication system featuring user registration, login, and access to a protected "secret page" for authenticated users. It integrates both frontend and backend components to deliver a seamless user experience. -There were some troubles along the way, including tokens and secrets. -The main trouble in this project is deploying which we have really struggled with. -Atlas DB worked well, but render and netlify have been tricky. The frontend deploy seemed to be related to the connection to the backend deploy, including env variables. +# Features + +- User Registration: Allows new users to create an account. +- User Authentication: Enables users to log in with their credentials. +- Secret Page: Restricted to authenticated users only. + +# Technologies + +# Frontend +- React: A JavaScript library for building user interfaces. (React) +- React Router DOM: For routing and navigation in a React application. (React Router) +- Zustand: A small, fast state management solution for React. (Zustand) +- Vite: A build tool that provides a fast development environment. (Vite) +- ESLint: A tool for identifying and fixing problems in JavaScript code. (ESLint) +- Deployed with Netlify + +# Backend +- Express: A web application framework for Node.js. (Express) +- Node.js: A JavaScript runtime built on Chrome's V8 engine. (Node.js) +- MongoDB Atlas: Cloud database service for MongoDB. (MongoDB Atlas) +- Mongoose: An ODM (Object Data Modeling) library for MongoDB and Node.js. (Mongoose) +- JSON Web Tokens (JWT): For handling authentication and authorization. (JWT) +- Environment Variables: Ensure that environment variables for database connection and other secrets are correctly configured in both the backend and frontend deployment platforms. +- Deployed on Render. + +# Challenges + +- Deployment Issues: Encountered difficulties with deploying both the frontend and backend. Initial challenges included configuring environment variables and ensuring correct integration between frontend and backend. +- Token Management: Faced issues with handling and validating tokens, which required adjustments to ensure secure user authentication. + +# Key Takeaways + +- Integration of Frontend and Backend: Gained experience in connecting a React frontend with an Express backend, including handling authentication and routing. +- Deployment Process: Learned about the deployment process using Netlify and Render, including managing environment variables and troubleshooting deployment issues. +- State Management: Improved understanding of state management with Zustand and how to efficiently manage application state in a React application. +- Database Configuration: Gained practical experience in setting up and configuring MongoDB Atlas with Mongoose for a production-ready database solution. ## View it live