From fba4b2c8b9e0bd0699e67c59678f9e5be9cc75f6 Mon Sep 17 00:00:00 2001 From: Javier Godoy Date: Sat, 1 Jun 2024 15:29:17 +0200 Subject: [PATCH 1/2] solved --- app.js | 3 + config/index.js | 30 +++++++- middleware/isLoggedIn.js | 8 ++ middleware/isLoggedOut.js | 9 +++ models/User.model.js | 32 ++++++-- package.json | 16 ++-- routes/auth.routes.js | 156 ++++++++++++++++++++++++++++++++++++++ routes/index.js | 3 +- views/auth/login.hbs | 24 ++++++ views/auth/logout.hbs | 4 + views/auth/singup.hbs | 24 ++++++ views/layout.hbs | 1 - views/partials/navbar.hbs | 1 + 13 files changed, 294 insertions(+), 17 deletions(-) create mode 100644 middleware/isLoggedIn.js create mode 100644 middleware/isLoggedOut.js create mode 100644 routes/auth.routes.js create mode 100644 views/auth/login.hbs create mode 100644 views/auth/logout.hbs create mode 100644 views/auth/singup.hbs create mode 100644 views/partials/navbar.hbs diff --git a/app.js b/app.js index 2ecc9f2220..3d84cecb6d 100644 --- a/app.js +++ b/app.js @@ -28,6 +28,9 @@ app.locals.title = `${capitalized(projectName)}- Generated with Ironlauncher`; const index = require('./routes/index'); app.use('/', index); +const authRoutes = require(`./routes/auth.routes`); +app.use(`/auth`, authRoutes); + // ❗ To handle errors. Routes that don't exist or errors that you handle in specific routes require('./error-handling')(app); diff --git a/config/index.js b/config/index.js index 4d9ff5c193..02facee0e7 100644 --- a/config/index.js +++ b/config/index.js @@ -17,6 +17,18 @@ const favicon = require("serve-favicon"); // https://www.npmjs.com/package/path const path = require("path"); +// ℹ️ Session middleware for authentication +// https://www.npmjs.com/package/express-session +const session = require("express-session"); + +// ℹ️ MongoStore in order to save the user session in the database +// https://www.npmjs.com/package/connect-mongo +const MongoStore = require("connect-mongo"); + +// Connects the mongo uri to maintain the same naming structure +const MONGO_URI = + process.env.MONGODB_URI || "mongodb://127.0.0.1:27017/New-project"; + // Middleware configuration module.exports = (app) => { // In development environment the app logs @@ -31,9 +43,23 @@ module.exports = (app) => { app.set("views", path.join(__dirname, "..", "views")); // Sets the view engine to handlebars app.set("view engine", "hbs"); - // Handles access to the public folder + // AHandles access to the public folder app.use(express.static(path.join(__dirname, "..", "public"))); // Handles access to the favicon - app.use(favicon(path.join(__dirname, "..", "public", "images", "favicon.ico"))); + app.use( + favicon(path.join(__dirname, "..", "public", "images", "favicon.ico")) + ); + + // ℹ️ Middleware that adds a "req.session" information and later to check that you are who you say you are 😅 + app.use( + session({ + secret: process.env.SESSION_SECRET || "super hyper secret key", + resave: false, + saveUninitialized: false, + store: MongoStore.create({ + mongoUrl: MONGO_URI, + }), + }) + ); }; diff --git a/middleware/isLoggedIn.js b/middleware/isLoggedIn.js new file mode 100644 index 0000000000..3fe7a04bae --- /dev/null +++ b/middleware/isLoggedIn.js @@ -0,0 +1,8 @@ +module.exports = (req, res, next) => { + // checks if the user is logged in when trying to access a specific page + if (!req.session.currentUser) { + return res.redirect("/auth/login"); + } + + next(); + }; \ No newline at end of file diff --git a/middleware/isLoggedOut.js b/middleware/isLoggedOut.js new file mode 100644 index 0000000000..cb1bd444dc --- /dev/null +++ b/middleware/isLoggedOut.js @@ -0,0 +1,9 @@ +module.exports = (req, res, next) => { + // if an already logged in user tries to access the login page it + // redirects the user to the home page + if (req.session.currentUser) { + return res.redirect("/"); + } + next(); + }; + \ No newline at end of file diff --git a/models/User.model.js b/models/User.model.js index 9cdd3a3ce4..0dacd5dade 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -1,13 +1,31 @@ const { Schema, model } = require("mongoose"); -// TODO: Please make sure you edit the user model to whatever makes sense in this case -const userSchema = new Schema({ - username: { - type: String, - unique: true +// TODO: Please make sure you edit the User model to whatever makes sense in this case +const userSchema = new Schema( + { + username: { + type: String, + required: false, + unique: true, + trim: true, + }, + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + password: { + type: String, + required: true, + }, }, - password: String -}); + { + // this second object adds extra properties: `createdAt` and `updatedAt` + timestamps: true, + } +); const User = model("User", userSchema); diff --git a/package.json b/package.json index 19489d9695..3f358f5d2c 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,20 @@ "start": "node server.js", "dev": "nodemon server.js" }, + "dependencies": { - "cookie-parser": "^1.4.5", - "dotenv": "^8.2.0", - "express": "^4.17.1", - "hbs": "^4.1.1", - "mongoose": "^6.1.2", + "bcryptjs": "^2.4.3", + "connect-mongo": "^5.1.0", + "cookie-parser": "^1.4.6", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "express-session": "^1.18.0", + "hbs": "^4.2.0", + "mongoose": "^8.4.0", "morgan": "^1.10.0", "serve-favicon": "^2.5.0" }, "devDependencies": { - "nodemon": "^2.0.7" + "nodemon": "^3.1.2" } } diff --git a/routes/auth.routes.js b/routes/auth.routes.js new file mode 100644 index 0000000000..e6ca30c1d1 --- /dev/null +++ b/routes/auth.routes.js @@ -0,0 +1,156 @@ +const express = require("express"); +const router = express.Router(); + +// ℹ️ Handles password encryption +const bcrypt = require("bcryptjs"); +const mongoose = require("mongoose"); + +// How many rounds should bcrypt run the salt (default - 10 rounds) +const saltRounds = 10; + +// Require the User model in order to interact with the database +const User = require("../models/User.model"); + +// Require necessary (isLoggedOut and isLiggedIn) middleware in order to control access to specific routes +const isLoggedOut = require("../middleware/isLoggedOut"); +const isLoggedIn = require("../middleware/isLoggedIn"); + +// GET /auth/signup +router.get("/signup", isLoggedOut, (req, res) => { + res.render("auth/signup"); +}); + +// POST /auth/signup +router.post("/signup", isLoggedOut, (req, res) => { + const { username, email, password } = req.body; + + // Check that username, email, and password are provided + if (username === "" || email === "" || password === "") { + res.status(400).render("auth/signup", { + errorMessage: + "All fields are mandatory. Please provide your username, email and password.", + }); + + return; + } + + if (password.length < 6) { + res.status(400).render("auth/signup", { + errorMessage: "Your password needs to be at least 6 characters long.", + }); + + return; + } + + // ! This regular expression checks password for special characters and minimum length + /* + const regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/; + if (!regex.test(password)) { + res + .status(400) + .render("auth/signup", { + errorMessage: "Password needs to have at least 6 chars and must contain at least one number, one lowercase and one uppercase letter." + }); + return; + } + */ + + // Create a new user - start by hashing the password + bcrypt + .genSalt(saltRounds) + .then((salt) => bcrypt.hash(password, salt)) + .then((hashedPassword) => { + // Create a user and save it in the database + return User.create({ username, email, password: hashedPassword }); + }) + .then((user) => { + res.redirect("/auth/login"); + }) + .catch((error) => { + if (error instanceof mongoose.Error.ValidationError) { + res.status(500).render("auth/signup", { errorMessage: error.message }); + } else if (error.code === 11000) { + res.status(500).render("auth/signup", { + errorMessage: + "Username and email need to be unique. Provide a valid username or email.", + }); + } else { + next(error); + } + }); +}); + +// GET /auth/login +router.get("/login", isLoggedOut, (req, res) => { + res.render("auth/login"); +}); + +// POST /auth/login +router.post("/login", isLoggedOut, (req, res, next) => { + const { username, email, password } = req.body; + + // Check that username, email, and password are provided + if (username === "" || email === "" || password === "") { + res.status(400).render("auth/login", { + errorMessage: + "All fields are mandatory. Please provide username, email and password.", + }); + + return; + } + + // Here we use the same logic as above + // - either length based parameters or we check the strength of a password + if (password.length < 6) { + return res.status(400).render("auth/login", { + errorMessage: "Your password needs to be at least 6 characters long.", + }); + } + + // Search the database for a user with the email submitted in the form + User.findOne({ email }) + .then((user) => { + // If the user isn't found, send an error message that user provided wrong credentials + if (!user) { + res + .status(400) + .render("auth/login", { errorMessage: "Wrong credentials." }); + return; + } + + // If user is found based on the username, check if the in putted password matches the one saved in the database + bcrypt + .compare(password, user.password) + .then((isSamePassword) => { + if (!isSamePassword) { + res + .status(400) + .render("auth/login", { errorMessage: "Wrong credentials." }); + return; + } + + // Add the user object to the session object + req.session.currentUser = user.toObject(); + // Remove the password field + delete req.session.currentUser.password; + + res.redirect("/"); + }) + .catch((err) => next(err)); // In this case, we send error handling to the error handling middleware. + }) + .catch((err) => next(err)); +}); + +// GET /auth/logout +router.get("/logout", isLoggedIn, (req, res) => { + req.session.destroy((err) => { + if (err) { + res.status(500).render("auth/logout", { errorMessage: err.message }); + return; + } + + res.redirect("/"); + }); +}); + +module.exports = router; diff --git a/routes/index.js b/routes/index.js index 81c2396ceb..f538ffed90 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,4 +1,5 @@ -const router = require("express").Router(); +const express = require('express'); +const router = express.Router(); /* GET home page */ router.get("/", (req, res, next) => { diff --git a/views/auth/login.hbs b/views/auth/login.hbs new file mode 100644 index 0000000000..0d33b08924 --- /dev/null +++ b/views/auth/login.hbs @@ -0,0 +1,24 @@ +
+

