Skip to content
Open

solved #2126

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ app.locals.title = `${capitalized(projectName)}- Generated with Ironlauncher`;
const index = require('./routes/index');
app.use('/', index);

const authRoutes = require(`./routes/auth.routes`);
app.use(`/auth`, authRoutes);

// ❗ To handle errors. Routes that don't exist or errors that you handle in specific routes
require('./error-handling')(app);

Expand Down
30 changes: 28 additions & 2 deletions config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ const favicon = require("serve-favicon");
// https://www.npmjs.com/package/path
const path = require("path");

// ℹ️ Session middleware for authentication
// https://www.npmjs.com/package/express-session
const session = require("express-session");

// ℹ️ MongoStore in order to save the user session in the database
// https://www.npmjs.com/package/connect-mongo
const MongoStore = require("connect-mongo");

// Connects the mongo uri to maintain the same naming structure
const MONGO_URI =
process.env.MONGODB_URI || "mongodb://127.0.0.1:27017/New-project";

// Middleware configuration
module.exports = (app) => {
// In development environment the app logs
Expand All @@ -31,9 +43,23 @@ module.exports = (app) => {
app.set("views", path.join(__dirname, "..", "views"));
// Sets the view engine to handlebars
app.set("view engine", "hbs");
// Handles access to the public folder
// AHandles access to the public folder
app.use(express.static(path.join(__dirname, "..", "public")));

// Handles access to the favicon
app.use(favicon(path.join(__dirname, "..", "public", "images", "favicon.ico")));
app.use(
favicon(path.join(__dirname, "..", "public", "images", "favicon.ico"))
);

// ℹ️ Middleware that adds a "req.session" information and later to check that you are who you say you are 😅
app.use(
session({
secret: process.env.SESSION_SECRET || "super hyper secret key",
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: MONGO_URI,
}),
})
);
};
8 changes: 8 additions & 0 deletions middleware/isLoggedIn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = (req, res, next) => {
// checks if the user is logged in when trying to access a specific page
if (!req.session.currentUser) {
return res.redirect("/auth/login");
}

next();
};
9 changes: 9 additions & 0 deletions middleware/isLoggedOut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = (req, res, next) => {
// if an already logged in user tries to access the login page it
// redirects the user to the home page
if (req.session.currentUser) {
return res.redirect("/");
}
next();
};

32 changes: 25 additions & 7 deletions models/User.model.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
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
// TODO: Please make sure you edit the User model to whatever makes sense in this case
const userSchema = new Schema(
{
username: {
type: String,
required: true,
unique: true,
trim: true,
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
},
password: {
type: String,
required: true,
},
},
password: String
});
{
// this second object adds extra properties: `createdAt` and `updatedAt`
timestamps: true,
}
);

const User = model("User", userSchema);

Expand Down
16 changes: 10 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
"start": "node server.js",
"dev": "nodemon server.js"
},

"dependencies": {
"cookie-parser": "^1.4.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"hbs": "^4.1.1",
"mongoose": "^6.1.2",
"bcryptjs": "^2.4.3",
"connect-mongo": "^5.1.0",
"cookie-parser": "^1.4.6",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.0",
"hbs": "^4.2.0",
"mongoose": "^8.4.0",
"morgan": "^1.10.0",
"serve-favicon": "^2.5.0"
},
"devDependencies": {
"nodemon": "^2.0.7"
"nodemon": "^3.1.2"
}
}
156 changes: 156 additions & 0 deletions routes/auth.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
const express = require("express");
const router = express.Router();

// ℹ️ Handles password encryption
const bcrypt = require("bcryptjs");
const mongoose = require("mongoose");

// How many rounds should bcrypt run the salt (default - 10 rounds)
const saltRounds = 10;

// Require the User model in order to interact with the database
const User = require("../models/User.model");

// Require necessary (isLoggedOut and isLiggedIn) middleware in order to control access to specific routes
const isLoggedOut = require("../middleware/isLoggedOut");
const isLoggedIn = require("../middleware/isLoggedIn");

// GET /auth/signup
router.get("/signup", isLoggedOut, (req, res) => {
res.render("auth/signup");
});

