From 9dcf1429a8628e35f8bb6876ac49b564c494039c Mon Sep 17 00:00:00 2001 From: BIG2EYEZ Date: Fri, 12 Jan 2024 16:58:47 +0800 Subject: [PATCH] anti csrf token generated upon login --- Sean/server.js | 253 +++++++++++++++++++++++------------------ Sean/views/inusers.ejs | 1 + Sean/views/login.ejs | 3 +- Sean/views/otp.ejs | 1 + package-lock.json | 86 ++++++++++++++ package.json | 2 + 6 files changed, 236 insertions(+), 110 deletions(-) diff --git a/Sean/server.js b/Sean/server.js index 241d115..8dd5cce 100644 --- a/Sean/server.js +++ b/Sean/server.js @@ -31,6 +31,18 @@ app.use( }, }) ); + +app.use((req, res, next) => { + if (!req.session.csrfToken) { + req.session.csrfToken = crypto.randomBytes(32).toString('hex'); + } + + // Make the CSRF token available in the response context + res.locals.csrfToken = req.session.csrfToken; + console.log(`Server-side CSRF Token: ${req.session.csrfToken}`); + next(); +}); + app.set("view engine", "ejs"); function isAuthenticated(req, res, next) { @@ -129,115 +141,138 @@ const logActivity = async (username, success, message) => { } }; - - // Login route - app.post('/login',[ - body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'), - body('password').escape().trim().isLength({ min: 1 }).withMessage('Password must not be empty'), - ], - async (req, res) => { - try { - const errors = validationResult(req); - - if (!errors.isEmpty()) { - // Handle validation errors, e.g., return an error message to the client - return res.render('login', { error: 'Invalid input. Please check your credentials.' }); - } - - let { username, password } = req.body; - username = username.trim(); - - const loginSql = "SELECT * FROM users WHERE username = ?"; - - connection.query(loginSql, [username], async (error, results) => { - if (error) { - console.error("Error executing login query:", error); - res.status(500).send("Internal Server Error"); - return; - } - - if (results.length > 0) { - const isLoginSuccessful = await bcrypt.compare(password, results[0].password); - - if (isLoginSuccessful) { - // Log successful login attempt - await logActivity(username, true, "Credentials entered correctly"); - - const user = results[0]; - const { otp, expirationTime } = generateOTP(); - - // Store the OTP and expiration time in the session for verification - req.session.otp = otp; - req.session.otpExpiration = expirationTime; - req.session.save(); - - // Send OTP via email - try { - await sendOTPByEmail(user.email, otp); - // Log successful OTP sending - await logActivity(username, true, "OTP successfully sent to user"); - } catch (sendOTPError) { - // Log unsuccessful OTP sending - await logActivity(username, false, "OTP failed to send to user"); - console.error("Error sending OTP:", sendOTPError); - res.status(500).send("Internal Server Error"); - return; - } - - // Render OTP input page - res.render("otp", { error: null, username: user.username }); - } else { - // Log unsuccessful login attempt - await logActivity(username, false, "Incorrect password"); - res.render("login", { error: "Invalid username or password" }); - } - } else { - // Log unsuccessful login attempt - await logActivity(username, false, "User not found"); - res.render("login", { error: "Invalid username or password" }); - } - }); - } catch (error) { - console.error("Error in login route:", error); - res.status(500).send("Internal Server Error"); - } - } - ); - app.post("/verify-otp", async (req, res) => { - try { - const enteredOTP = req.body.otp; - - if (enteredOTP === req.session.otp) { - // Log successful OTP entry and login - if (req.body.username) { - await logActivity(req.body.username, true, "OTP entered correctly. Successful login"); - } - - // Correct OTP, generate a session token - const sessionToken = crypto.randomBytes(32).toString('hex'); - - // Store the session token in the session - req.session.authenticated = true; - req.session.username = req.body.username; - req.session.sessionToken = sessionToken; - console.log(`Generated Session Token: ${sessionToken}`); - - // Redirect to home page with session token - res.redirect("/home"); - } else { - // Log unsuccessful OTP entry - if (req.body.username) { - await logActivity(req.body.username, false, "Incorrect OTP entered"); - } - - // Incorrect OTP, render login page with error - res.render("login", { error: "Incorrect OTP. Please try again." }); - } - } catch (error) { - console.error("Error in OTP verification route:", error); - res.status(500).send("Internal Server Error"); - } - }); + app.post('/login', [ + body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'), + body('password').escape().trim().isLength({ min: 1 }).withMessage('Password must not be empty'), + body('csrf_token').escape().trim().isLength({ min: 1 }).withMessage('CSRF token must not be empty'), +], +async (req, res) => { + try { + const errors = validationResult(req); + + // Validate CSRF token + if (req.body.csrf_token !== req.session.csrfToken) { + return res.status(403).send("Invalid CSRF token"); + } + + if (!errors.isEmpty()) { + // Handle validation errors, e.g., return an error message to the client + return res.render('login', { error: 'Invalid input. Please check your credentials.', csrfToken: req.session.csrfToken }); + } + + let { username, password } = req.body; + username = username.trim(); + + const loginSql = "SELECT * FROM users WHERE username = ?"; + + connection.query(loginSql, [username], async (error, results) => { + if (error) { + console.error("Error executing login query:", error); + res.status(500).send("Internal Server Error"); + return; + } + + if (results.length > 0) { + const isLoginSuccessful = await bcrypt.compare(password, results[0].password); + + if (isLoginSuccessful) { + // Log successful login attempt + await logActivity(username, true, "Credentials entered correctly"); + + const user = results[0]; + const { otp, expirationTime } = generateOTP(); + + // Store the OTP and expiration time in the session for verification + req.session.otp = otp; + req.session.otpExpiration = expirationTime; + req.session.save(); + + // Send OTP via email + try { + await sendOTPByEmail(user.email, otp); + // Log successful OTP sending + await logActivity(username, true, "OTP successfully sent to user"); + } catch (sendOTPError) { + // Log unsuccessful OTP sending + await logActivity(username, false, "OTP failed to send to user"); + console.error("Error sending OTP:", sendOTPError); + res.status(500).send("Internal Server Error"); + return; + } + + // Render OTP input page + res.render("otp", { error: null, username: user.username, csrfToken: req.session.csrfToken }); + } else { + // Log unsuccessful login attempt + await logActivity(username, false, "Incorrect password"); + res.render("login", { error: "Invalid username or password", csrfToken: req.session.csrfToken }); + } + } else { + // Log unsuccessful login attempt + await logActivity(username, false, "User not found"); + res.render("login", { error: "Invalid username or password", csrfToken: req.session.csrfToken }); + } + }); + } catch (error) { + console.error("Error in login route:", error); + res.status(500).send("Internal Server Error"); + } +}); + +// OTP verification route +app.post("/verify-otp", [ + body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'), + body('csrf_token').escape().trim().isLength({ min: 1 }).withMessage('CSRF token must not be empty'), +], +async (req, res) => { + try { + const errors = validationResult(req); + + // Validate CSRF token + if (req.body.csrf_token !== req.session.csrfToken) { + return res.status(403).send("Invalid CSRF token"); + } + + if (!errors.isEmpty()) { + // Handle validation errors, e.g., return an error message to the client + return res.render('otp', { error: 'Invalid OTP. Please try again.', username: req.body.username, csrfToken: req.session.csrfToken }); + } + + const enteredOTP = req.body.otp; + + if (enteredOTP === req.session.otp) { + // Log successful OTP entry and login + if (req.body.username) { + await logActivity(req.body.username, true, "OTP entered correctly. Successful login"); + } + + // Correct OTP, generate a session token + const sessionToken = crypto.randomBytes(32).toString('hex'); + + // Store the session token in the session + req.session.authenticated = true; + req.session.username = req.body.username; + req.session.sessionToken = sessionToken; + res.locals.csrfToken = req.session.csrfToken; + console.log(`Server-side CSRF Token: ${req.session.csrfToken}`); + console.log(`Generated Session Token: ${sessionToken}`); + + // Redirect to home page with session token + res.redirect("/home"); + } else { + // Log unsuccessful OTP entry + if (req.body.username) { + await logActivity(req.body.username, false, "Incorrect OTP entered"); + } + + // Incorrect OTP, render login page with error + res.render("login", { error: "Incorrect OTP. Please try again.", csrfToken: req.session.csrfToken }); + } + } catch (error) { + console.error("Error in OTP verification route:", error); + res.status(500).send("Internal Server Error"); + } +}); app.get("/logout", (req, res) => { try { diff --git a/Sean/views/inusers.ejs b/Sean/views/inusers.ejs index cec5785..6109a16 100644 --- a/Sean/views/inusers.ejs +++ b/Sean/views/inusers.ejs @@ -96,6 +96,7 @@ +
diff --git a/Sean/views/login.ejs b/Sean/views/login.ejs index 0d90b6a..2c4428f 100644 --- a/Sean/views/login.ejs +++ b/Sean/views/login.ejs @@ -86,7 +86,7 @@ button:hover { - + @@ -95,4 +95,5 @@ button:hover { + diff --git a/Sean/views/otp.ejs b/Sean/views/otp.ejs index 620e73c..64df4ef 100644 --- a/Sean/views/otp.ejs +++ b/Sean/views/otp.ejs @@ -69,6 +69,7 @@
+ diff --git a/package-lock.json b/package-lock.json index 6317c6e..86da709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -305,11 +305,87 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + } + }, + "csurf": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", + "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + } + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1288,6 +1364,11 @@ "glob": "^7.1.3" } }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1530,6 +1611,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index 0bb46cd..b302596 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "dependencies": { "bcrypt": "^5.1.1", "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", + "csurf": "^1.11.0", "dotenv": "^16.3.1", "ejs": "^3.1.9", "express": "^4.18.2",