Log In

+
+ + + + + + + + + + + {{#if errorMessage}} +

{{errorMessage}}

+ {{/if}} +
+
\ No newline at end of file diff --git a/views/auth/logout.hbs b/views/auth/logout.hbs new file mode 100644 index 0000000000..967ef80507 --- /dev/null +++ b/views/auth/logout.hbs @@ -0,0 +1,4 @@ +{{!-- this file is only shown whenever there is an error during log out --}} +{{#if errorMessage}} + {{errorMessage}} +{{/if}} \ No newline at end of file diff --git a/views/auth/singup.hbs b/views/auth/singup.hbs new file mode 100644 index 0000000000..cbee87acf0 --- /dev/null +++ b/views/auth/singup.hbs @@ -0,0 +1,24 @@ +
+

Sign Up

+
+ + + + + + + + + + + {{#if errorMessage}} +

{{errorMessage}}

+ {{/if}} +
+
\ No newline at end of file diff --git a/views/layout.hbs b/views/layout.hbs index 73199c166b..f6e472aa00 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -10,7 +10,6 @@ - {{{body}}} diff --git a/views/partials/navbar.hbs b/views/partials/navbar.hbs new file mode 100644 index 0000000000..deb05060ba --- /dev/null +++ b/views/partials/navbar.hbs @@ -0,0 +1 @@ +Signup \ No newline at end of file From fd134f6b8c81d4e0a7c8845c6c9fbef121c00d9f Mon Sep 17 00:00:00 2001 From: Javier Godoy Date: Sat, 1 Jun 2024 15:33:14 +0200 Subject: [PATCH 2/2] solved --- models/User.model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/User.model.js b/models/User.model.js index 0dacd5dade..46738dcd3b 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -5,7 +5,7 @@ const userSchema = new Schema( { username: { type: String, - required: false, + required: true, unique: true, trim: true, },