// POST /auth/signup
router.post("/signup", isLoggedOut, (req, res) => {
const { username, email, password } = req.body;

// Check that username, email, and password are provided
if (username === "" || email === "" || password === "") {
res.status(400).render("auth/signup", {
errorMessage:
"All fields are mandatory. Please provide your username, email and password.",
});

return;
}

if (password.length < 6) {
res.status(400).render("auth/signup", {
errorMessage: "Your password needs to be at least 6 characters long.",
});

return;
}

// ! This regular expression checks password for special characters and minimum length
/*
const regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/;
if (!regex.test(password)) {
res
.status(400)
.render("auth/signup", {
errorMessage: "Password needs to have at least 6 chars and must contain at least one number, one lowercase and one uppercase letter."
});
return;
}
*/

// Create a new user - start by hashing the password
bcrypt
.genSalt(saltRounds)
.then((salt) => bcrypt.hash(password, salt))
.then((hashedPassword) => {
// Create a user and save it in the database
return User.create({ username, email, password: hashedPassword });
})
.then((user) => {
res.redirect("/auth/login");
})
.catch((error) => {
if (error instanceof mongoose.Error.ValidationError) {
res.status(500).render("auth/signup", { errorMessage: error.message });
} else if (error.code === 11000) {
res.status(500).render("auth/signup", {
errorMessage:
"Username and email need to be unique. Provide a valid username or email.",
});
} else {
next(error);
}
});
});

// GET /auth/login
router.get("/login", isLoggedOut, (req, res) => {
res.render("auth/login");
});

// POST /auth/login
router.post("/login", isLoggedOut, (req, res, next) => {
const { username, email, password } = req.body;

// Check that username, email, and password are provided
if (username === "" || email === "" || password === "") {
res.status(400).render("auth/login", {
errorMessage:
"All fields are mandatory. Please provide username, email and password.",
});

return;
}

// Here we use the same logic as above
// - either length based parameters or we check the strength of a password
if (password.length < 6) {
return res.status(400).render("auth/login", {
errorMessage: "Your password needs to be at least 6 characters long.",
});
}

// Search the database for a user with the email submitted in the form
User.findOne({ email })
.then((user) => {
// If the user isn't found, send an error message that user provided wrong credentials
if (!user) {
res
.status(400)
.render("auth/login", { errorMessage: "Wrong credentials." });
return;
}

// If user is found based on the username, check if the in putted password matches the one saved in the database
bcrypt
.compare(password, user.password)
.then((isSamePassword) => {
if (!isSamePassword) {
res
.status(400)
.render("auth/login", { errorMessage: "Wrong credentials." });
return;
}

// Add the user object to the session object
req.session.currentUser = user.toObject();
// Remove the password field
delete req.session.currentUser.password;

res.redirect("/");
})
.catch((err) => next(err)); // In this case, we send error handling to the error handling middleware.
})
.catch((err) => next(err));
});

// GET /auth/logout
router.get("/logout", isLoggedIn, (req, res) => {
req.session.destroy((err) => {
if (err) {
res.status(500).render("auth/logout", { errorMessage: err.message });
return;
}

res.redirect("/");
});
});

module.exports = router;
3 changes: 2 additions & 1 deletion routes/index.js
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
24 changes: 24 additions & 0 deletions views/auth/login.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div id="login-form">
<h2>Log In</h2>
<form action="/auth/login" method="POST">

<label> Username
<input type="text" name="username" placeholder="Your username" />
</label>

<label> Email
<input type="email" name="email" placeholder="Your email" />
</label>

<label> Password
<input type="password" name="password" placeholder="Your password" />
</label>

<button type="submit">Log In</button>


{{#if errorMessage}}
<p class="error">{{errorMessage}}</p>
{{/if}}
</form>
</div>
4 changes: 4 additions & 0 deletions views/auth/logout.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{!-- this file is only shown whenever there is an error during log out --}}
{{#if errorMessage}}
{{errorMessage}}
{{/if}}
24 changes: 24 additions & 0 deletions views/auth/singup.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div id="form">
<h2>Sign Up</h2>
<form action="/auth/signup" method="POST">

<label> Username
<input type="text" name="username" placeholder="Your username" />
</label>

<label> Email
<input type="email" name="email" placeholder="Your email" />
</label>

<label> Password
<input type="password" name="password" placeholder="Your password" />
</label>

<button type="submit">Sign Up</button>


{{#if errorMessage}}
<p class="error">{{errorMessage}}</p>
{{/if}}
</form>
</div>
1 change: 0 additions & 1 deletion views/layout.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
</head>

<body>

{{{body}}}

<script src="/js/script.js"></script>
Expand Down
1 change: 1 addition & 0 deletions views/partials/navbar.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a href="/signup">Signup</a>