diff --git a/README.md b/README.md
index 71c6aad..6275025 100644
--- a/README.md
+++ b/README.md
@@ -4,3 +4,12 @@ How to get started with this project?
Security in GIT repo.
i repeat DO NOT USE CREDS IN CODE! Please use .env files (https://www.npmjs.com/package/dotenv)
+
+## Workload
+1) Ti Seng
+* Webserver Microservices
+* IoT sensor
+* All Database / Backend Functions
+* consumer website api and user function
+2) Sean
+* Admin Website Microservice
diff --git a/Sean/models/User.js b/Sean/models/User.js
index ab12ee9..8cda275 100644
--- a/Sean/models/User.js
+++ b/Sean/models/User.js
@@ -1,35 +1,50 @@
-// models/User.js
const { Sequelize, DataTypes } = require('sequelize');
-const sequelize = new Sequelize(process.env.database, process.env.user, process.env.password, {
- host: process.env.host,
- dialect: 'mysql',
- timezone: 'Z', // Set the timezone to UTC
-});
+const bcrypt = require('bcrypt');
-const User = sequelize.define('User', {
- name: {
- type: DataTypes.STRING,
- allowNull: false,
- },
- username: {
- type: DataTypes.STRING,
- allowNull: false,
- unique: true,
- },
- email: {
- type: DataTypes.STRING,
- allowNull: false,
- unique: true,
- },
- password: {
- type: DataTypes.STRING,
- allowNull: false,
- },
- jobTitle: {
- type: DataTypes.STRING,
- allowNull: false,
- },
-});
+module.exports = (sequelize) => {
+ const User = sequelize.define('users', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ name: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ },
+ username: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ unique: true,
+ },
+ email: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ unique: true,
+ },
+ password: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ },
+ jobTitle: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ },
+ reset_token: {
+ type: DataTypes.STRING,
+ },
+ reset_token_expiry: {
+ type: DataTypes.DATE,
+ },
+ }, {
+ hooks: {
+ beforeCreate: async (user) => {
+ user.password = await bcrypt.hash(user.password, 10);
+ },
+ },
+ timestamps: false, // Disabling timestamps here
+ });
-module.exports = User;
+ return User;
+};
diff --git a/Sean/models/userLogs.js b/Sean/models/userLogs.js
new file mode 100644
index 0000000..672bfe6
--- /dev/null
+++ b/Sean/models/userLogs.js
@@ -0,0 +1,30 @@
+// userLogs.js
+
+const { Sequelize, DataTypes } = require('sequelize');
+
+module.exports = (sequelize) => {
+ const userLogs = sequelize.define('user_logs', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ username: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ },
+ activity: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ },
+ timestamp: {
+ type: DataTypes.DATE,
+ allowNull: false,
+ defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
+ },
+ }, {
+ timestamps: false, // Disabling timestamps
+ });
+
+ return userLogs;
+};
diff --git a/Sean/modules/mysql.js b/Sean/modules/mysql.js
index 7dbb840..cc53850 100644
--- a/Sean/modules/mysql.js
+++ b/Sean/modules/mysql.js
@@ -2,45 +2,47 @@ const mysql = require("mysql2");
const path = require("path");
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
const fs = require('fs');
-/*
-const mysqlConfig = {
- host: process.env.host,
- user: process.env.user,
- password: process.env.password,
- database: process.env.database,
- timezone: "Z", // Set the timezone to UTC
-};
+const UserModel = require('../models/User');// Adjust the path based on your project structure
+const { Sequelize } = require('sequelize');
-const connection = mysql.createConnection(mysqlConfig);
-connection.connect((err) => {
- if (err) {
- console.error("Error connecting to MySQL:", err);
- return;
- }
- console.log("Connected to MySQL");
-});
-*/
-/*
-const connection = mysql.createConnection({
- host: process.env.host,
- user: process.env.DB_USER,
- password: process.env.DB_PASS,
- database: "database",
- ssl: {
- ca: fs.readFileSync(path.resolve(__dirname, '../../cert/DigiCertGlobalRootCA.crt.pem')),
- }
- });
-*/
-const connection = mysql.createConnection({
- host: process.env.host,
- user: process.env.DB_USER,
- password: process.env.DB_PASS,
- database: "adminusers",
- timezone: "Z", // Set the timezone to UTC
- });
-
-
-module.exports = { connection };
+
+ const sequelize = new Sequelize(
+ "adminusers",
+ process.env.DB_USER,
+ process.env.DB_PASS,
+ {
+ host: "mpsqldatabasean.mysql.database.azure.com",
+ dialect: 'mysql',
+ // attributeBehavior?: 'escape' | 'throw' | 'unsafe-legacy';
+ attributeBehavior: 'escape',
+ dialectOptions: {
+ ssl: {
+ ca: fs.readFileSync(path.resolve(__dirname, '../../cert/DigiCertGlobalRootCA.crt.pem')),
+
+ },
+
+ },
+ },
+
+
+ );
+
+ sequelize.authenticate().then(() => {
+ console.log('Connection has been established successfully.');
+ }).catch((error) => {
+ console.error('Unable to connect to the database: ', error);
+ });
+
+
+ const User = UserModel(sequelize);
+
+ // Synchronize the models with the database
+ sequelize.sync();
+
+ module.exports = {
+ sequelize,
+ User,
+ };
diff --git a/Sean/server.js b/Sean/server.js
index cb5bc0b..00b1949 100644
--- a/Sean/server.js
+++ b/Sean/server.js
@@ -1,5 +1,6 @@
const express = require("express");
const session = require("express-session");
+const rateLimit = require('express-rate-limit');
const mysql2 = require('mysql2');
const bodyParser = require("body-parser");
const bcrypt = require("bcrypt");
@@ -8,10 +9,13 @@ const nodemailer = require("nodemailer");
const otpGenerator = require('otp-generator');
const { body, validationResult } = require('express-validator');
const validator = require('validator');
+const { format } = require('date-fns');
+const { Sequelize } = require('sequelize');
const { transporter } = require("./modules/nodeMailer");
const { connection } = require("./modules/mysql");
-
+const { sequelize, User } = require("./modules/mysql");
+const userLogs= require('./models/userLogs')(sequelize); // Adjust the path based on your project structure
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
@@ -19,15 +23,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,
- })
-);
+
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();
@@ -68,136 +76,144 @@ const sendOTPByEmail = async (email, otp) => {
app.get("/login", (req, res) => {
- // Pass an initial value for the error variable
+
res.render("login", { error: null });
});
-const logActivity = async (username, success, message) => {
+
+
+ const limiter = rateLimit({
+ windowMs: 15 * 60 * 1000, // 15 minutes
+ max: 5, // limit each IP to 3 requests per windowMs
+ message: 'Too many login attempts from this IP, please try again later.',
+ });
+
+ app.use('/login', limiter);
+
+
+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()) {
+ return res.render('login', { error: 'Invalid input. Please check your credentials.', csrfToken: req.session.csrfToken });
+ }
+
+ let { username, password } = req.body;
+ username = username.trim();
+
+ const user = await User.findOne({ where: { username } });
+
+ if (user) {
+ const isLoginSuccessful = await bcrypt.compare(password, user.password);
+
+ if (isLoginSuccessful) {
+ await userLogs.create({ username, success: true, activity: "Credentials entered correctly" });
+
+
+ const { otp, expirationTime } = generateOTP();
+
+ req.session.otp = otp;
+ req.session.otpExpiration = expirationTime;
+ req.session.save();
+
+ try {
+ await sendOTPByEmail(user.email, otp);
+ await userLogs.create({
+ username: username,
+ activity: "OTP successfully sent to user",
+ });
+
+ } catch (sendOTPError) {
+ await userLogs.create({
+ username: username,
+ activity: "OTP failed to send to user",
+ });
+
+ console.error("Error sending OTP:", sendOTPError);
+ return res.status(500).send("Internal Server Error");
+ }
+
+ res.render("otp", { error: null, username: user.username, csrfToken: req.session.csrfToken });
+ } else {
+ await userLogs.create({ username, success: false, activity: "Incorrect password" });
+
+ res.render("login", { error: "Invalid username or password", csrfToken: req.session.csrfToken });
+ }
+ } else {
+ await userLogs.create({
+ username: username,
+ activity: "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 {
- if (!username) {
- console.error("Error logging activity: Username is null or undefined");
- return;
+ const errors = validationResult(req);
+
+ if (!errors.isEmpty()) {
+ return res.render('otp', { error: 'Invalid OTP. Please try again.', username: req.body.username, csrfToken: req.session.csrfToken });
}
- 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)
- }
- };
-
-
-
- // 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 = ?";
- const updateLastLoginSql = "UPDATE users SET lastLogin = CURRENT_TIMESTAMP 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 (!req.session) {
+ console.error("Session is not defined.");
+ return res.status(500).send("Internal Server Error");
+ }
+
+ const user = await User.findOne({ where: { username: req.body.username } });
+
+ if (!user) {
+ console.error("User not found.");
+ return res.status(500).send("Internal Server Error");
+ }
+
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");
-
+ await userLogs.create({ username: req.body.username, activity: "OTP entered correctly" });
}
- // Correct OTP, redirect to home page
+ const sessionToken = crypto.randomBytes(32).toString('hex');
+
req.session.authenticated = true;
req.session.username = req.body.username;
+ req.session.sessionToken = sessionToken;
+
+
+ csrfTokenSession = crypto.randomBytes(32).toString('hex');
+
+ // Log anti-CSRF token
+ console.log(`Generated Anti-CSRF Token: ${csrfTokenSession}`);
+
+ // Set CSRF token as a cookie
+ 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}`);
+
res.redirect("/home");
} else {
- // Log unsuccessful OTP entry
if (req.body.username) {
- await logActivity(req.body.username, false, "Incorrect OTP entered");
-
+ await userLogs.create({ username: req.body.username, activity: "Incorrect OTP entered" });
}
- // Incorrect OTP, render login page with error
- res.render("login", { error: "Incorrect OTP. Please try again." });
+ res.render("login", { error: "Incorrect OTP. Please try again."});
}
} catch (error) {
console.error("Error in OTP verification route:", error);
@@ -205,45 +221,65 @@ const logActivity = async (username, success, message) => {
}
});
+ app.get("/logout", async (req, res) => {
+ try {
+ const username = req.session.username || "Unknown User";
+
+ // Log the logout activity using Sequelize
+ await userLogs.create({ username, activity: "User logged out. Session destroyed." });
+
+
+ // Log the user out by clearing the session
+ req.session.destroy(async (err) => {
+ if (err) {
+ console.error("Error destroying session:", err);
+
+ // Log the logout activity using Sequelize
+ await userLogs.create({ username, activity: "User logged out unsuccessfully. Session not destroyed." });
+ } else {
+ console.log("Session destroyed.");
+
+ // Clear the session token cookie
+ res.clearCookie('sessionToken');
+ }
+
+ // 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");
+ }
+ });
+
-// Update your /home route to retrieve the overall last 10 logins for all users
-app.get("/home", isAuthenticated, (req, res) => {
- // Retrieve the overall last 10 logins for all users
- const loginsQuery =
- "SELECT username, lastLogin FROM users ORDER BY lastLogin DESC LIMIT 10";
- connection.query(loginsQuery, (error, loginResults) => {
- if (error) {
- console.error("Error executing login logs query:", error);
- res.status(500).send("Internal Server Error");
- return;
- }
-
- // Log the results on the server side
- console.log("Login Logs on Server:", loginResults);
-
- // Render the home page with login logs data
- res.render("home", {
- username: req.session.username,
- loginLogs: loginResults,
- });
+ 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) {
+
+
+ app.get("/inusers", isAuthenticated, async (req, res) => {
+ try {
+ // Fetch all user data from the database using Sequelize
+ const allUsers = await User.findAll({
+ attributes: ['name', 'username', 'email', 'jobTitle'],
+ });
+
+ const currentUsername = req.session.username;
+
+ // Render the inusers page with JSON data
+ res.render("inusers", { allUsers, csrfToken: csrfTokenSession, currentUsername });
+ } catch (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 });
});
-});
+
function isStrongPassword(password) {
// Password must be at least 10 characters long
if (password.length < 10) {
@@ -273,336 +309,212 @@ function isStrongPassword(password) {
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() });
- }
-
- const { name, username, email, password, jobTitle } = req.body;
- console.log("Sanitized Input:", {
- name,
- username,
- email,
- password: "*****", // Avoid logging passwords
- jobTitle,
- });
- // 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
-
- // Validate password complexity (additional 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"
- );
-
- // Log the results of the query
- console.log("Query Results:", results);
-
- // Respond with a success message
- res
- .status(201)
- .json({ message: "User created successfully" });
- });
- }
- );
- });
- });
- }
- );
- }
- );
- } 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" });
- }
- }
- );
+ '/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 (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) {
+ return res.status(403).json({ error: 'CSRF token mismatch' });
+ }
+
+ // Extract user input
+ const { name, username, email, password, jobTitle } = req.body;
+ console.log(submittedCSRFToken);
+
+ // 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 existingUser = await User.findOne({ where: { username } });
+
+ if (existingUser) {
+ // Log unsuccessful user creation due to username taken
+ await userLogs.create({ username: creatorUsername, activity: "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 existingEmailUser = await User.findOne({ where: { email } });
+
+ if (existingEmailUser) {
+ // Log unsuccessful user creation due to email taken
+ await userLogs.create({ username: creatorUsername, activity: "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
+ const hashedPassword = await bcrypt.hash(password, 10);
+
+ // Start a transaction
+ const t = await sequelize.transaction();
+
+ try {
+ // Create the user
+ const newUser = await User.create({
+ name,
+ username,
+ email,
+ password: hashedPassword,
+ lastLogin: null,
+ jobTitle,
+ }, { transaction: t });
+
+ // Commit the transaction
+ await t.commit();
+
+ // Log successful user creation
+ await userLogs.create({ username: creatorUsername, activity: "user created successfully" });
+
+ return res.status(200).json({ message: "User created successfully" });
+ } catch (createUserError) {
+ // Rollback the transaction in case of an error
+ await t.rollback();
+
+ console.error("Error creating user:", createUserError);
+
+ // Log unsuccessful user creation due to an error
+ await userLogs.create({ username: creatorUsername, activity: "internal error" });
+
+ return res.status(500).json({ error: "Internal Server Error" });
+ }
+ } catch (error) {
+ console.error("Error creating user:", error);
+ return res.status(500).json({ error: "Internal Server Error" });
+ }
+ }
+);
-app.get("/forgot-password", (req, res) => {
- res.render("forgot-password"); // Assuming you have an EJS template for this
-});
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;
+app.post("/forgot-password", async (req, res) => {
+ let user; // Declare the 'user' variable outside the try-catch block
- // Sanitize the input
- const sanitizedUsernameOrEmail = validator.escape(usernameOrEmail);
+ try {
+ const { usernameOrEmail } = req.body;
- // Perform the logic for sending the reset password email
+ // Sanitize the input
+ const sanitizedUsernameOrEmail = validator.escape(usernameOrEmail);
- // 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
+ // Find the user by username or email
+ user = await User.findOne({
+ where: {
+ [Sequelize.Op.or]: [
+ { username: sanitizedUsernameOrEmail },
+ { email: sanitizedUsernameOrEmail },
+ ],
+ },
+ });
- // 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");
- }
- });
- }
- }
- );
- }
+ if (!user) {
+ const error = "Username or email not found.";
+ return res.render("forgot-password", { error, success: null });
}
- );
+
+ // Generate reset token and update the user
+ const reset_token = crypto.randomBytes(20).toString("hex");
+ const reset_token_expiry = new Date(Date.now() + 3600000); // Token expires in 1 hour
+
+ // Update the user with the reset token and expiry
+ await User.update(
+ {
+ reset_token,
+ reset_token_expiry,
+ },
+ {
+ where: {
+ id: user.id, // Replace 'id' with the actual primary key field of your User model
+ },
+ }
+ );
+
+ // Send email with reset link
+ const resetLink = `http://localhost:3000/reset-password/${reset_token}`;
+ const mailOptions = {
+ to: user.email,
+ subject: "Password Reset",
+ text: `Click on the following link to reset your password: ${resetLink}`,
+ };
+
+ await transporter.sendMail(mailOptions);
+
+ 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
+ await userLogs.create({
+ username: user.username,
+ activity: "Password reset link sent successfully",
+ });
+ } catch (error) {
+ if (user) {
+ await userLogs.create({
+ username: user.username,
+ activity: "Password reset link unsuccessfully sent. Please check with the administrator.",
+ });
+ }
+ console.error("Error during password reset:", error);
+ const errorMessage = "An error occurred during the password reset process.";
+ res.render("forgot-password", { error: errorMessage, success: null });
+ }
});
+
+
-// 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;
+ app.post("/reset-password/:token", async (req, res) => {
+ try {
+ 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);
+ // 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" });
- }
+ // Find user with matching reset token and not expired
+ const user = await User.findOne({
+ where: {
+ reset_token: sanitizedToken,
+ reset_token_expiry: { [Sequelize.Op.gt]: new Date() },
+ },
+ });
- if (selectResults.length === 0) {
+ if (!user) {
// Pass the error to the template when rendering the reset-password page
return res.render("reset-password", {
token,
@@ -633,31 +545,34 @@ app.post("/reset-password/:token", async (req, res) => {
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");
- }
+ const updateQuery = {
+ password: hashedPassword,
+ reset_token: null,
+ reset_token_expiry: null,
+ };
+ const whereCondition = {
+ reset_token: sanitizedToken,
+ };
+
+ await User.update(updateQuery, {
+ where: whereCondition,
});
- });
+ // Log password reset activity
+ await userLogs.create({
+ username: user.username,
+ activity: "Password reset successfully",
+ });
+
+ // Redirect to the success page upon successful password reset
+ res.redirect("/success");
+ } catch (error) {
+ console.error("Error during password reset:", error);
+ // Pass the error to the template when rendering the reset-password page
+ res.render("reset-password", {
+ token: req.params.token,
+ resetError: "Error during password reset",
+ });
+ }
});
app.get("/success", (req, res) => {
@@ -675,9 +590,15 @@ app.get("/reset-password/:token", (req, res) => {
success: null,
});
});
+
app.post("/reset-password", async (req, res) => {
- const { username, password, confirmPassword } = req.body;
+ const { username, password, confirmPassword, csrf_token } = req.body;
const creatorUsername = req.session.username;
+ const submittedCSRFToken = req.body.csrf_token;
+
+ if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) {
+ return res.status(403).json({ error: 'CSRF token mismatch' });
+ }
// Sanitize the inputs
const sanitizedUsername = validator.escape(username);
@@ -697,169 +618,154 @@ app.post("/reset-password", async (req, res) => {
});
}
- // Hash the new password
- const hashedPassword = await bcrypt.hash(sanitizedPassword, 10);
+ try {
+ // Find the user in the database
+ const user = await User.findOne({ where: { username: sanitizedUsername } });
- // Check if the user exists in the database before updating the password
- const userExists = await checkIfUserExists(sanitizedUsername);
+ if (!user) {
+ return res.status(404).json({ error: "User does not exist" });
+ }
- if (!userExists) {
- return res.status(404).json({ error: "User does not exist" });
+ // Generate a random salt and hash the new password
+ const saltRounds = 10;
+ const hashedPassword = await bcrypt.hash(sanitizedPassword, saltRounds);
+
+ // Update user's password
+ await User.update(
+ { password: hashedPassword },
+ { where: { username: sanitizedUsername } }
+ );
+
+ // Log password reset activity
+ await userLogs.create({
+ username: creatorUsername,
+ activity: `Password has been reset for ${sanitizedUsername}`,
+ });
+
+ // Password update successful
+ return res.status(200).json({ success: "Password updated successfully" });
+ } catch (error) {
+ console.error("Error updating password:", error);
+ return res.status(500).json({ error: "Error updating password" });
}
-
- // 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';
+app.get('/searchUser', async (req, res) => {
+ const { username } = req.query;
+
+ // Sanitize the input
+ const sanitizedUsername = validator.escape(username);
+
+ try {
+ // Find the user in the database
+ const user = await User.findOne({ where: { username: sanitizedUsername } });
+ console.log(user);
+ if (!user) {
+ // No user found with the given username
+ res.status(404).json({ success: false, error: 'User not found' });
+ } else {
+ // User found, return user data
+ res.json(user);
+
+ }
+ } catch (error) {
+ console.error('Sequelize query error:', error);
+ res.status(500).json({ success: false, error: 'Internal Server Error' });
+ }
+});
+
+app.get('/api/users', async (req, res) => {
+ try {
+ // Find all users in the database
+ const users = await User.findAll();
+
+ // Return the users in the response
+ res.json(users);
+ } catch (error) {
+ console.error('Sequelize query error:', error);
+ res.status(500).json({ success: false, error: 'Internal Server Error' });
+ }
+});
- 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);
- }
- });
- });
+app.get('/api/searchUser', async (req, res) => {
+ const { username } = req.query;
+ console.log(username);
+ try {
+ // Find the user in the database by username
+ const user = await User.findOne({ where: { username } });
+
+ if (!user) {
+ // No user found with the given username
+ res.status(404).json({ success: false, error: 'User not found' });
+ } else {
+ // User found, return user data
+ res.json(user);
+
+ }
+ } catch (error) {
+ console.error('Sequelize query error:', error);
+ res.status(500).json({ success: false, error: 'Internal Server Error' });
+ }
+});
- // 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 = ?';
+app.delete('/api/deleteUser/:username', async (req, res) => {
+ const { username } = req.params;
+ 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 !== csrfTokenSession) {
+ return res.status(403).json({ success: false, error: 'CSRF token mismatch' });
+ }
+
+ // Log deletion activity to UserLogs model
+ const deletionActivity = `User ${username} has been successfully deleted`;
+ await userLogs.create({ username: creatorUsername, activity: deletionActivity });
+
+ // Perform user deletion using the User model
+ const deletedUser = await User.destroy({ where: { username } });
+
+ if (!deletedUser) {
+ res.status(404).json({ success: false, error: 'User not found' });
+ } else {
+ res.json({ success: true, message: 'User deleted successfully' });
+ }
+ } catch (error) {
+ console.error('Sequelize query error:', error);
+ res.status(500).json({ success: false, error: 'Internal Server Error', details: error.message });
+ }
+});
- 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);
- }
- });
- });
-
- // Route to delete a user by username
-
- 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 {
- // 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.get('/api/getLogs', async (req, res) => {
+ try {
+ // Query the database to fetch logs using Sequelize model
+ const logs = await userLogs.findAll({
+ attributes: ['id', 'username', 'activity', 'timestamp'],
+ });
+
+ // Format timestamps to a more readable format with timezone conversion
+ const formattedLogs = logs.map((log) => ({
+ id: log.id,
+ username: log.username,
+ activity: log.activity,
+ timestamp: format(new Date(log.timestamp), 'yyyy-MM-dd HH:mm:ssXXX', { timeZone: 'Asia/Singapore' }),
+ }));
+
+ // Send the formatted logs as a JSON response
+ res.json(formattedLogs);
+ } catch (error) {
+ console.error('Sequelize query error:', error);
+ res.status(500).json({ error: 'Error fetching logs from Sequelize' });
+ }
+});
+
app.use(express.static("views"));
app.listen(PORT, () => {
diff --git a/Sean/views/home.ejs b/Sean/views/home.ejs
index a2e2d81..cff55e4 100644
--- a/Sean/views/home.ejs
+++ b/Sean/views/home.ejs
@@ -8,7 +8,15 @@
Home
+
+
Welcome to the Home Page, <%= username %>!
-
Last 10 Logins:
-
-
-
- Username |
- Last Login Time |
-
-
-
- <% loginLogs.forEach(log => { %>
-
- <%= log.username %> |
- <%= new Date(log.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) %> |
-
- <% }); %>
-
-
-
-
+
-