commit
f91e99330a
@ -19,32 +19,19 @@ const PORT = process.env.PORT || 3000;
|
|||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
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.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) {
|
function isAuthenticated(req, res, next) {
|
||||||
if (req.session && req.session.authenticated) {
|
if (req.session && req.session.authenticated) {
|
||||||
return next();
|
return next();
|
||||||
@ -144,17 +131,11 @@ const logActivity = async (username, success, message) => {
|
|||||||
app.post('/login', [
|
app.post('/login', [
|
||||||
body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'),
|
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('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) => {
|
async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const errors = validationResult(req);
|
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()) {
|
if (!errors.isEmpty()) {
|
||||||
// Handle validation errors, e.g., return an error message to the client
|
// 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 });
|
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
|
// OTP verification route
|
||||||
app.post("/verify-otp", [
|
app.post("/verify-otp", [
|
||||||
body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'),
|
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) => {
|
async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const errors = validationResult(req);
|
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()) {
|
if (!errors.isEmpty()) {
|
||||||
// Handle validation errors, e.g., return an error message to the client
|
// 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 });
|
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;
|
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) {
|
if (enteredOTP === req.session.otp) {
|
||||||
// Log successful OTP entry and login
|
// Log successful OTP entry
|
||||||
if (req.body.username) {
|
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
|
// Correct OTP, generate a session token
|
||||||
@ -253,8 +234,22 @@ async (req, res) => {
|
|||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
req.session.username = req.body.username;
|
req.session.username = req.body.username;
|
||||||
req.session.sessionToken = sessionToken;
|
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}`);
|
console.log(`Generated Session Token: ${sessionToken}`);
|
||||||
|
|
||||||
// Redirect to home page with session token
|
// Redirect to home page with session token
|
||||||
@ -274,6 +269,8 @@ async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.get("/logout", (req, res) => {
|
app.get("/logout", (req, res) => {
|
||||||
try {
|
try {
|
||||||
const username = req.session.username || "Unknown User";
|
const username = req.session.username || "Unknown User";
|
||||||
|
@ -146,6 +146,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
const allUsers = <%- JSON.stringify(allUsers) %>;
|
const allUsers = <%- JSON.stringify(allUsers) %>;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('#resetPasswordLink').on('click', function () {
|
$('#resetPasswordLink').on('click', function () {
|
||||||
$('#resetPasswordFormContainer').show();
|
$('#resetPasswordFormContainer').show();
|
||||||
@ -431,3 +430,5 @@ $('#resetPasswordForm').on('submit', function (e) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,7 +86,6 @@ button:hover {
|
|||||||
|
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input type="password" id="password" name="password" placeholder="Enter your password" required>
|
<input type="password" id="password" name="password" placeholder="Enter your password" required>
|
||||||
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
|
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
<label for="otp">OTP:</label>
|
<label for="otp">OTP:</label>
|
||||||
<input type="text" id="otp" name="otp" required>
|
<input type="text" id="otp" name="otp" required>
|
||||||
<br>
|
<br>
|
||||||
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
|
|
||||||
<button type="submit">Submit OTP</button>
|
<button type="submit">Submit OTP</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
57
package-lock.json
generated
57
package-lock.json
generated
@ -18,6 +18,16 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"tar": "^6.1.11"
|
"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": {
|
"@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": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||||
},
|
},
|
||||||
|
"esm": {
|
||||||
|
"version": "3.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||||
|
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
|
||||||
|
},
|
||||||
"etag": {
|
"etag": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
@ -662,6 +682,15 @@
|
|||||||
"validator": "^13.9.0"
|
"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": {
|
"filelist": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||||
@ -718,6 +747,14 @@
|
|||||||
"path-exists": "^4.0.0"
|
"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": {
|
"forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
"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": {
|
"node-fetch": {
|
||||||
"version": "2.7.0",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"whatwg-url": "^5.0.0"
|
"data-uri-to-buffer": "^4.0.0",
|
||||||
|
"fetch-blob": "^3.1.4",
|
||||||
|
"formdata-polyfill": "^4.0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nodemailer": {
|
"nodemailer": {
|
||||||
@ -1668,6 +1712,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
|
"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": {
|
"webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"ejs": "^3.1.9",
|
"ejs": "^3.1.9",
|
||||||
|
"esm": "^3.2.25",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-rate-limit": "^7.1.5",
|
"express-rate-limit": "^7.1.5",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
@ -31,6 +32,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mqtt": "^5.3.3",
|
"mqtt": "^5.3.3",
|
||||||
"mysql2": "^3.6.5",
|
"mysql2": "^3.6.5",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"otp-generator": "^4.0.1",
|
"otp-generator": "^4.0.1",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user