diff --git a/app.js b/app.js index 2ecc9f2220..18ce25be3a 100644 --- a/app.js +++ b/app.js @@ -8,15 +8,21 @@ require('./db'); // Handles http requests (express is node js framework) // https://www.npmjs.com/package/express const express = require('express'); +const { sessionConfig, loggedUser } = require("./config/session.config"); // Handles the handlebars // https://www.npmjs.com/package/hbs const hbs = require('hbs'); + const app = express(); // ℹ️ 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'; @@ -25,11 +31,12 @@ const capitalized = string => string[0].toUpperCase() + string.slice(1).toLowerC app.locals.title = `${capitalized(projectName)}- Generated with Ironlauncher`; // 👇 Start handling routes here -const index = require('./routes/index'); +const index = require('./routes/routes'); 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; diff --git a/config/index.js b/config/index.js index 4d9ff5c193..a26cf973a5 100644 --- a/config/index.js +++ b/config/index.js @@ -1,5 +1,6 @@ // We reuse this import in order to have access to the `body` property in requests const express = require("express"); +const hbs = require("hbs"); // ℹ️ Responsible for the messages you see in the terminal as requests are coming in // https://www.npmjs.com/package/morgan @@ -27,6 +28,11 @@ module.exports = (app) => { app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); + hbs.registerPartials(__dirname + "/../views/partials"); + + + + // Normalizes the path to the views folder app.set("views", path.join(__dirname, "..", "views")); // Sets the view engine to handlebars diff --git a/config/session.config.js b/config/session.config.js new file mode 100644 index 0000000000..18cf04fa04 --- /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", // esto lo guardaamos en el dot.env COOKIE_SECRET + resave: false, + saveUninitialized: false, + cookie: { + secure: false, // mandamos la cookie en protocolos HTTP/HTTPS si es true solo HTTPS + httpOnly: true, // no es accesible por el Javascript del client-browser + maxAge: 24 * 3600 * 1000 * MAX_AGE, // una semana de vida + }, + store: new MongoStore({ + mongoUrl: mongoose.connection._connectionString, //monngoose.connection.db + 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(); + } + }; diff --git a/controllers/auth.controller.js b/controllers/auth.controller.js new file mode 100644 index 0000000000..14b348480f --- /dev/null +++ b/controllers/auth.controller.js @@ -0,0 +1,83 @@ +const User = require("../models/User.model"); +const mongoose = require("mongoose"); + + +module.exports.register = (req, res, next) => { + res.render("auth/register") +} + +module.exports.doRegister = (req, res, next) => { + User.create(req.body) + .then((user) => { + res.redirect("/login"); + }) + .catch((err) => { + if (err instanceof mongoose.Error.ValidationError) { + res.render("auth/register", { + user: { + email: req.body.email, + }, + errors: err.errors, + }); + } + else if (err.code === 11000) { + + res.render("auth/register", { + user: req.body, + errors: { + email: "El correo electrónico ya está registrado.", + }, + }); + } else { + next(err); + } + }); +}; + + +module.exports.login = (req, res, next) => { + res.render("auth/login"); + }; + module.exports.doLogin = (req, res, next) => { + const { email, password } = req.body; + User.findOne({ email }) + .then((user) => { + if (user) { + return user.checkPassword(password).then((match) => { + if (match) { + req.session.userId = user.id; // genero cookie y session + res.redirect("/main"); + } else { + res.render("auth/login", { + errors: { + error: "Email o contraseña incorrectos", + email: req.body.email, // Mensaje de error para el usuario + }, + }); + + } + }); + } else { + res.render("auth/login", { + errors: { + error: "Email o contraseña incorrectos", + email: req.body.email, // Mensaje de error para el usuario + }, + }); + } + }) + .catch((err) => next(err)); + }; + + module.exports.logout = (req, res, next) => { + req.session.destroy(); + res.clearCookie("express-cookie"); + res.redirect("/login"); + }; + + + module.exports.main = (req, res, next) => { + res.render("auth/register") + } + + \ No newline at end of file diff --git a/controllers/misc.controller.js b/controllers/misc.controller.js new file mode 100644 index 0000000000..78d19aa1df --- /dev/null +++ b/controllers/misc.controller.js @@ -0,0 +1,3 @@ +module.exports.home= (req, res, next) => { + res.render("index"); +}; diff --git a/controllers/user.controller.js b/controllers/user.controller.js new file mode 100644 index 0000000000..7afb1864dd --- /dev/null +++ b/controllers/user.controller.js @@ -0,0 +1,17 @@ +const User = require("../models/User.model"); + +module.exports.profile = (req, res, next) =>{ + res.render("users/main"); +} + +module.exports.private = (req, res, next) =>{ + res.render("users/private"); +} + +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/error-handling/index.js b/error-handling/index.js deleted file mode 100644 index 5d6e74bc1c..0000000000 --- a/error-handling/index.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = (app) => { - app.use((req, res, next) => { - // this middleware runs whenever requested page is not available - res.status(404).render("not-found"); - }); - - app.use((err, req, res, next) => { - // whenever you call next(err), this middleware will handle the error - // always logs the error - console.error("ERROR", req.method, req.path, err); - - // only render if the error ocurred before sending the response - if (!res.headersSent) { - res.status(500).render("error"); - } - }); -}; diff --git a/middlewares/auth.middleware.js b/middlewares/auth.middleware.js new file mode 100644 index 0000000000..c268f65489 --- /dev/null +++ b/middlewares/auth.middleware.js @@ -0,0 +1,17 @@ +module.exports.isAutenticated = (req, res, next) => { + if (req.currentUser) { + next(); + }else{ + res.redirect("/login"); + } + + +}; + +module.exports.isNoAutenticated = (req, res, next) => { + if (!req.currentUser) { + next(); + }else{ + res.redirect("/main"); + } +}; \ No newline at end of file diff --git a/models/User.model.js b/models/User.model.js index 9cdd3a3ce4..e3b66e4e6a 100644 --- a/models/User.model.js +++ b/models/User.model.js @@ -1,14 +1,44 @@ -const { Schema, model } = require("mongoose"); +const mongoose = require("mongoose"); +const bcrypt = require("bcrypt"); + +const EMAIL_PATTERN = + /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + // TODO: Please make sure you edit the user model to whatever makes sense in this case -const userSchema = new Schema({ - username: { +const UserSchema = new mongoose.Schema({ + email: { type: String, - unique: true + required: [true, "Email is required"], + unique: true, + match: [EMAIL_PATTERN, "Email is invalid"], + trim: true, + lowercase: true, }, - password: String + password: { + type: String, + required: true, + minLength: [8, "Password must be 8 characters or longer"], + } }); -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; diff --git a/package.json b/package.json index 19489d9695..8de3a24434 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.1", "hbs": "^4.1.1", "mongoose": "^6.1.2", "morgan": "^1.10.0", diff --git a/public/images/homer.gif b/public/images/homer.gif new file mode 100644 index 0000000000..1d04e506f4 Binary files /dev/null and b/public/images/homer.gif differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 9453385b99..89553919b1 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1,5 +1,5 @@ body { - padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } diff --git a/routes/index.js b/routes/index.js deleted file mode 100644 index 81c2396ceb..0000000000 --- a/routes/index.js +++ /dev/null @@ -1,8 +0,0 @@ -const router = require("express").Router(); - -/* GET home page */ -router.get("/", (req, res, next) => { - res.render("index"); -}); - -module.exports = router; diff --git a/routes/routes.js b/routes/routes.js new file mode 100644 index 0000000000..a1b30dda99 --- /dev/null +++ b/routes/routes.js @@ -0,0 +1,24 @@ +const router = require("express").Router(); + +const miscController = require("../controllers/misc.controller") +const authController = require("../controllers/auth.controller") +const authMiddleweares = require("../middlewares/auth.middleware") +const userController = require ("../controllers/user.controller") +/* GET home page */ +router.get("/", miscController.home) + +router.get("/register", authMiddleweares.isNoAutenticated, authController.register) +router.post("/register", authMiddleweares.isNoAutenticated, authController.doRegister) +router.get("/login", authMiddleweares.isNoAutenticated, authMiddleweares.isNoAutenticated, authController.login) +router.post("/login", authMiddleweares.isNoAutenticated, authController.doLogin) +router.get("/logout",authController.logout) +router.get("/main", authMiddleweares.isAutenticated, userController.profile) +router.get("/private", authMiddleweares.isAutenticated, userController.private) + + + + + + + +module.exports = router; diff --git a/views/auth/login.hbs b/views/auth/login.hbs new file mode 100644 index 0000000000..716d69505b --- /dev/null +++ b/views/auth/login.hbs @@ -0,0 +1,30 @@ +