diff --git a/Controllers/auth.controller.js b/Controllers/auth.controller.js new file mode 100644 index 0000000000..fe770b1359 --- /dev/null +++ b/Controllers/auth.controller.js @@ -0,0 +1,92 @@ +const User = require("../models/User.model"); +const mongoose = require("mongoose"); + +// signup +module.exports.signup = (req, res, next) => { + res.render("auth/signup"); // Correct path FINALLYYYYYY +}; + + +// doSignup +module.exports.doSignup = (req, res, next) => { + User.create(req.body) + .then((user) => { + res.redirect("/login"); + }) + .catch((err) => { + if (err instanceof mongoose.Error.ValidationError) { + // Handle validation errors + res.render("auth/signup", { + user: { + email: req.body.email, + }, + errors: err.errors, + }); + } else if (err.code === 11000) { + // Handle duplicate key error + res.render("auth/signup", { + user: { + email: req.body.email, + }, + errors:{ + password:"This email is already registered. Please use a different one." + } + + }); + } else { + // Handle any other errors + next(err); + } + }); + } + + +// Render login form +module.exports.login = (req, res, next) => { + res.render("auth/login"); // Correct view path +}; + +// Handle login submission +module.exports.doLogin = (req, res, next) => { + const { email, password } = req.body; + + const renderWithErrors = () => { + res.render("auth/login", { + email, + error: "Email o contraseña incorrectos", // Incorrect email or password + }); + }; + +// The juicy part we did in class + User.findOne({ email }) + .then((user) => { + if (user) { + return user.checkPassword(password).then((match) => { + if (match) { + req.session.userId = user.id; // Save user ID in session (cookie magic!) + res.redirect("/profile"); + } else { + console.log("Contraseña incorrecta"); + renderWithErrors(); + } + }); + } else { + console.log("No existe usuario con ese email"); + renderWithErrors(); + } + }) + .catch((err) => next(err)); +}; + +// How you logout +module.exports.logout = (req, res, next) => { + req.session.destroy(); + res.clearCookie("express-cookie"); + res.redirect("/login"); +}; + + +// Add the main + + +// Add the private \ No newline at end of file diff --git a/Controllers/misc.controller.js b/Controllers/misc.controller.js new file mode 100644 index 0000000000..5ad3a6a1ac --- /dev/null +++ b/Controllers/misc.controller.js @@ -0,0 +1,13 @@ +const User = require("../models/User.model"); + +module.exports.home = (req, res, next) => { + res.render("index"); +}; + +module.exports.panel = (req, res, next) => { + User.find({ _id: { $ne: req.currentUser.id } }) // Excluir el usuario actual + .then((users) => { + res.render("control/panel", { users }); + }) + .catch((err) => next(err)); +}; \ No newline at end of file diff --git a/Controllers/users.controller.js b/Controllers/users.controller.js new file mode 100644 index 0000000000..24b61bc6fa --- /dev/null +++ b/Controllers/users.controller.js @@ -0,0 +1,14 @@ +const User = require("../models/User.model"); + +module.exports.profile = (req, res, next) => { + console.log(req.currentUser); + res.render("user/profile"); +}; + +module.exports.delete = (req, res, next) => { + User.findByIdAndDelete(req.params.id) + .then(() => { + res.redirect("/panel"); + }) + .catch((err) => next(err)); +}; \ No newline at end of file diff --git a/app.js b/app.js index 2ecc9f2220..74af6a385f 100644 --- a/app.js +++ b/app.js @@ -12,11 +12,15 @@ const express = require('express'); // Handles the handlebars // https://www.npmjs.com/package/hbs const hbs = require('hbs'); +hbs.registerPartials(__dirname + "/views/partials"); const app = express(); +const { sessionConfig, loggedUser } = require("./config/session.config"); // ℹ️ This function is getting exported from the config folder. It runs most middlewares require('./config')(app); +app.use(sessionConfig); +app.use(loggedUser); // default value for title local const projectName = 'lab-express-basic-auth'; @@ -31,5 +35,4 @@ app.use('/', index); // ❗ To handle errors. Routes that don't exist or errors that you handle in specific routes require('./error-handling')(app); -module.exports = app; - +module.exports = app; \ No newline at end of file diff --git a/config/index.js b/config/index.js index 4d9ff5c193..192aff1502 100644 --- a/config/index.js +++ b/config/index.js @@ -36,4 +36,4 @@ module.exports = (app) => { // Handles access to the favicon app.use(favicon(path.join(__dirname, "..", "public", "images", "favicon.ico"))); -}; +}; \ No newline at end of file diff --git a/config/session.config.js b/config/session.config.js new file mode 100644 index 0000000000..e7b06c13c5 --- /dev/null +++ b/config/session.config.js @@ -0,0 +1,42 @@ +const User = require("../models/User.model"); +const expressSession = require("express-session"); +const MongoStore = require("connect-mongo"); +const mongoose = require("mongoose"); + +const MAX_AGE = 7; + +module.exports.sessionConfig = expressSession({ + name: "express-cookie", + secret: "super-secret", + resave: false, + saveUninitialized: false, + cookie: { + secure: false, + httpOnly: true, + maxAge: 24 * 3600 * 1000 * MAX_AGE, + }, + store: new MongoStore({ + mongoUrl: mongoose.connection._connectionString, + ttl: 24 * 3600 * MAX_AGE, + }), +}); + +module.exports.loggedUser = (req, res, next) => { + const userId = req.session.userId; + + if (userId) { + User.findById(userId) + .then((userFromDB) => { + if (userFromDB) { + req.currentUser = userFromDB; // todos los middlewares ya tienen acceso a currentUser + res.locals.currentUser = userFromDB; // res.locals es el objeto donde se manda informacion a todas las vistas (hbs) + next(); + } else { + next(); + } + }) + .catch((err) => next(err)); + } else { + next(); + } +}; \ No newline at end of file diff --git a/middlewares/admin.middleware.js b/middlewares/admin.middleware.js new file mode 100644 index 0000000000..3c347c1c4f --- /dev/null +++ b/middlewares/admin.middleware.js @@ -0,0 +1,9 @@ +module.exports.isAdmin = (req, res, next) => { + if (req.currentUser.isAdmin) { + next(); + } else { + res.render("error", { + error: "No eres admin", + }); + } + }; \ No newline at end of file diff --git a/middlewares/auth.middleware.js b/middlewares/auth.middleware.js new file mode 100644 index 0000000000..3e09fa2d43 --- /dev/null +++ b/middlewares/auth.middleware.js @@ -0,0 +1,15 @@ +module.exports.isAuthenticated = (req, res, next) => { + if (req.currentUser) { + next(); + } else { + res.redirect("/login"); + } + }; + + module.exports.isNotAuthenticated = (req, res, next) => { + if (!req.currentUser) { + next(); + } else { + res.redirect("/profile"); + } + }; \ No newline at end of file diff --git a/models/User.model.js b/models/User.model.js index 9cdd3a3ce4..e9ceb65c85 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -1,14 +1,46 @@ -const { Schema, model } = require("mongoose"); +const mongoose = require("mongoose"); +const bcrypt = require("bcrypt"); -// TODO: Please make sure you edit the user model to whatever makes sense in this case -const userSchema = new Schema({ - username: { +const EMAIL_PATTERN = + /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + +const UserSchema = new mongoose.Schema({ + name: { + type: String, + trim: true, + }, + email: { + type: String, + required: [true, "Email is required"], + unique: true, + match: [EMAIL_PATTERN, "Email is invalid"], + trim: true, + lowercase: true, + }, + password: { type: String, - unique: true + required: true, + minLength: [8, "Password must be 8 characters or longer"], }, - password: String }); -const User = model("User", userSchema); +UserSchema.pre("save", function (next) { + const user = this; + + if (user.isModified("password")) { + bcrypt.hash(user.password, 10).then((hash) => { + user.password = hash; + next(); + }); + } else { + next(); + } +}); + +UserSchema.methods.checkPassword = function (password) { + return bcrypt.compare(password, this.password); +}; + +const User = mongoose.model("User", UserSchema); -module.exports = User; +module.exports = User; \ No newline at end of file diff --git a/package.json b/package.json index 19489d9695..4ba0707b23 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "nodemon server.js" }, "dependencies": { + "bcrypt": "^5.1.1", "cookie-parser": "^1.4.5", "dotenv": "^8.2.0", "express": "^4.17.1", diff --git a/routes/index.js b/routes/index.js index 81c2396ceb..b5e650174f 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,54 @@ const router = require("express").Router(); +//Define the controllers +const authController = require("../Controllers/auth.controller") +const miscController = require("../Controllers/misc.controller"); +const usersController = require("../Controllers/users.controller"); + +//Define the middlewares +const authMiddlewares = require("../middlewares/auth.middleware"); +const adminMiddlewares = require("../middlewares/admin.middleware"); + /* GET home page */ router.get("/", (req, res, next) => { res.render("index"); }); + +router.get("/", miscController.home); +router.get("/panel", adminMiddlewares.isAdmin, miscController.panel); + + +// Signup routes +router.get("/signup", authMiddlewares.isNotAuthenticated, authController.signup); +router.post("/signup", authMiddlewares.isNotAuthenticated, authController.doSignup); + +// Login routes +router.get("/login", authMiddlewares.isNotAuthenticated, authController.login); +router.post("/login", authMiddlewares.isNotAuthenticated, authController.doLogin); + +// Now the Logout part +router.get("/logout", authController.logout); + +// Delete your profile +router.post("/users/delete/:id", adminMiddlewares.isAdmin, usersController.delete); + +// and aha, this is the Profillleeee +router.get("/profile", authMiddlewares.isAuthenticated, usersController.profile); + +// The main (protected) +router.get("/main", authMiddlewares.isAuthenticated, (req, res, next) => { + res.render("user/main"); +}); + +// Private (protected) +router.get("/private", authMiddlewares.isAuthenticated, (req, res, next) => { + res.render("user/private"); +}); + module.exports = router; + + + + + diff --git a/views/auth/login.hbs b/views/auth/login.hbs new file mode 100644 index 0000000000..e78098e350 --- /dev/null +++ b/views/auth/login.hbs @@ -0,0 +1,14 @@ +

