diff --git a/.env b/.env index c0c68b1ca0..40a2f65347 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -PORT=3000 \ No newline at end of file +PORT=3000 +SESSION_SECRET=thisIsSoBoringThatIcouldDie \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7f09c232c5..e742f78633 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ npm-debug.log # Environment Variables should NEVER be published # We are submitting it for learning purposes -# .env +.env # for maintenance purposes - not best practices package-lock.json diff --git a/app.js b/app.js index 2ecc9f2220..e2cc65ee49 100644 --- a/app.js +++ b/app.js @@ -1,35 +1,54 @@ +// app.js // ℹ️ Gets access to environment variables/settings // https://www.npmjs.com/package/dotenv -require('dotenv/config'); +require("dotenv").config(); // ℹ️ Connects to the database -require('./db'); +require("./db"); // Handles http requests (express is node js framework) // https://www.npmjs.com/package/express -const express = require('express'); +const express = require("express"); // Handles the handlebars // https://www.npmjs.com/package/hbs -const hbs = require('hbs'); +const hbs = require("hbs"); const app = express(); -// ℹ️ This function is getting exported from the config folder. It runs most middlewares -require('./config')(app); + +// Place session configuration here, right after initializing Express and before other middleware/route setups +const session = require('express-session'); +const MongoStore = require('connect-mongo'); + +app.use( + session({ + secret: process.env.SESSION_SECRET || 'superSecret', // Remember to set SESSION_SECRET in your .env + resave: false, + saveUninitialized: false, + store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost/basic-auth' }) + }) +); + +// ℹ️ This function is getting exported from the config folder. It runs most pieces of middleware +require("./config")(app); // default value for title local -const projectName = 'lab-express-basic-auth'; -const capitalized = string => string[0].toUpperCase() + string.slice(1).toLowerCase(); +const capitalize = require("./utils/capitalize"); +const projectName = "basic-auth"; -app.locals.title = `${capitalized(projectName)}- Generated with Ironlauncher`; +app.locals.appTitle = `${capitalize(projectName)} created with IronLauncher`; // 👇 Start handling routes here -const index = require('./routes/index'); -app.use('/', index); +const indexRoutes = require("./routes/index.routes"); +app.use("/", indexRoutes); + +// authRouter +const authRouter = require('./routes/auth.routes'); +app.use('/', authRouter); // ❗ To handle errors. Routes that don't exist or errors that you handle in specific routes -require('./error-handling')(app); +require("./error-handling")(app); module.exports = app; diff --git a/middleware/isAuthenticated.js b/middleware/isAuthenticated.js new file mode 100644 index 0000000000..2452783efb --- /dev/null +++ b/middleware/isAuthenticated.js @@ -0,0 +1,11 @@ +const isAuthenticated = (req, res, next) => { + if (req.session.currentUser) { + // User is logged in, proceed to the next middleware/route handler + next(); + } else { + // User is not logged in, redirect to the login page + res.redirect('/login'); + } + }; + + module.exports = isAuthenticated; \ No newline at end of file diff --git a/models/User.model.js b/models/User.model.js index 9cdd3a3ce4..d66cff0c72 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -1,14 +1,22 @@ -const { Schema, model } = require("mongoose"); +// models/User.model.js +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 +const userSchema = new Schema( + { + username: { + type: String, + trim: true, + required: [true, 'Username is required.'], + unique: true + }, + passwordHash: { + type: String, + required: [true, 'Password is required.'] + } }, - password: String -}); + { + timestamps: true + } +); -const User = model("User", userSchema); - -module.exports = User; +module.exports = model('User', userSchema); \ No newline at end of file diff --git a/package.json b/package.json index 19489d9695..9367304112 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,12 @@ "dev": "nodemon server.js" }, "dependencies": { + "bcryptjs": "^2.4.3", + "connect-mongo": "^5.1.0", "cookie-parser": "^1.4.5", - "dotenv": "^8.2.0", + "dotenv": "^8.6.0", "express": "^4.17.1", + "express-session": "^1.18.0", "hbs": "^4.1.1", "mongoose": "^6.1.2", "morgan": "^1.10.0", diff --git a/routes/auth.routes.js b/routes/auth.routes.js new file mode 100644 index 0000000000..f1aaa7f3e3 --- /dev/null +++ b/routes/auth.routes.js @@ -0,0 +1,99 @@ +// routes/auth.routes.js + +const { Router } = require("express"); +const router = new Router(); + +const bcryptjs = require("bcryptjs"); +const saltRounds = 10; + +const User = require("../models/User.model"); + +const isAuthenticated = require('../middleware/isAuthenticated'); + +// GET route ==> to display the signup form to users +router.get("/signup", (req, res) => res.render("auth/signup")); + +// POST route ==> to process form data +router.post("/signup", (req, res, next) => { + // console.log("The form data: ", req.body); + + const { username, password } = req.body; + + bcryptjs + .genSalt(saltRounds) + .then((salt) => bcryptjs.hash(password, salt)) + .then((hashedPassword) => { + // console.log(`Password hash: ${hashedPassword}`); + return User.create({ + // username: username + username, + email, + // passwordHash => this is the key from the User model + // ^ + // | |--> this is placeholder (how we named returning value from the previous method (.hash())) + passwordHash: hashedPassword, + }); + }) + .then((userFromDB) => { + // console.log("Newly created user is: ", userFromDB); + res.redirect('/userProfile'); + }) + .catch((error) => next(error)); +}); + + + +// GET route to display the login form +router.get("/login", (req, res) => { + res.render("auth/login"); // Make sure you create this view +}); + +// POST route to process the login form +router.post("/login", (req, res, next) => { + const { username, password } = req.body; + + User.findOne({ username }) + .then(user => { + if (!user) { + // Username not found + res.render("auth/login", { errorMessage: "Username not found." }); + return; + } + // User found, now compare passwords + if (bcryptjs.compareSync(password, user.passwordHash)) { + // Password matches, login successful + req.session.currentUser = user; // Save user info in session + res.redirect("/userProfile"); // Redirect to the user profile or another protected route + } else { + // Password does not match + res.render("auth/login", { errorMessage: "Incorrect password." }); + } + }) + .catch(error => next(error)); +}); + + +router.get('/userProfile', (req, res) => { + if (!req.session.currentUser) { + // If no user is logged in, redirect to the login page + res.redirect('/login'); + } else { + // If a user is logged in, render the user profile page + res.render('users/user-profile', { user: req.session.currentUser }); + } +}); + + +// Use isAuthenticated middleware to protect the /main and /private routes +// Place these AFTER your signup and login routes to keep a logical order + +router.get('/main', isAuthenticated, (req, res) => { + res.render('protected/main'); // Make sure you have a main.hbs view +}); + +router.get('/private', isAuthenticated, (req, res) => { + res.render('protected/private'); // Make sure you have a private.hbs view +}); + + +module.exports = router; diff --git a/routes/index.js b/routes/index.routes.js similarity index 61% rename from routes/index.js rename to routes/index.routes.js index 81c2396ceb..f538ffed90 100644 --- a/routes/index.js +++ b/routes/index.routes.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/utils/capitalize.js b/utils/capitalize.js new file mode 100644 index 0000000000..46b5e45c8e --- /dev/null +++ b/utils/capitalize.js @@ -0,0 +1,5 @@ +function capitalize (string) { + return string[0].toUpperCase() + string.slice(1).toLowerCase(); + } + + module.exports = capitalize; \ No newline at end of file diff --git a/views/auth/login.hbs b/views/auth/login.hbs new file mode 100644 index 0000000000..cdaf6bab4c --- /dev/null +++ b/views/auth/login.hbs @@ -0,0 +1,12 @@ +
This is a protected page. Only logged-in users can see this.
+Back to Home \ No newline at end of file diff --git a/views/protected/private.hbs b/views/protected/private.hbs new file mode 100644 index 0000000000..771419558f --- /dev/null +++ b/views/protected/private.hbs @@ -0,0 +1,4 @@ +Welcome to the private page! It's for your eyes only.
+Back to Home \ No newline at end of file diff --git a/views/users/user-profile.hbs b/views/users/user-profile.hbs new file mode 100644 index 0000000000..5b39285657 --- /dev/null +++ b/views/users/user-profile.hbs @@ -0,0 +1 @@ +This is your profile page my friend!
\ No newline at end of file