diff --git a/example.env.txt b/example.env.txt index 60dc7ce..0f5f071 100644 --- a/example.env.txt +++ b/example.env.txt @@ -1,3 +1,3 @@ -MONGODB_URL = mongodb://localhost:27017/course +MONGODB_URL = mongodb://localhost:27017/course SECRET_KEY = YOUR_SECRET_KEY_HERE PORT = 3000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1aced15..dae56f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "body-parser": "^1.20.2", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.6", "csurf": "^1.11.0", @@ -227,12 +228,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -240,7 +241,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -700,6 +701,43 @@ "node": ">= 0.6" } }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -1761,9 +1799,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", diff --git a/package.json b/package.json index cedbe16..155dcfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "bcrypt": "^5.1.1", + "body-parser": "^1.20.2", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.6", "csurf": "^1.11.0", diff --git a/src/app.js b/src/app.js index b1ceafd..7364722 100644 --- a/src/app.js +++ b/src/app.js @@ -1,14 +1,15 @@ const express = require("express"); -const mongoose = require("mongoose"); + const fs = require("fs"); const passport = require("passport"); const LocalStrategy = require("passport-local").Strategy; const session = require("express-session"); const flash = require("connect-flash"); const morgan = require("morgan"); -const bcrypt = require("bcrypt"); // Import bcrypt for password hashing -const rateLimit = require("express-rate-limit"); -const csrf = require("csurf"); +const bodyparser = require('body-parser') +const limiter=require("./utils/limiter") +const addCSRF = require("./middlewares/addCSRF"); + const cookieParser = require("cookie-parser"); const mongoSanitize = require("express-mongo-sanitize"); const dotenv = require("dotenv"); @@ -26,33 +27,35 @@ const isAuthenticated = require("./middlewares/isAuthenticated"); const app = express(); -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 5, // 5 requests per windowMs - message: "Too many requests from this IP, please try again later.", -}); //Views folder should be accessible from anywhere.. app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs"); -app.use(express.urlencoded({ extended: true })); + +app.use(bodyparser.urlencoded({extended:true})); +// app.use(app.use(bodyparser.urlencoded({ extended: true })); +app.use(bodyparser.json()); app.use(morgan("dev")); app.use(mongoSanitize()); -const addCSRF = require("./middlewares/addCSRF"); + //Regular middleware app.use(cookieParser()); -//app.use(csrf()); -//app.use(addCSRF) +const csrf = require("csurf"); + app.use( session({ - secret: process.env.SECRET_KEY, + secret: process.env.SECRET_KEY, resave: false, saveUninitialized: true, }) -); +); + +app.use(csrf()); +app.use(addCSRF) + app.use(flash()); // Initialize Passport and session middleware require("./config/passportConfig"); @@ -62,180 +65,21 @@ app.use(passport.session()); const csrfProtection = csrf({ cookie: true }); app.use(csrfProtection); -app.get("/login", limiter, csrfProtection, (req, res) => { - if (req.isAuthenticated()) { - return res.redirect("/"); - } else { - res.render("login", { - messages: req.flash("error"), - csrfToken: req.csrfToken(), - }); // Pass flash messages to the template - } -}); - -app.post("/login", limiter, csrfProtection, (req, res, next) => { - /*console.log(req.body, req.csrfToken()) - if (!req.body._csrf || req.body._csrf !== req.csrfToken()) { - return res.status(403).send("CSRF token validation failed."); - }*/ - passport.authenticate("local", (err, user, info) => { - if (err) { - return next(err); - } - if (!user) { - req.flash("error", "Incorrect username or password."); // Set flash message - return res.redirect("/login"); // Redirect with flash message - } - req.logIn(user, (err) => { - if (err) { - return next(err); - } - return res.redirect("/"); - }); - })(req, res, next); -}); - -app.get("/logout", limiter, (req, res) => { - req.session.destroy(function (err) { - if (err) { - console.error("Error during logout:", err); - } else { - res.redirect("/login"); - } - }); -}); - -app.get("/", isAuthenticated, (req, res) => { - // This route is protected and can only be accessed by authenticated users - res.render("home"); -}); - -app.get("/register", (req, res) => { - if (req.isAuthenticated()) return res.redirect("/"); - console.log(req.csrfToken()); - res.render("register", { - messages: req.flash("error"), - csrfToken: req.csrfToken(), - }); -}); -app.post("/register", limiter, csrfProtection, async (req, res) => { - /*if (!req.body._csrf || req.body._csrf !== req.csrfToken()) { - return res.status(403).send("CSRF token validation failed."); - }*/ - const { username, email, password, confirmPassword, fullName } = req.body; - - try { - // Check if the username or email already exists in the database - const existingUser = await User.findOne({ - $or: [{ username: username }, { email: email }], - }); - - if (existingUser) { - req.flash("error", "Username or email already in use."); - return res.redirect("/register"); - } - - // Check if the password and confirmPassword match - if (password !== confirmPassword) { - req.flash("error", "Passwords do not match."); - return res.redirect("/register"); - } - - // Hash the password before saving it - const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password, salt); - - // Create a new user document and save it to the database - const newUser = new User({ - username: username, - email: email, - password: hashedPassword, - fullName, - // Additional user profile fields can be added here - }); - - await newUser.save(); - - // Redirect to the login page after successful registration - res.redirect("/login"); - } catch (error) { - console.error("Error during registration:", error); - req.flash("error", "Registration failed. Please try again."); - res.redirect("/register"); - } -}); - -app.get("/profile", isAuthenticated, async (req, res) => { - res.render("profile", { - user: req.user, - messages: req.flash(), - csrfToken: req.csrfToken(), - }); -}); - -app.post( - "/profile", - limiter, - isAuthenticated, - csrfProtection, - async (req, res) => { - /*if (!req.body._csrf || req.body._csrf !== req.csrfToken()) { - return res.status(403).send("CSRF token validation failed."); - }*/ - const { fullName, avatarUrl, bio, location, website } = req.body; - - try { - // Find the user by their ID (you need to have the user ID stored in the session) - const userId = req.user._id; // Assuming you have a user object in the session - const user = await User.findById(userId); - - if (!user) { - // Handle the case where the user is not found - return res.status(404).send("User not found."); - } - - // Update the user's profile fields - user.fullName = fullName; - user.avatarUrl = avatarUrl; - user.bio = bio; - user.location = location; - user.website = website; - - // Save the updated user profile - await user.save(); - - // Redirect to the user's profile page or any other desired page - return res.redirect("/profile"); - } catch (error) { - console.error("Error updating profile:", error); - // Handle the error, display an error message, or redirect to an error page - return res.status(500).send("Error updating profile."); - } - } -); app.use("/courses", limiter, isAuthenticated, async function (req, res) { const courses = await courseModel.find(); return res.render("course", { courses: courses }); }); -app.post("/search-course", limiter, isAuthenticated, async function (req, res) { - const query = req.body.query; - const regexQuery = { - title: { $regex: query, $options: "i" }, - }; - try { - const searchCourses = await courseModel.findOne(regexQuery); - res.json(searchCourses); - } catch (err) { - console.error(err); - res.json({ message: "An error occurred while searching." }); - } -}); + app.use("/css", express.static("src/css")); +// user routes +const userRoutes=require("./routes/userRoutes") +app.use(userRoutes) + // Start the server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { diff --git a/src/config/dbconfig.js b/src/config/dbconfig.js index 6ac912a..ebe078f 100644 --- a/src/config/dbconfig.js +++ b/src/config/dbconfig.js @@ -15,3 +15,6 @@ function dbConfig() { }); } module.exports = dbConfig; + + +//Everything diff --git a/src/controller/userController.js b/src/controller/userController.js new file mode 100644 index 0000000..baa6c4c --- /dev/null +++ b/src/controller/userController.js @@ -0,0 +1,181 @@ +const passport = require("passport"); +const csrf=require("csurf"); +const addCSRF=require("../middlewares/addCSRF") +const isAuthenticated=require("../middlewares/isAuthenticated") +const User=require("../db/User") +const bcrypt=require("bcrypt") +const courseModel= require("../db/courseDB") +const csrfProtection = csrf({cookie:true}); + + + + + + +exports.loginGet=(req, res) => { + if (req.isAuthenticated()) { + return res.redirect("/"); + } else { + res.render("login", { + messages: req.flash("error"), + csrfToken: req.csrfToken(), + }); // Pass flash messages to the template + } + }; + + exports.loginPost=(req, res, next) => { + /*console.log(req.body, req.csrfToken()) + if (!req.body._csrf || req.body._csrf !== req.csrfToken()) { + return res.status(403).send("CSRF token validation failed."); + }*/ + passport.authenticate("local", (err, user, info) => { + if (err) { + return next(err); + } + if (!user) { + req.flash("error", "Incorrect username or password."); // Set flash message + return res.redirect("/login"); // Redirect with flash message + } + req.logIn(user, (err) => { + if (err) { + return next(err); + } + return res.redirect("/"); + }); + })(req, res, next); + }; + + exports.logout=(req, res) => { + req.session.destroy(function (err) { + if (err) { + console.error("Error during logout:", err); + } else { + res.redirect("/login"); + } + }); + }; + +exports.landingPage= (req, res) => { + // This route is protected and can only be accessed by authenticated users + res.render("home"); + }; + + exports.registerGet= (req, res) => { + if (req.isAuthenticated()) return res.redirect("/"); + console.log(req.csrfToken()); + res.render("register", { + messages: req.flash("error"), + csrfToken: req.csrfToken(), + }); + }; + + + + + exports.registerPost=async (req, res) => { + /*if (!req.body._csrf || req.body._csrf !== req.csrfToken()) { + return res.status(403).send("CSRF token validation failed."); + }*/ + const { username, email, password, confirmPassword, fullName } = req.body; + + try { + // Check if the username or email already exists in the database + const existingUser = await User.findOne({ + $or: [{ username: username }, { email: email }], + }); + + if (existingUser) { + req.flash("error", "Username or email already in use."); + return res.redirect("/register"); + } + + // Check if the password and confirmPassword match + if (password !== confirmPassword) { + req.flash("error", "Passwords do not match."); + return res.redirect("/register"); + } + + // Hash the password before saving it + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + // Create a new user document and save it to the database + const newUser = new User({ + username: username, + email: email, + password: hashedPassword, + fullName, + // Additional user profile fields can be added here + }); + + await newUser.save(); + + // Redirect to the login page after successful registration + res.redirect("/login"); + } catch (error) { + console.error("Error during registration:", error); + req.flash("error", "Registration failed. Please try again."); + res.redirect("/register"); + } + }; + + +exports.profileGet=async (req, res) => { + res.render("profile", { + user: req.user, + messages: req.flash(), + csrfToken: req.csrfToken(), + }); + }; + + + + exports.profilePost= async (req, res) => { + /*if (!req.body._csrf || req.body._csrf !== req.csrfToken()) { + return res.status(403).send("CSRF token validation failed."); + }*/ + const { fullName, avatarUrl, bio, location, website } = req.body; + + try { + // Find the user by their ID (you need to have the user ID stored in the session) + const userId = req.user._id; // Assuming you have a user object in the session + const user = await User.findById(userId); + + if (!user) { + // Handle the case where the user is not found + return res.status(404).send("User not found."); + } + + // Update the user's profile fields + user.fullName = fullName; + user.avatarUrl = avatarUrl; + user.bio = bio; + user.location = location; + user.website = website; + + // Save the updated user profile + await user.save(); + + // Redirect to the user's profile page or any other desired page + return res.redirect("/profile"); + } catch (error) { + console.error("Error updating profile:", error); + // Handle the error, display an error message, or redirect to an error page + return res.status(500).send("Error updating profile."); + } + }; + + + exports.searchCourse=async function (req, res) { + const query = req.body.query; + const regexQuery = { + title: { $regex: query, $options: "i" }, + }; + try { + const searchCourses = await courseModel.findOne(regexQuery); + res.json(searchCourses); + } catch (err) { + console.error(err); + res.json({ message: "An error occurred while searching." }); + } + }; \ No newline at end of file diff --git a/src/middlewares/addCSRF.js b/src/middlewares/addCSRF.js index c24c881..b4e6b0d 100644 --- a/src/middlewares/addCSRF.js +++ b/src/middlewares/addCSRF.js @@ -1,6 +1,9 @@ function addCSRF(req, res, next) { - res.locals.csrfToken = req.csrfToken(); + var token = req.csrfToken(); + res.cookie('XSRF-TOKEN', token); + res.locals.csrfToken = token; next(); + } module.exports = addCSRF; \ No newline at end of file diff --git a/src/routes/userRoutes.js b/src/routes/userRoutes.js new file mode 100644 index 0000000..d8acfc8 --- /dev/null +++ b/src/routes/userRoutes.js @@ -0,0 +1,25 @@ +const express=require("express"); +const csrf=require("csurf") +const addCSRF = require("../middlewares/addCSRF"); +const csrfProtection=csrf({cookie:true}) +const {loginGet, loginPost, logout, landingPage, registerGet, registerPost, profileGet, profilePost, searchCourse}=require("../controller/userController") +// const { loginGet, loginPost, logout, landingPage, registerGet, registerPost, profileGet, profilePost, searchCourse } = require("../controller/userRoutesController"); +const limiter=require("../utils/limiter") +const isAuthenticated = require("../middlewares/isAuthenticated"); + +const router=express.Router(); + + + +router.route("/login").get(limiter,csrfProtection,loginGet) +router.route("/login").post(csrfProtection,limiter,loginPost) +router.route("/logout").get(limiter,logout) +router.route("/").get(limiter,isAuthenticated,landingPage) +router.route("/register").get(limiter,registerGet) +router.route("/register").post(limiter,csrfProtection,registerPost) +router.route("/profile").get(limiter,isAuthenticated,profileGet) +router.route("/profile").post(limiter,isAuthenticated,csrfProtection,profilePost) +router.route("/search-course").post(limiter,isAuthenticated,searchCourse) + + +module.exports=router; \ No newline at end of file diff --git a/src/utils/limiter.js b/src/utils/limiter.js new file mode 100644 index 0000000..6a636f3 --- /dev/null +++ b/src/utils/limiter.js @@ -0,0 +1,10 @@ +const rateLimit = require("express-rate-limit"); + + +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, // 5 requests per windowMs + message: "Too many requests from this IP, please try again later.", + }); + +module.exports=limiter \ No newline at end of file