Log in

+{{#if error}} +

{{error}}

+{{/if}} + +
+ + + + + + + +
\ No newline at end of file diff --git a/views/auth/signup.hbs b/views/auth/signup.hbs new file mode 100644 index 0000000000..fce76831ed --- /dev/null +++ b/views/auth/signup.hbs @@ -0,0 +1,42 @@ +
+

Sign Up

+
+ +
+ + + {{#if errors.email}} +
+ {{errors.email}} +
+ {{/if}} +
+ + +
+ + + {{#if errors.password}} +
+ {{errors.password}} +
+ {{/if}} +
+ + + +
+
\ No newline at end of file diff --git a/views/control/panel.hbs b/views/control/panel.hbs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/views/index.hbs b/views/index.hbs index 1f308fdb35..e1b36caefa 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -1,2 +1,7 @@

{{title}}

Welcome to {{title}}

+ +
+ Sign Up +
+ diff --git a/views/layout.hbs b/views/layout.hbs index 73199c166b..5035a9335c 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -1,19 +1,23 @@ - + {{> nav}} {{title}} + - - + + + {{{body}}} - + + - + + \ No newline at end of file diff --git a/views/partials/nav.hbs b/views/partials/nav.hbs new file mode 100644 index 0000000000..7dab195400 --- /dev/null +++ b/views/partials/nav.hbs @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/views/user/main.hbs b/views/user/main.hbs new file mode 100644 index 0000000000..e20c4c77d6 --- /dev/null +++ b/views/user/main.hbs @@ -0,0 +1,3 @@ +

Alohaaaa means hello and goodbye

+

Quak Quakkkk:

+Goofy Duckkk diff --git a/views/user/private.hbs b/views/user/private.hbs new file mode 100644 index 0000000000..a5957d4f14 --- /dev/null +++ b/views/user/private.hbs @@ -0,0 +1,5 @@ +
+

Call me if you get lost

+

This page is protected and only visible to you bookieee!

+ Speaking of being iconic +
diff --git a/views/user/profile.hbs b/views/user/profile.hbs new file mode 100644 index 0000000000..338f940e8c --- /dev/null +++ b/views/user/profile.hbs @@ -0,0 +1,6 @@ +{{!-- views/users/user-profile.hbs --}} +

PROFILE

+

This is your profile page my friend!

+
+

{{currentUser.email}}

+
\ No newline at end of file