From 8aeb622b17420b0a42aec29b2ea1e38b8eea4f8a Mon Sep 17 00:00:00 2001 From: BIG2EYEZ Date: Thu, 18 Jan 2024 18:34:09 +0800 Subject: [PATCH] CHANGE ALL MYSQL TO ORM --- Sean/models/User.js | 77 +-- Sean/models/userLogs.js | 30 ++ Sean/modules/mysql.js | 76 +-- Sean/server.js | 1076 +++++++++++++++++---------------------- Sean/views/inusers.ejs | 2 - Sean/views/inusers.js | 27 +- package-lock.json | 601 +++++++++++++++++++++- package.json | 5 +- 8 files changed, 1169 insertions(+), 725 deletions(-) create mode 100644 Sean/models/userLogs.js 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 2501d2d..cc53850 100644 --- a/Sean/modules/mysql.js +++ b/Sean/modules/mysql.js @@ -2,47 +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: "adminusers", - timezone: "Z", // Set the timezone to UTC - ssl: { - ca: fs.readFileSync(path.resolve(__dirname, '../../cert/DigiCertGlobalRootCA.crt.pem')), + + 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')), - } - }); -/* -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 }; + }, + + }, + }, + + + ); + + 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 3b36cf4..00b1949 100644 --- a/Sean/server.js +++ b/Sean/server.js @@ -9,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()); @@ -77,57 +80,6 @@ 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) - } - }; const limiter = rateLimit({ @@ -135,167 +87,162 @@ const logActivity = async (username, success, message) => { 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'), + +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); + 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 }); + 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"); } - let { username, password } = req.body; - username = username.trim(); + res.render("otp", { error: null, username: user.username, csrfToken: req.session.csrfToken }); + } else { + await userLogs.create({ username, success: false, activity: "Incorrect password" }); - const loginSql = "SELECT * FROM users WHERE username = ?"; + res.render("login", { error: "Invalid username or password", csrfToken: req.session.csrfToken }); + } + } else { + await userLogs.create({ + username: username, + activity: "User not found", + }); - 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"); + 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 - csrfTokensession = crypto.randomBytes(32).toString('hex'); - - // Set anti-CSRF token in res.locals - - - // Log anti-CSRF token - console.log(`Generated Anti-CSRF Token: ${csrfTokensession}`); - // 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"); - } -}); - - - - app.get("/logout", (req, res) => { + body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'), + ], + async (req, res) => { try { - const username = req.session.username || "Unknown User"; + 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 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) { + if (req.body.username) { + await userLogs.create({ username: req.body.username, activity: "OTP entered correctly" }); + } + + 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 { + if (req.body.username) { + await userLogs.create({ username: req.body.username, activity: "Incorrect OTP entered" }); + } + + res.render("login", { error: "Incorrect OTP. Please try again."}); + } + } catch (error) { + console.error("Error in OTP verification route:", error); + res.status(500).send("Internal Server Error"); + } + }); + + app.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); - await logLogoutActivity(username, false, "User logged out unsucessfully. Session not destroyed."); + + // Log the logout activity using Sequelize + await userLogs.create({ username, activity: "User logged out unsuccessfully. Session not destroyed." }); } else { - - console.log(`Session destroyed.`); + console.log("Session destroyed."); + + // Clear the session token cookie res.clearCookie('sessionToken'); - // 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"); }); @@ -305,6 +252,7 @@ async (req, res) => { } }); + app.get("/home", isAuthenticated, (req, res) => { // Render the home page with sensor data @@ -315,21 +263,23 @@ async (req, res) => { -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; } - const currentUsername = req.session.username; - // Render the inusers page with JSON data - res.render("inusers", { allUsers ,csrfToken: csrfTokensession, currentUsername:currentUsername }); }); -}); + function isStrongPassword(password) { // Password must be at least 10 characters long if (password.length < 10) { @@ -359,29 +309,7 @@ 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', @@ -405,13 +333,14 @@ app.post( // Validate the anti-CSRF token const submittedCSRFToken = req.body.csrf_token; - if (!csrfTokensession || submittedCSRFToken !== csrfTokensession) { + 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 @@ -421,216 +350,171 @@ app.post( } // 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" }); - } + const existingUser = await User.findOne({ where: { username } }); - 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." - }); - } + if (existingUser) { + // Log unsuccessful user creation due to username taken + await userLogs.create({ username: creatorUsername, activity: "username taken" }); - // 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" }); - } + return res.status(400).json({ + error: "Username is already taken", + message: "Username is already taken. Please choose a different username." + }); + } - 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." - }); - } - bcrypt.genSalt(10, (saltError, salt) => { - if (saltError) { - console.error("Error generating salt:", saltError); - return res.status(500).json({ error: "Internal Server Error" }); - } - - bcrypt.hash(req.body.password, salt, (hashError, hashedPassword) => { - if (hashError) { - console.error("Error hashing password:", hashError); - return res.status(500).json({ error: "Internal Server Error" }); - } - - connection.beginTransaction((transactionErr) => { - if (transactionErr) { - console.error("Error starting transaction:", transactionErr); - return res.status(500).json({ error: "Internal Server Error" }); - } + // Check if the email is already taken + const existingEmailUser = await User.findOne({ where: { email } }); - // Define the insert query - const insertUserQuery = - "INSERT INTO users (name, username, email, password, lastLogin, jobTitle) VALUES (?, ?, ?, ?, NULL, ?)"; + if (existingEmailUser) { + // Log unsuccessful user creation due to email taken + await userLogs.create({ username: creatorUsername, activity: "email taken" }); - // Log the query and its parameters - console.log("Insert Query:", insertUserQuery); - console.log("Query Parameters:", [name, username, email, hashedPassword, jobTitle]); + return res.status(400).json({ + error: "Email is already in use", + message: "Email is already in use. Please choose another email." + }); + } - // Execute the query with user data - connection.query(insertUserQuery, [name, username, email, hashedPassword, jobTitle], (queryErr, results) => { - if (queryErr) { - console.error("Error executing query:", queryErr); + // Hash the password + const hashedPassword = await bcrypt.hash(password, 10); - // 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; - } + // Start a transaction + const t = await sequelize.transaction(); - // Commit the transaction - connection.commit((commitErr) => { - if (commitErr) { - console.error("Error committing transaction:", commitErr); - return res.status(500).json({ error: "Internal Server Error" }); - } - - res.status(200).json({ message: "User created successfully" }); - logUserCreationActivity(creatorUsername, true, "user created successfully"); + try { + // Create the user + const newUser = await User.create({ + name, + username, + email, + password: hashedPassword, + lastLogin: null, + jobTitle, + }, { transaction: t }); - - }); - }); - }); - }); - }); - }); - }); - } catch (error) { - console.error("Error creating user:", error); - return res.status(500).json({ error: "Internal Server Error" }); - } - } - ); + // 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", { 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, @@ -658,51 +542,37 @@ app.post("/reset-password/:token", async (req, res) => { } // Hash the new password - const hashedPassword = await new Promise((resolve, reject) => { - bcrypt.genSalt(10, (saltError, salt) => { - if (saltError) { - console.error("Error generating salt:", saltError); - reject("Internal Server Error"); - } - - // Use the generated salt to hash the password - bcrypt.hash(sanitizedPassword, salt, (hashError, hashed) => { - if (hashError) { - console.error("Error hashing password:", hashError); - reject("Internal Server Error"); - } - - resolve(hashed); - }); - }); - }); + 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) => { @@ -720,12 +590,13 @@ app.get("/reset-password/:token", (req, res) => { 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 (!csrfTokensession|| submittedCSRFToken !== csrfTokensession) { + if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) { return res.status(403).json({ error: 'CSRF token mismatch' }); } @@ -747,183 +618,154 @@ app.post("/reset-password", async (req, res) => { }); } - // Generate a random salt - const saltRounds = 10; // You can adjust the number of rounds based on your security requirements - const salt = await bcrypt.genSalt(saltRounds); + try { + // Find the user in the database + const user = await User.findOne({ where: { username: sanitizedUsername } }); - // Hash the new password with the generated salt - const hashedPassword = await bcrypt.hash(sanitizedPassword, salt); + if (!user) { + return res.status(404).json({ error: "User does not exist" }); + } - // Check if the user exists in the database before updating the password - const userExists = await checkIfUserExists(sanitizedUsername); + // Generate a random salt and hash the new password + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(sanitizedPassword, saltRounds); - if (!userExists) { - return res.status(404).json({ error: "User does not exist" }); + // 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('/searchUser', async (req, res) => { + const { username } = req.query; -app.get('/api/users', (req, res) => { - const query = 'SELECT * FROM users'; + // 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); - } - }); - }); - - 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; - console.log(csrfToken); - console.log(csrfTokensession); - // 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 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/inusers.ejs b/Sean/views/inusers.ejs index eeec05f..4962a64 100644 --- a/Sean/views/inusers.ejs +++ b/Sean/views/inusers.ejs @@ -41,7 +41,6 @@ Name Username Email - Last Login Job Title @@ -52,7 +51,6 @@ <%- user.name %> <%- user.username %> <%- user.email %> - <%- new Date(user.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) %> <%- user.jobTitle %> <% }); %> diff --git a/Sean/views/inusers.js b/Sean/views/inusers.js index abd9b89..975ed5b 100644 --- a/Sean/views/inusers.js +++ b/Sean/views/inusers.js @@ -27,9 +27,10 @@ $(document).ready(function () { }); $('#searchUserButton').on('click', function () { - console.log('Search button clicked'); + const searchUsername = $('#searchUserInput').val(); // Call the function to search for the user + searchUser(searchUsername); }); @@ -71,9 +72,9 @@ function searchUser(username) { throw new Error(`HTTP error! Status: ${response.status}`); } }) - .then(users => { + .then(user => { // Display search results - displaySearchResults(users); + displaySearchResults(user); }) .catch(error => { console.error('Search error:', error); @@ -83,24 +84,17 @@ function searchUser(username) { // Function to display search results function displaySearchResults(users) { + const searchResultsList = $('#searchResultsList'); // Clear previous results searchResultsList.empty(); - if (users && users.length > 0) { - users.forEach(user => { - const listItem = `
  • ${user.username} -
  • `; + const listItem = `
  • ${users.username} -
  • `; searchResultsList.append(listItem); - }); - // Show the search results container $('#searchResultsContainer').show(); - } else { - // Hide the search results container if no results - $('#searchResultsContainer').hide(); - } -} + } // Event listener for delete user button in search results $('#searchResultsList').on('click', '.deleteUserButton', function () { @@ -303,8 +297,7 @@ $('#resetPasswordForm').on('submit', function (e) { const password = $('#resetPassword').val(); const confirmPassword = $('#resetConfirmPassword').val(); const csrf_token = $('#userForm input[name="csrf_token"]').val(); - console.log('Username:', username); - console.log('New Password:', password); + // Validate passwords if (password !== confirmPassword) { @@ -493,11 +486,7 @@ fetchLogs(); // Assuming allUsers is an array containing user information const user = allUsers.find(user => user.username === currentUsername); const userRole = user?.jobTitle; -console.log('All Users:', allUsers); -console.log('Current Username:', currentUsername); -// Log the user role to the console -console.log('User Role:', userRole); // Function to enable/disable actions based on user role function handleUserRoleAccess() { diff --git a/package-lock.json b/package-lock.json index 77487a2..08346ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,64 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -30,6 +88,11 @@ } } }, + "@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, "@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -72,6 +135,12 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true + }, "@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -86,17 +155,17 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "@types/node": { - "version": "20.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", - "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", + "version": "20.11.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz", + "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", "requires": { "undici-types": "~5.26.4" } }, "@types/validator": { - "version": "13.11.7", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.7.tgz", - "integrity": "sha512-q0JomTsJ2I5Mv7dhHhQLGjMvX0JJm5dyZ1DXQySIUzU1UlwzB8bt+R6+LODUbz0UDIOvEzGc28tk27gBJw2N8Q==" + "version": "13.11.8", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", + "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==" }, "abbrev": { "version": "1.1.1", @@ -169,6 +238,11 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -183,6 +257,11 @@ "node-addon-api": "^5.0.0" } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -259,6 +338,18 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, + "cli-color": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", + "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.61", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -287,11 +378,25 @@ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -336,6 +441,16 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "csrf": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", @@ -396,11 +511,30 @@ } } }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "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==" }, + "date-fns": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.2.0.tgz", + "integrity": "sha512-E4KWKavANzeuusPi0jUjpuI22SURAznGkx7eZV+4i6x2A+IZxAMcajgkvuDAU1bg40+xuhW1zRdVIIM/4khuIg==" + }, + "date-fns-tz": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz", + "integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -502,6 +636,32 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "requires": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "dependencies": { + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -535,6 +695,51 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" }, + "es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -555,6 +760,15 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -687,6 +901,21 @@ "validator": "^13.9.0" } }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "requires": { + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + } + } + }, "fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -752,6 +981,22 @@ "path-exists": "^4.0.0" } }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, "formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -770,6 +1015,17 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -859,6 +1115,11 @@ "get-intrinsic": "^1.1.3" } }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -954,11 +1215,24 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "requires": { + "hasown": "^2.0.0" + } + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -969,11 +1243,30 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -985,6 +1278,61 @@ "minimatch": "^3.1.2" } }, + "js-beautify": { + "version": "1.14.11", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", + "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.3", + "glob": "^10.3.3", + "nopt": "^7.2.0" + }, + "dependencies": { + "abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==" + }, + "glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "nopt": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "requires": { + "abbrev": "^2.0.0" + } + } + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1011,6 +1359,14 @@ "yallist": "^4.0.0" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "requires": { + "es5-ext": "~0.10.2" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1031,6 +1387,21 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, + "memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -1107,17 +1478,19 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, "moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "version": "0.5.44", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.44.tgz", + "integrity": "sha512-nv3YpzI/8lkQn0U6RkLd+f0W/zy/JnoR5/EyPz/dNkPTBjA2jNLCVxaiQ8QpeLymhSZvX0wCL5s27NQWdOPwAw==", "requires": { "moment": "^2.29.4" + }, + "dependencies": { + "moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" + } } }, "ms": { @@ -1126,9 +1499,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.5.tgz", - "integrity": "sha512-pS/KqIb0xlXmtmqEuTvBXTmLoQ5LmAz5NW/r8UyQ1ldvnprNEj3P9GbmuQQ2J0A4LO+ynotGi6TbscPa8OUb+w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.7.1.tgz", + "integrity": "sha512-4EEqYu57mnkW5+Bvp5wBebY7PpfyrmvJ3knHcmLkp8FyBu4kqgrF2GxIjsC2tbLNZWqJaL21v/MYH7bU5f03oA==", "requires": { "denque": "^2.1.0", "generate-function": "^2.3.1", @@ -1180,6 +1553,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, "node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -1311,6 +1689,32 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "requires": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==" + } + } + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -1341,6 +1745,11 @@ "source-map-js": "^1.0.2" } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1400,6 +1809,16 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "retry-as-promised": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", @@ -1517,6 +1936,73 @@ "uuid": "^8.3.2", "validator": "^13.9.0", "wkx": "^0.5.0" + }, + "dependencies": { + "moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" + } + } + }, + "sequelize-cli": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.2.tgz", + "integrity": "sha512-V8Oh+XMz2+uquLZltZES6MVAD+yEnmMfwfn+gpXcDiwE3jyQygLt4xoI0zG8gKt6cRcs84hsKnXAKDQjG/JAgg==", + "requires": { + "cli-color": "^2.0.3", + "fs-extra": "^9.1.0", + "js-beautify": "^1.14.5", + "lodash": "^4.17.21", + "resolve": "^1.22.1", + "umzug": "^2.3.0", + "yargs": "^16.2.0" + }, + "dependencies": { + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } } }, "sequelize-pool": { @@ -1556,6 +2042,19 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -1596,6 +2095,16 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1619,6 +2128,14 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1627,6 +2144,11 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "tar": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", @@ -1645,6 +2167,15 @@ "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1665,6 +2196,11 @@ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1682,11 +2218,24 @@ "random-bytes": "~1.0.0" } }, + "umzug": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", + "integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==", + "requires": { + "bluebird": "^3.7.2" + } + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1736,6 +2285,14 @@ "webidl-conversions": "^3.0.0" } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, "which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", @@ -1767,6 +2324,16 @@ "strip-ansi": "^6.0.0" } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 3ca984c..58ffab1 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "body-parser": "^1.20.2", "cookie-parser": "^1.4.6", "csurf": "^1.11.0", + "date-fns": "^3.2.0", + "date-fns-tz": "^2.0.0", "dotenv": "^16.3.1", "ejs": "^3.1.9", "esm": "^3.2.25", @@ -31,7 +33,7 @@ "helmet": "^7.1.0", "moment": "^2.30.1", "mqtt": "^5.3.3", - "mysql2": "^3.6.5", + "mysql2": "^3.7.1", "node-fetch": "^3.3.2", "nodemailer": "^6.9.7", "otp-generator": "^4.0.1", @@ -39,6 +41,7 @@ "qrcode": "^1.5.3", "sanitize-html": "^2.11.0", "sequelize": "^6.35.2", + "sequelize-cli": "^6.6.2", "sql": "^0.78.0", "validator": "^13.11.0" },