diff --git a/Sean/server.js b/Sean/server.js index 8dd5cce..46eb2e6 100644 --- a/Sean/server.js +++ b/Sean/server.js @@ -19,32 +19,19 @@ const PORT = process.env.PORT || 3000; require("dotenv").config(); app.use(bodyParser.urlencoded({ extended: true })); -app.use( - session({ - secret: process.env.key, - resave: false, - saveUninitialized: true, - cookie: { - secure: false, // Make sure to set this to true in a production environment with HTTPS - httpOnly: true, - maxAge: 24 * 60 * 60 * 1000, // Session duration in milliseconds (here set to 1 day) - }, - }) -); - -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"); +app.use(session({ + secret: process.env.key, + resave: false, + saveUninitialized: true, + cookie: { + secure: false, // Make sure to set this to true in a production environment with HTTPS + httpOnly: true, + maxAge: 24 * 60 * 60 * 1000, // Session duration in milliseconds (here set to 1 day) + }, +})); function isAuthenticated(req, res, next) { if (req.session && req.session.authenticated) { return next(); @@ -144,17 +131,11 @@ const logActivity = async (username, success, message) => { 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 }); @@ -222,17 +203,11 @@ async (req, res) => { // 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 }); @@ -240,10 +215,16 @@ async (req, res) => { const enteredOTP = req.body.otp; + if (!req.session) { + // If session is not defined, handle accordingly + console.error("Session is not defined."); + return res.status(500).send("Internal Server Error"); + } + if (enteredOTP === req.session.otp) { - // Log successful OTP entry and login + // Log successful OTP entry if (req.body.username) { - await logActivity(req.body.username, true, "OTP entered correctly. Successful login"); + await logActivity(req.body.username, true, "OTP entered correctly"); } // Correct OTP, generate a session token @@ -253,8 +234,22 @@ async (req, res) => { 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}`); + + // Generate and store anti-CSRF token in the session + req.session.csrfToken = crypto.randomBytes(32).toString('hex'); + + // Set anti-CSRF token in res.locals + res.locals.csrfToken = req.session.csrfToken; + + // Log anti-CSRF token + console.log(`Generated Anti-CSRF Token: ${req.session.csrfToken}`); + + // Implement secure session handling: + // 1. Set secure, HttpOnly, and SameSite flags + // 2. Set an expiration time for the session token + // 3. Regenerate the session after authentication + res.cookie('sessionToken', sessionToken, { secure: true, httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000) }); // Expires in 1 day + console.log(`Generated Session Token: ${sessionToken}`); // Redirect to home page with session token @@ -273,7 +268,9 @@ async (req, res) => { res.status(500).send("Internal Server Error"); } }); - + + + app.get("/logout", (req, res) => { try { const username = req.session.username || "Unknown User"; diff --git a/Sean/views/inusers.ejs b/Sean/views/inusers.ejs index 6109a16..05a6c24 100644 --- a/Sean/views/inusers.ejs +++ b/Sean/views/inusers.ejs @@ -146,6 +146,7 @@ diff --git a/Sean/views/inusers.js b/Sean/views/inusers.js index 32caea6..a92b225 100644 --- a/Sean/views/inusers.js +++ b/Sean/views/inusers.js @@ -1,6 +1,5 @@ - $(document).ready(function () { $('#resetPasswordLink').on('click', function () { $('#resetPasswordFormContainer').show(); @@ -429,5 +428,7 @@ $('#resetPasswordForm').on('submit', function (e) { }); }); + + diff --git a/Sean/views/login.ejs b/Sean/views/login.ejs index 2c4428f..ec63272 100644 --- a/Sean/views/login.ejs +++ b/Sean/views/login.ejs @@ -86,7 +86,6 @@ button:hover { - diff --git a/Sean/views/otp.ejs b/Sean/views/otp.ejs index 64df4ef..1966179 100644 --- a/Sean/views/otp.ejs +++ b/Sean/views/otp.ejs @@ -69,7 +69,7 @@
- + diff --git a/package-lock.json b/package-lock.json index 86da709..9a453a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,16 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" + }, + "dependencies": { + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "@otplib/core": { @@ -386,6 +396,11 @@ } } }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -530,6 +545,11 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -662,6 +682,15 @@ "validator": "^13.9.0" } }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -718,6 +747,14 @@ "path-exists": "^4.0.0" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1143,12 +1180,19 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "requires": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "nodemailer": { @@ -1668,6 +1712,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "web-streams-polyfill": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", + "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==" + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index b302596..c5e4907 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "csurf": "^1.11.0", "dotenv": "^16.3.1", "ejs": "^3.1.9", + "esm": "^3.2.25", "express": "^4.18.2", "express-session": "^1.17.3", "express-validator": "^7.0.1", @@ -30,6 +31,7 @@ "moment": "^2.30.1", "mqtt": "^5.3.3", "mysql2": "^3.6.5", + "node-fetch": "^3.3.2", "nodemailer": "^6.9.7", "otp-generator": "^4.0.1", "otplib": "^12.0.1",