diff --git a/.env b/.env index c0c68b1ca0..2077cbde4d 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -PORT=3000 \ No newline at end of file +PORT=3000 +SESS_SECRET=secret \ No newline at end of file diff --git a/app.js b/app.js index 2ecc9f2220..37f6e1a358 100644 --- a/app.js +++ b/app.js @@ -1,35 +1,46 @@ // ℹ️ 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); +require("./config")(app); + +require("./config/session.config")(app); // default value for title local -const projectName = 'lab-express-basic-auth'; -const capitalized = string => string[0].toUpperCase() + string.slice(1).toLowerCase(); +const projectName = "lab-express-basic-auth"; +const capitalized = (string) => + string[0].toUpperCase() + string.slice(1).toLowerCase(); app.locals.title = `${capitalized(projectName)}- Generated with Ironlauncher`; // 👇 Start handling routes here -const index = require('./routes/index'); -app.use('/', index); +const index = require("./routes/index"); +app.use("/", index); + +const authRoutes = require("./routes/auth"); +app.use("/", authRoutes); + +const mainRoutes = require("./routes/main"); +app.use("/", mainRoutes); + +const privateRoutes = require("./routes/private"); +app.use("/", privateRoutes); // ❗ 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/config/session.config.js b/config/session.config.js new file mode 100644 index 0000000000..95adc52b1d --- /dev/null +++ b/config/session.config.js @@ -0,0 +1,34 @@ +// require session +const session = require('express-session'); + +const MongoStore = require('connect-mongo'); + +// 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, + store: MongoStore.create({ + mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost/lab-express-basic-auth', + ttl: 60 * 60 * 24 // 1 day + }), + resave: true, + saveUninitialized: true, + cookie: { + sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', + secure: process.env.NODE_ENV === 'production', + httpOnly: true, + maxAge: 600000 // 60 * 1000 ms === 1 min + } + }) + ); +}; diff --git a/middleware/isLoggedIn.js b/middleware/isLoggedIn.js new file mode 100644 index 0000000000..3b089e99e7 --- /dev/null +++ b/middleware/isLoggedIn.js @@ -0,0 +1,21 @@ +// middleware/route-guard.js + +// 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; + \ No newline at end of file diff --git a/middleware/isLoggedOut.js b/middleware/isLoggedOut.js new file mode 100644 index 0000000000..e90067c74c --- /dev/null +++ b/middleware/isLoggedOut.js @@ -0,0 +1,12 @@ +// middleware/route-guard.js + +// 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 = isLoggedOut; diff --git a/models/User.model.js b/models/User.model.js index 9cdd3a3ce4..f8b0a6fb41 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -6,7 +6,7 @@ const userSchema = new Schema({ type: String, unique: true }, - password: String + passwordHash: String }); const User = model("User", userSchema); diff --git a/package.json b/package.json index 19489d9695..d86da07c94 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,12 @@ "dev": "nodemon server.js" }, "dependencies": { + "bcrypt": "^5.1.1", + "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/funny-cat.jpg b/public/images/funny-cat.jpg new file mode 100644 index 0000000000..16f7e965c6 Binary files /dev/null and b/public/images/funny-cat.jpg differ diff --git a/public/images/office-meme.gif b/public/images/office-meme.gif new file mode 100644 index 0000000000..2413336a66 Binary files /dev/null and b/public/images/office-meme.gif differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 9453385b99..c9379a0cee 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -4,5 +4,9 @@ body { } a { - color: #00B7FF; + color: #00b7ff; +} + +img { + width: 80vw; } diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000000..fa1fcb31bd --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,96 @@ +const express = require("express"); +const bcrypt = require("bcrypt"); +const User = require("../models/User.model"); + +const isLoggedIn = require("../middleware/isLoggedIn"); +const isLoggedOut = require("../middleware/isLoggedOut"); + +const router = express.Router(); + +const saltRounds = 10; + +/* GET home page */ +router.get("/signup", isLoggedOut, (req, res, next) => { + res.render("auth/signup"); +}); + +// auth.routes.js +// the imports, get and post route remain untouched for now + +router.get("/userProfile", isLoggedIn, async (req, res) => { + try { + const user = await User.findOne({ + username: req.session.currentUser.username, + }); + res.render("auth/profile", user); + } catch (error) { + console.log(error); + res.redirect("/"); + } +}); + +router.post("/signup", isLoggedOut, async (req, res, next) => { + // console.log("The form data: ", req.body); + + const { username, email, password } = req.body; + + const hashedPassword = await bcrypt + + .genSalt(saltRounds) + + .then((salt) => bcrypt.hash(password, salt)) + + .then((hashedPassword) => { + return hashedPassword; + }) + + .catch((error) => next(error)); + + try { + const user = await User.create({ + username, + email, + passwordHash: hashedPassword, + }); + req.session.currentUser = user; + res.redirect("/userProfile"); + } catch (error) { + console.log(error); + res.redirect("/"); + } +}); + +router.get("/login", isLoggedOut, (req, res, next) => { + res.render("auth/login"); +}); + +router.post("/login", isLoggedOut, async (req, res, next) => { + const { username, password } = req.body; + + try { + const user = await User.findOne({ username }); + + if (!user) { + res.render("auth/login", { error: "Invalid login" }); + return; + } + + if (bcrypt.compareSync(password, user.passwordHash)) { + req.session.currentUser = user; + res.redirect("/userProfile"); + } + } catch (error) { + next(error); + } +}); + +router.post("/logout", isLoggedIn, (req, res, next) => { + req.session.destroy((err) => { + if (err) { + next(err); + } + }); + res.redirect("/"); +}); + +module.exports = router; diff --git a/routes/main.js b/routes/main.js new file mode 100644 index 0000000000..967f517b53 --- /dev/null +++ b/routes/main.js @@ -0,0 +1,11 @@ +const express = require("express"); + +const router = express.Router(); + +const isLoggedIn = require("../middleware/isLoggedIn"); + +router.get("/main", isLoggedIn, (req, res, next) => { + res.render("main"); +}); + +module.exports = router; diff --git a/routes/private.js b/routes/private.js new file mode 100644 index 0000000000..373b20b958 --- /dev/null +++ b/routes/private.js @@ -0,0 +1,11 @@ +const express = require("express"); + +const router = express.Router(); + +const isLoggedIn = require("../middleware/isLoggedIn"); + +router.get("/private", isLoggedIn, (req, res, next) => { + res.render("private"); +}); + +module.exports = router; diff --git a/views/auth/login.hbs b/views/auth/login.hbs new file mode 100644 index 0000000000..dccfbcfb31 --- /dev/null +++ b/views/auth/login.hbs @@ -0,0 +1,22 @@ +{{!-- views/auth/login.hbs --}} + +
This is your profile page my friend!
+{{ username }}
\ No newline at end of file diff --git a/views/auth/signup.hbs b/views/auth/signup.hbs new file mode 100644 index 0000000000..0433dc3e87 --- /dev/null +++ b/views/auth/signup.hbs @@ -0,0 +1,23 @@ +{{!-- views/auth/signup.hbs --}} + +Welcome to {{title}}
+ +Main +Login +Signup + \ No newline at end of file diff --git a/views/main.hbs b/views/main.hbs new file mode 100644 index 0000000000..7d6bb4a3f6 --- /dev/null +++ b/views/main.hbs @@ -0,0 +1,5 @@ +
\ No newline at end of file
diff --git a/views/private.hbs b/views/private.hbs
new file mode 100644
index 0000000000..52399321c6
--- /dev/null
+++ b/views/private.hbs
@@ -0,0 +1,3 @@
+
\ No newline at end of file