907 lines
33 KiB
JavaScript
907 lines
33 KiB
JavaScript
const express = require("express");
|
|
const session = require("express-session");
|
|
const mysql2 = require('mysql2');
|
|
const bodyParser = require("body-parser");
|
|
const bcrypt = require("bcrypt");
|
|
const crypto = require("crypto");
|
|
const nodemailer = require("nodemailer");
|
|
const otpGenerator = require('otp-generator');
|
|
const { body, validationResult } = require('express-validator');
|
|
const validator = require('validator');
|
|
|
|
const { transporter } = require("./modules/nodeMailer");
|
|
const { connection } = require("./modules/mysql");
|
|
|
|
const app = express();
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.use(bodyParser.json());
|
|
const PORT = process.env.PORT || 3000;
|
|
require("dotenv").config();
|
|
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
|
|
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();
|
|
} else {
|
|
res.redirect("/login");
|
|
}
|
|
}
|
|
const generateOTP = () => {
|
|
const otp = otpGenerator.generate(6, { upperCase: false, specialChars: false });
|
|
const expirationTime = Date.now() + 5 * 60 * 1000; // 5 minutes expiration
|
|
return { otp, expirationTime };
|
|
};
|
|
|
|
const sendOTPByEmail = async (email, otp) => {
|
|
try {
|
|
const transporter = nodemailer.createTransport({
|
|
service: 'gmail',
|
|
auth: {
|
|
user: process.env.euser, // replace with your email
|
|
pass: process.env.epass // replace with your email password
|
|
}
|
|
});
|
|
|
|
const mailOptions = {
|
|
from: process.env.euser,
|
|
to: email,
|
|
subject: 'Login OTP',
|
|
text: `Your OTP for login is: ${otp}`
|
|
};
|
|
|
|
await transporter.sendMail(mailOptions);
|
|
console.log('OTP sent successfully to', email);
|
|
} catch (error) {
|
|
console.error('Error sending OTP:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
|
|
app.get("/login", (req, res) => {
|
|
|
|
res.render("login", { error: null });
|
|
});
|
|
|
|
const logActivity = async (username, success, message) => {
|
|
try {
|
|
if (!username) {
|
|
console.error("Error logging activity: Username is null or undefined");
|
|
return;
|
|
}
|
|
|
|
const activity = success ? `successful login: ${message}` : `unsuccessful login: ${message}`;
|
|
const logSql =
|
|
"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
|
|
const logParams = [username, activity];
|
|
|
|
connection.query(logSql, logParams, (error, results) => {
|
|
if (error) {
|
|
console.error("Error executing logSql:", error);
|
|
// Handle error (you may want to log it or take other appropriate actions)
|
|
} else {
|
|
console.log("Activity logged successfully");
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Error in logActivity function:", error);
|
|
// Handle error (you may want to log it or take other appropriate actions)
|
|
}
|
|
};
|
|
|
|
const logLogoutActivity = async (username, success, message) => {
|
|
try {
|
|
if (!username) {
|
|
console.error("Error logging activity: Username is null or undefined");
|
|
return;
|
|
}
|
|
|
|
const activity = success ? `successful logout: ${message}` : `unsuccessful logout: ${message}`;
|
|
const logSql =
|
|
"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
|
|
const logParams = [username, activity];
|
|
|
|
connection.query(logSql, logParams, (error, results) => {
|
|
if (error) {
|
|
console.error("Error executing logSql:", error);
|
|
// Handle error (you may want to log it or take other appropriate actions)
|
|
} else {
|
|
console.log("Activity logged successfully");
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Error in logActivity function:", error);
|
|
// Handle error (you may want to log it or take other appropriate actions)
|
|
}
|
|
};
|
|
|
|
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.', 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'),
|
|
],
|
|
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('otp', { error: 'Invalid OTP. Please try again.', username: req.body.username, csrfToken: req.session.csrfToken });
|
|
}
|
|
|
|
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
|
|
if (req.body.username) {
|
|
await logActivity(req.body.username, true, "OTP entered correctly");
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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}`);
|
|
// Set CSRF token as a cookie
|
|
|
|
// 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
|
|
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");
|
|
}
|
|
});
|
|
|
|
function setCSRFToken(req, res, next) {
|
|
res.locals.csrfToken = req.session.csrfToken;
|
|
next();
|
|
}
|
|
|
|
app.use(setCSRFToken);
|
|
|
|
app.get("/logout", (req, res) => {
|
|
try {
|
|
const username = req.session.username || "Unknown User";
|
|
// Log the user out by clearing the session
|
|
req.session.destroy(async (err) => {
|
|
if (err) {
|
|
console.error("Error destroying session:", err);
|
|
await logLogoutActivity(username, false, "User logged out unsucessfully. Session not destroyed.");
|
|
} else {
|
|
|
|
console.log(`Session destroyed.`);
|
|
|
|
// Log the logout activity using a separate async function
|
|
await logLogoutActivity(username, true, "User logged out. Session destroyed.");
|
|
}
|
|
// Redirect to the login page after logout
|
|
res.redirect("/login");
|
|
});
|
|
} catch (error) {
|
|
console.error("Error in logout route:", error);
|
|
res.status(500).send("Internal Server Error");
|
|
}
|
|
});
|
|
|
|
|
|
app.get("/home", isAuthenticated, (req, res) => {
|
|
// Render the home page with sensor data
|
|
res.render("home", {
|
|
username: req.session.username,
|
|
});
|
|
});
|
|
|
|
|
|
|
|
app.get("/inusers", isAuthenticated, (req, res) => {
|
|
// Fetch all user data from the database
|
|
const allUsersQuery = "SELECT * FROM users";
|
|
|
|
connection.query(allUsersQuery, (error, allUsers) => {
|
|
if (error) {
|
|
console.error("Error fetching all users:", error);
|
|
res.status(500).send("Internal Server Error");
|
|
return;
|
|
}
|
|
|
|
// Render the inusers page with JSON data
|
|
res.render("inusers", { allUsers ,csrfToken: req.session.csrfToken });
|
|
});
|
|
});
|
|
function isStrongPassword(password) {
|
|
// Password must be at least 10 characters long
|
|
if (password.length < 10) {
|
|
return false;
|
|
}
|
|
|
|
// Password must contain at least one uppercase letter
|
|
if (!/[A-Z]/.test(password)) {
|
|
return false;
|
|
}
|
|
|
|
// Password must contain at least one lowercase letter
|
|
if (!/[a-z]/.test(password)) {
|
|
return false;
|
|
}
|
|
|
|
// Password must contain at least one digit
|
|
if (!/\d/.test(password)) {
|
|
return false;
|
|
}
|
|
|
|
// Password must contain at least one symbol
|
|
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const logUserCreationActivity = async (creatorUsername, success, message) => {
|
|
try {
|
|
const activity = success
|
|
? "successful user creation"
|
|
: `unsuccessful user creation: ${message}`;
|
|
const logSql =
|
|
"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
|
|
const logParams = [creatorUsername, activity];
|
|
|
|
connection.query(logSql, logParams, (error, results) => {
|
|
if (error) {
|
|
console.error("Error logging user creation activity:", error);
|
|
// Handle error (you may want to log it or take other appropriate actions)
|
|
} else {
|
|
console.log("User creation activity logged successfully");
|
|
}
|
|
|
|
});
|
|
} catch (error) {
|
|
console.error("Error in logUserCreationActivity function:", error);
|
|
// Handle error (you may want to log it or take other appropriate actions)
|
|
}
|
|
};
|
|
|
|
app.post(
|
|
'/createUser',
|
|
[
|
|
body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(),
|
|
body('username').trim().isLength({ min: 1 }).withMessage('Username must not be empty').escape(),
|
|
body('email').isEmail().withMessage('Invalid email address').normalizeEmail(),
|
|
body('password').custom((value) => {
|
|
if (!isStrongPassword(value)) { throw new Error('Password does not meet complexity requirements'); } return true;
|
|
}),
|
|
body('jobTitle').trim().isLength({ min: 1 }).withMessage('Job title must not be empty').escape(),
|
|
],
|
|
async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
// Validate the anti-CSRF token
|
|
const submittedCSRFToken = req.body.csrf_token;
|
|
|
|
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
|
|
return res.status(403).json({ error: 'CSRF token mismatch' });
|
|
}
|
|
|
|
// Extract user input
|
|
const { name, username, email, password, jobTitle } = req.body;
|
|
|
|
// Extract the username of the user creating a new user
|
|
const creatorUsername = req.session.username; // Adjust this based on how you store the creator's username in your session
|
|
|
|
// Additional password complexity check
|
|
if (!isStrongPassword(password)) {
|
|
return res.status(400).json({ error: "Password does not meet complexity requirements" });
|
|
}
|
|
|
|
// Check if the username is already taken
|
|
const checkUsernameQuery = "SELECT * FROM users WHERE username = ?";
|
|
connection.query(checkUsernameQuery, [username], (usernameQueryErr, usernameResults) => {
|
|
if (usernameQueryErr) {
|
|
console.error("Error checking username:", usernameQueryErr);
|
|
return res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
|
|
if (usernameResults.length > 0) {
|
|
// Log unsuccessful user creation due to username taken
|
|
logUserCreationActivity(creatorUsername, false, "username taken");
|
|
return res.status(400).json({
|
|
error: "Username is already taken",
|
|
message: "Username is already taken. Please choose a different username."
|
|
});
|
|
}
|
|
|
|
// Check if the email is already taken
|
|
const checkEmailQuery = "SELECT * FROM users WHERE email = ?";
|
|
connection.query(checkEmailQuery, [email], (emailQueryErr, emailResults) => {
|
|
if (emailQueryErr) {
|
|
console.error("Error checking email:", emailQueryErr);
|
|
return res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
|
|
if (emailResults.length > 0) {
|
|
// Log unsuccessful user creation due to email taken
|
|
logUserCreationActivity(creatorUsername, false, "email taken");
|
|
return res.status(400).json({
|
|
error: "Email is already in use",
|
|
message: "Email is already in use. Please choose another email."
|
|
});
|
|
}
|
|
|
|
// Hash the password before storing it in the database
|
|
bcrypt.hash(password, 10, (hashError, hashedPassword) => {
|
|
if (hashError) {
|
|
console.error("Error hashing password:", hashError);
|
|
return res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
|
|
// Start a transaction
|
|
connection.beginTransaction((transactionErr) => {
|
|
if (transactionErr) {
|
|
console.error("Error starting transaction:", transactionErr);
|
|
return res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
|
|
// Define the insert query
|
|
const insertUserQuery =
|
|
"INSERT INTO users (name, username, email, password, lastLogin, jobTitle) VALUES (?, ?, ?, ?, NULL, ?)";
|
|
|
|
// Log the query and its parameters
|
|
console.log("Insert Query:", insertUserQuery);
|
|
console.log("Query Parameters:", [name, username, email, hashedPassword, jobTitle]);
|
|
|
|
// Execute the query with user data
|
|
connection.query(insertUserQuery, [name, username, email, hashedPassword, jobTitle], (queryErr, results) => {
|
|
if (queryErr) {
|
|
console.error("Error executing query:", queryErr);
|
|
|
|
// Rollback the transaction in case of an error
|
|
connection.rollback((rollbackErr) => {
|
|
if (rollbackErr) {
|
|
console.error("Error rolling back transaction:", rollbackErr);
|
|
}
|
|
// Log unsuccessful user creation due to an error
|
|
logUserCreationActivity(creatorUsername, false, "internal error");
|
|
return res.status(500).json({ error: "Internal Server Error" });
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Commit the transaction
|
|
connection.commit((commitErr) => {
|
|
if (commitErr) {
|
|
console.error("Error committing transaction:", commitErr);
|
|
// Log unsuccessful user creation due to an error
|
|
logUserCreationActivity(creatorUsername, false, "internal error");
|
|
return res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
|
|
// Log successful user creation
|
|
logUserCreationActivity(creatorUsername, true, "user created successfully");
|
|
|
|
// Redirect to "/inusers"
|
|
res.redirect('/inusers');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error("Error creating user:", error);
|
|
// Log unsuccessful user creation due to an error
|
|
logUserCreationActivity(req.session.username, false, "internal error"); // Adjust this based on how you store the creator's username in your session
|
|
res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
|
|
app.get("/forgot-password", (req, res) => {
|
|
res.render("forgot-password", { error: null, success: null });
|
|
});
|
|
|
|
// Handle the submission of the forgot password form
|
|
app.post("/forgot-password", (req, res) => {
|
|
const { usernameOrEmail } = req.body;
|
|
|
|
// Sanitize the input
|
|
const sanitizedUsernameOrEmail = validator.escape(usernameOrEmail);
|
|
|
|
// Perform the logic for sending the reset password email
|
|
|
|
// Check if the username or email exists in the database
|
|
const checkUserQuery = "SELECT * FROM users WHERE username = ? OR email = ?";
|
|
connection.query(
|
|
checkUserQuery,
|
|
[sanitizedUsernameOrEmail, sanitizedUsernameOrEmail],
|
|
(checkError, checkResults) => {
|
|
if (checkError) {
|
|
console.error("Error checking user:", checkError);
|
|
const error = "An error occurred during the password reset process.";
|
|
res.render("forgot-password", { error, success: null });
|
|
} else if (checkResults.length === 0) {
|
|
const error = "Username or email not found.";
|
|
res.render("forgot-password", { error, success: null });
|
|
} else {
|
|
// Assuming the user exists, generate a reset token and send an email
|
|
const user = checkResults[0];
|
|
const resetToken = crypto.randomBytes(20).toString("hex");
|
|
const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour
|
|
|
|
// Update user with reset token and expiry
|
|
const updateQuery =
|
|
"UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?";
|
|
connection.query(
|
|
updateQuery,
|
|
[resetToken, resetTokenExpiry, user.id],
|
|
(updateError) => {
|
|
if (updateError) {
|
|
console.error("Error updating reset token:", updateError);
|
|
const error =
|
|
"An error occurred during the password reset process.";
|
|
res.render("forgot-password", { error, success: null });
|
|
} else {
|
|
// Send email with reset link
|
|
const resetLink = `http://localhost:3000/reset-password/${resetToken}`;
|
|
const mailOptions = {
|
|
to: user.email,
|
|
subject: "Password Reset",
|
|
text: `Click on the following link to reset your password: ${resetLink}`,
|
|
};
|
|
transporter.sendMail(mailOptions, (emailError, info) => {
|
|
if (emailError) {
|
|
console.error("Error sending email:", emailError);
|
|
const error =
|
|
"An error occurred during the password reset process.";
|
|
res.render("forgot-password", { error, success: null });
|
|
} else {
|
|
console.log("Email sent: " + info.response);
|
|
const success =
|
|
"Password reset email sent successfully. Check your inbox.";
|
|
res.render("forgot-password", { error: null, success });
|
|
|
|
// Log the successful sending of the reset link in the database
|
|
logPasswordResetActivity(user.username,"link sent successfully");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
// Function to log the password reset activity in the database
|
|
function logPasswordResetActivity(username, activity) {
|
|
const logSql =
|
|
"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
|
|
connection.query(logSql, [username, activity], (error) => {
|
|
if (error) {
|
|
console.error("Error logging password reset activity:", error);
|
|
} else {
|
|
console.log("Password reset activity logged successfully");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle Reset Password request
|
|
app.post("/reset-password/:token", async (req, res) => {
|
|
const { token } = req.params;
|
|
const { password, confirmPassword } = req.body;
|
|
|
|
// Sanitize the inputs
|
|
const sanitizedToken = validator.escape(token);
|
|
const sanitizedPassword = validator.escape(password);
|
|
const sanitizedConfirmPassword = validator.escape(confirmPassword);
|
|
|
|
// Find user with matching reset token and not expired
|
|
const selectQuery =
|
|
"SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()";
|
|
connection.query(selectQuery, [sanitizedToken], async (selectErr, selectResults) => {
|
|
if (selectErr) {
|
|
console.error("Error querying reset token:", selectErr);
|
|
return res.status(500).json({ error: "Error querying reset token" });
|
|
}
|
|
|
|
if (selectResults.length === 0) {
|
|
// Pass the error to the template when rendering the reset-password page
|
|
return res.render("reset-password", {
|
|
token,
|
|
resetError: "Invalid or expired reset token",
|
|
});
|
|
}
|
|
|
|
// Check if passwords match
|
|
if (sanitizedPassword !== sanitizedConfirmPassword) {
|
|
// Pass the error to the template when rendering the reset-password page
|
|
return res.render("reset-password", {
|
|
token,
|
|
resetError: "Passwords do not match",
|
|
});
|
|
}
|
|
|
|
// Check if the new password meets complexity requirements
|
|
if (!isStrongPassword(sanitizedPassword)) {
|
|
// Pass the error to the template when rendering the reset-password page
|
|
return res.render("reset-password", {
|
|
token,
|
|
resetError:
|
|
"Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.",
|
|
});
|
|
}
|
|
|
|
// Hash the new password
|
|
const hashedPassword = await bcrypt.hash(sanitizedPassword, 10);
|
|
|
|
// Update user's password and clear reset token
|
|
const updateQuery =
|
|
"UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE reset_token = ?";
|
|
connection.query(updateQuery, [hashedPassword, sanitizedToken], async (updateErr, updateResults) => {
|
|
if (updateErr) {
|
|
console.error("Error updating password:", updateErr);
|
|
// Pass the error to the template when rendering the reset-password page
|
|
res.render("reset-password", {
|
|
token,
|
|
resetError: "Error updating password",
|
|
});
|
|
} else {
|
|
// Log password reset activity
|
|
const username = selectResults[0].username; // Assuming 'username' is the column name
|
|
const logQuery = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, 'Password Reseted successfully', NOW())";
|
|
connection.query(logQuery, [username], (logErr) => {
|
|
if (logErr) {
|
|
console.error("Error logging password reset:", logErr);
|
|
// You might want to handle the logging error here
|
|
}
|
|
});
|
|
// Redirect to the success page upon successful password reset
|
|
res.redirect("/success");
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
app.get("/success", (req, res) => {
|
|
res.render("success");
|
|
});
|
|
|
|
app.get("/reset-password/:token", (req, res) => {
|
|
const { token } = req.params;
|
|
const error = req.query.error || null; // Get error from query parameter
|
|
// Assuming you have this line in your server code where you render the reset-password view
|
|
res.render("reset-password", {
|
|
token,
|
|
passwordValidationError: null,
|
|
resetError: null,
|
|
success: null,
|
|
});
|
|
});
|
|
app.post("/reset-password", async (req, res) => {
|
|
|
|
const { username, password, confirmPassword, csrf_token } = req.body;
|
|
const creatorUsername = req.session.username;
|
|
const submittedCSRFToken = req.body.csrf_token;
|
|
|
|
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
|
|
return res.status(403).json({ error: 'CSRF token mismatch' });
|
|
}
|
|
|
|
// Sanitize the inputs
|
|
const sanitizedUsername = validator.escape(username);
|
|
const sanitizedPassword = validator.escape(password);
|
|
const sanitizedConfirmPassword = validator.escape(confirmPassword);
|
|
|
|
// Check if passwords match
|
|
if (sanitizedPassword !== sanitizedConfirmPassword) {
|
|
return res.status(400).json({ error: "Passwords do not match" });
|
|
}
|
|
|
|
// Check if the new password meets complexity requirements
|
|
if (!isStrongPassword(sanitizedPassword)) {
|
|
return res.status(400).json({
|
|
error:
|
|
"Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.",
|
|
});
|
|
}
|
|
|
|
// Hash the new password
|
|
const hashedPassword = await bcrypt.hash(sanitizedPassword, 10);
|
|
|
|
// Check if the user exists in the database before updating the password
|
|
const userExists = await checkIfUserExists(sanitizedUsername);
|
|
|
|
if (!userExists) {
|
|
return res.status(404).json({ error: "User does not exist" });
|
|
}
|
|
|
|
// Update user's password based on the username
|
|
const updateQuery = "UPDATE users SET password = ? WHERE username = ?";
|
|
connection.query(updateQuery, [hashedPassword, sanitizedUsername], async (updateErr, updateResults) => {
|
|
if (updateErr) {
|
|
console.error("Error updating password:", updateErr);
|
|
return res.status(500).json({ error: "Error updating password" });
|
|
}
|
|
|
|
// Check if the update affected any rows
|
|
if (updateResults.affectedRows > 0) {
|
|
// Log password reset activity
|
|
const logQuery = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, NOW())";
|
|
const logActivity = `Password has been reset for ${sanitizedUsername}`;
|
|
connection.query(logQuery, [creatorUsername, logActivity], (logErr) => {
|
|
if (logErr) {
|
|
console.error("Error logging password reset:", logErr);
|
|
// You might want to handle the logging error here
|
|
}
|
|
|
|
// Password update successful
|
|
return res.status(200).json({ success: "Password updated successfully" });
|
|
});
|
|
} else {
|
|
return res.status(404).json({
|
|
error: "User not found or password not updated.",
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
async function checkIfUserExists(username) {
|
|
return new Promise((resolve, reject) => {
|
|
const query = "SELECT * FROM users WHERE username = ?";
|
|
connection.query(query, [username], (err, results) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(results.length > 0);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
app.get('/searchUser', (req, res) => {
|
|
const { username } = req.query;
|
|
|
|
// Sanitize the input
|
|
const sanitizedUsername = validator.escape(username);
|
|
|
|
const query = 'SELECT * FROM users WHERE username = ?';
|
|
|
|
connection.query(query, [sanitizedUsername], (err, results) => {
|
|
if (err) {
|
|
console.error('MySQL query error:', err);
|
|
res.status(500).json({ success: false, error: 'Internal Server Error' });
|
|
} else if (results.length === 0) {
|
|
// No user found with the given username
|
|
res.status(404).json({ success: false, error: 'User not found' });
|
|
} else {
|
|
res.json(results);
|
|
}
|
|
});
|
|
});
|
|
|
|
app.get('/api/users', (req, res) => {
|
|
const query = 'SELECT * FROM users';
|
|
|
|
connection.query(query, (err, results) => {
|
|
if (err) {
|
|
console.error('MySQL query error:', err);
|
|
res.status(500).json({ success: false, error: 'Internal Server Error' });
|
|
} else {
|
|
res.json(results);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Route to search for a user by username
|
|
app.get('/api/searchUser', (req, res) => {
|
|
const { username } = req.query;
|
|
const query = 'SELECT * FROM users WHERE username = ?';
|
|
|
|
connection.query(query, [username], (err, results) => {
|
|
if (err) {
|
|
console.error('MySQL query error:', err);
|
|
res.status(500).json({ success: false, error: 'Internal Server Error' });
|
|
} else {
|
|
res.json(results);
|
|
}
|
|
});
|
|
});
|
|
|
|
app.delete('/api/deleteUser/:username', async (req, res) => {
|
|
const { username } = req.params;
|
|
const query = 'DELETE FROM users WHERE username = ?';
|
|
const creatorUsername = req.session.username;
|
|
|
|
try {
|
|
// Extract CSRF token from the request body
|
|
const { csrfToken } = req.body;
|
|
|
|
// Compare CSRF token with the one stored in the session
|
|
if (csrfToken !== req.session.csrfToken) {
|
|
return res.status(403).json({ success: false, error: 'CSRF token mismatch' });
|
|
}
|
|
|
|
// Log deletion activity to USER_LOGS
|
|
const deletionActivity = `User ${username} has been successfully deleted`;
|
|
const logQuery = 'INSERT INTO USER_LOGS (USERNAME, ACTIVITY, TIMESTAMP) VALUES (?, ?, CURRENT_TIMESTAMP)';
|
|
await executeQuery(logQuery, [creatorUsername, deletionActivity]);
|
|
|
|
// Perform user deletion
|
|
const results = await executeQuery(query, [username]);
|
|
|
|
if (results.affectedRows === 0) {
|
|
res.status(404).json({ success: false, error: 'User not found' });
|
|
} else {
|
|
res.json({ success: true, message: 'User deleted successfully' });
|
|
}
|
|
} catch (error) {
|
|
console.error('MySQL query error:', error);
|
|
res.status(500).json({ success: false, error: 'Internal Server Error', details: error.message });
|
|
}
|
|
});
|
|
|
|
|
|
async function executeQuery(sql, values) {
|
|
return new Promise((resolve, reject) => {
|
|
connection.query(sql, values, (err, results) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(results);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
app.get('/api/getLogs', (req, res) => {
|
|
// Query the database to fetch logs
|
|
const query = 'SELECT id, username, activity, timestamp FROM user_logs';
|
|
connection.query(query, (err, results) => {
|
|
if (err) {
|
|
console.error('Error fetching logs from MySQL:', err);
|
|
res.status(500).json({ error: 'Error fetching logs from MySQL' });
|
|
return;
|
|
}
|
|
|
|
// Format timestamps to a more readable format
|
|
const formattedLogs = results.map((log) => ({
|
|
id: log.id,
|
|
username: log.username,
|
|
activity: log.activity,
|
|
timestamp: new Date(log.timestamp).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }),
|
|
}));
|
|
|
|
// Send the formatted logs as a JSON response
|
|
res.json(formattedLogs);
|
|
});
|
|
});
|
|
|
|
|
|
app.use(express.static("views"));
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server is running on port ${PORT}`);
|
|
});
|