diff --git a/.env b/.env index c0c68b1ca0..fe43d031c2 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -PORT=3000 \ No newline at end of file +PORT=3000 +SESS_SECRET = 'super session secret' \ No newline at end of file diff --git a/app.js b/app.js index 2ecc9f2220..0fdb10dcdf 100644 --- a/app.js +++ b/app.js @@ -17,6 +17,7 @@ const app = express(); // ℹ️ This function is getting exported from the config folder. It runs most middlewares require('./config')(app); +require('./config/session.config')(app); // default value for title local const projectName = 'lab-express-basic-auth'; diff --git a/config/session.config.js b/config/session.config.js new file mode 100644 index 0000000000..7e4c86c49a --- /dev/null +++ b/config/session.config.js @@ -0,0 +1,40 @@ +// require session +const session = require('express-session'); + +// require mongostore +const MongoStore = require('connect-mongo'); + +// require mongoose +const mongoose = require('mongoose'); + +// since we are going to USE this middleware in the app.js, +// let's export it and have it receive a parameter +module.exports = app => { + // <== app is just a placeholder here + // but will become a real "app" in the app.js + // when this file gets imported/required there + + // required for the app when deployed to Heroku (in production) + app.set('trust proxy', 1); + + // use session + app.use( + session({ + secret: process.env.SESS_SECRET, + resave: true, + saveUninitialized: false, + cookie: { + sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', + secure: process.env.NODE_ENV === 'production', + httpOnly: true, + maxAge: 60000 // 60 * 1000 ms === 1 min + }, // storing in db + store: MongoStore.create({ + mongoUrl: process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/basic-auth' + + // ttl => time to live + // ttl: 60 * 60 * 24 // 60sec * 60min * 24h => 1 day + }) + }) + ); +}; diff --git a/middleware/route-guard.js b/middleware/route-guard.js new file mode 100644 index 0000000000..b97a00012c --- /dev/null +++ b/middleware/route-guard.js @@ -0,0 +1,22 @@ +// checks if the user is logged in when trying to access a specific page +const isLoggedIn = (req, res, next) => { + if (!req.session.currentUser) { + return res.redirect('/login'); + } + next(); + }; + + // if an already logged in user tries to access the login page it + // redirects the user to the home page + const isLoggedOut = (req, res, next) => { + if (req.session.currentUser) { + return res.redirect('/'); + } + next(); + }; + + module.exports = { + isLoggedIn, + isLoggedOut + }; + \ No newline at end of file diff --git a/models/User.model.js b/models/User.model.js index 9cdd3a3ce4..8575d3a1b4 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -4,8 +4,21 @@ const { Schema, model } = require("mongoose"); const userSchema = new Schema({ username: { type: String, + trim: true, + required: [true, 'Username is required.'], unique: true }, + email: { + type: String, + required: [true, 'Email is required.'], + unique: true, + lowercase: true, + trim: true + }, + passwordHash: { + type: String, + required: [true, 'Password is required.'] + }, password: String }); diff --git a/package.json b/package.json index 19489d9695..4f918a0152 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", "express": "^4.17.1", + "express-session": "^1.18.0", "hbs": "^4.1.1", "mongoose": "^6.1.2", "morgan": "^1.10.0", diff --git a/public/images/funnycat.png b/public/images/funnycat.png new file mode 100644 index 0000000000..14ff1c4f88 Binary files /dev/null and b/public/images/funnycat.png differ diff --git a/routes/index.js b/routes/index.js index 81c2396ceb..f2fded73e0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,89 @@ const router = require("express").Router(); +const bcryptjs = require("bcryptjs") +const saltRounds = 10 +const User = require('../models/User.model'); + +// require auth middleware +const { isLoggedIn, isLoggedOut } = require('../middleware/route-guard.js'); /* GET home page */ router.get("/", (req, res, next) => { res.render("index"); }); +/* GET signup page */ +router.get('/signup', isLoggedOut, (req, res, next) => { + res.render("auth/signup"); +}); + +/* POST signup page */ +router.post('/signup', isLoggedOut, (req, res, next) => { + const { username, email, password } = req.body; + + bcryptjs + .genSalt(saltRounds) + .then(salt => bcryptjs.hash(password, salt)) + .then(hashedPassword => { + return User.create({ + username: username, + email: email, + passwordHash: hashedPassword + }); + }) + .then(userFromDB => { + res.redirect('/userProfile/' + userFromDB.username); + }) + .catch(error => next(error)); +}); + +router.get('/userProfile', isLoggedIn, (req, res) => { + res.render('users/user-profile', { userInSession: req.session.currentUser }); +}); + +// GET route ==> to display the login form to users +router.get('/login', isLoggedOut, (req, res) => res.render('auth/login')); + +// POST login route ==> to process form data +router.post('/login', isLoggedOut, (req, res, next) => { + console.log('SESSION =====> ', req.session); + const { email, password } = req.body; + + if (email === '' || password === '') { + res.render('auth/login', { + errorMessage: 'Please enter both, email and password to login.' + }); + return; + } + + User.findOne({ email }) + .then(user => { + if (!user) { + console.log("Email not registered. "); + res.render('auth/login', { errorMessage: 'User not found and/or incorrect password.' }); + return; + } else if (bcryptjs.compareSync(password, user.passwordHash)) { + // when we introduce session, the following line gets replaced with what follows: + // res.render('users/user-profile', { user }); + + //******* SAVE THE USER IN THE SESSION ********// + req.session.currentUser = user; + res.redirect('/userProfile'); + } else { + console.log("Incorrect password. "); + res.render('auth/login', { errorMessage: 'User not found and/or incorrect password.' }); + } + }) + .catch(error => next(error)); +}); + +router.post('/logout', isLoggedIn, (req, res, next) => { + req.session.destroy(err => { + if (err) next(err); + res.redirect('/'); + }); +}); + +router.get('/main', isLoggedIn, (req, res) => res.render('main')); +router.get('/private', isLoggedIn, (req, res) => res.render('private')); + module.exports = router; diff --git a/views/auth/login.hbs b/views/auth/login.hbs new file mode 100644 index 0000000000..c7be17e7a0 --- /dev/null +++ b/views/auth/login.hbs @@ -0,0 +1,22 @@ +{{!-- views/auth/login.hbs --}} + +
a funny picture of a cat
+
+Back to homepage
\ No newline at end of file
diff --git a/views/private.hbs b/views/private.hbs
new file mode 100644
index 0000000000..0d507f7930
--- /dev/null
+++ b/views/private.hbs
@@ -0,0 +1,2 @@
+This is your profile page my friend!
+ +{{else}} + +Still no logged in user, sorry!
+ +{{/if}} \ No newline at end of file