diff --git a/Sean/server.js b/Sean/server.js index a0ca3c4..cb5bc0b 100644 --- a/Sean/server.js +++ b/Sean/server.js @@ -7,6 +7,7 @@ const crypto = require("crypto"); const nodemailer = require("nodemailer"); const otpGenerator = require('otp-generator'); const { body, validationResult } = require('express-validator'); +const validator = require('validator'); const { transporter } = require("./modules/nodeMailer"); const { connection } = require("./modules/mysql"); @@ -296,173 +297,198 @@ const logUserCreationActivity = async (creatorUsername, success, message) => { } }; -app.post("/createUser", async (req, res) => { - try { +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 + + // Validate password complexity (additional check) if (!isStrongPassword(password)) { - return res - .status(400) - .json({ error: "Password does not meet complexity requirements" }); + 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" }); - }); - } - ); - }); - }); - } - ); + 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) { + } catch (error) { console.error("Error creating user:", error); // Log unsuccessful user creation due to an error logUserCreationActivity(req.session.username, false, "internal error"); // Adjust this based on how you store the creator's username in your session res.status(500).json({ error: "Internal Server Error" }); + } } -}); + ); app.get("/forgot-password", (req, res) => { res.render("forgot-password"); // Assuming you have an EJS template for this @@ -475,71 +501,74 @@ app.get("/forgot-password", (req, res) => { // Handle the submission of the forgot password form app.post("/forgot-password", (req, res) => { const { usernameOrEmail } = req.body; - + + // Sanitize the input + const sanitizedUsernameOrEmail = validator.escape(usernameOrEmail); + // Perform the logic for sending the reset password email - + // Check if the username or email exists in the database const checkUserQuery = "SELECT * FROM users WHERE username = ? OR email = ?"; connection.query( - checkUserQuery, - [usernameOrEmail, usernameOrEmail], - (checkError, checkResults) => { - if (checkError) { - console.error("Error checking user:", checkError); - const error = "An error occurred during the password reset process."; + checkUserQuery, + [sanitizedUsernameOrEmail, sanitizedUsernameOrEmail], + (checkError, checkResults) => { + if (checkError) { + console.error("Error checking user:", checkError); + const error = "An error occurred during the password reset process."; + res.render("forgot-password", { error, success: null }); + } else if (checkResults.length === 0) { + const error = "Username or email not found."; + res.render("forgot-password", { error, success: null }); + } else { + // Assuming the user exists, generate a reset token and send an email + const user = checkResults[0]; + const resetToken = crypto.randomBytes(20).toString("hex"); + const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour + + // Update user with reset token and expiry + const updateQuery = + "UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?"; + connection.query( + updateQuery, + [resetToken, resetTokenExpiry, user.id], + (updateError) => { + if (updateError) { + console.error("Error updating reset token:", updateError); + const error = + "An error occurred during the password reset process."; res.render("forgot-password", { error, success: null }); - } else if (checkResults.length === 0) { - const error = "Username or email not found."; - res.render("forgot-password", { error, success: null }); - } else { - // Assuming the user exists, generate a reset token and send an email - const user = checkResults[0]; - const resetToken = crypto.randomBytes(20).toString("hex"); - const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour - - // Update user with reset token and expiry - const updateQuery = - "UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?"; - connection.query( - updateQuery, - [resetToken, resetTokenExpiry, user.id], - (updateError) => { - if (updateError) { - console.error("Error updating reset token:", updateError); - const error = - "An error occurred during the password reset process."; - res.render("forgot-password", { error, success: null }); - } else { - // Send email with reset link - const resetLink = `http://localhost:3000/reset-password/${resetToken}`; - const mailOptions = { - to: user.email, - subject: "Password Reset", - text: `Click on the following link to reset your password: ${resetLink}`, - }; - transporter.sendMail(mailOptions, (emailError, info) => { - if (emailError) { - console.error("Error sending email:", emailError); - const error = - "An error occurred during the password reset process."; - res.render("forgot-password", { error, success: null }); - } else { - console.log("Email sent: " + info.response); - const success = - "Password reset email sent successfully. Check your inbox."; - res.render("forgot-password", { error: null, success }); - - // Log the successful sending of the reset link in the database - logPasswordResetActivity(user.username,"link sent successfully"); - } - }); - } - } - ); + } else { + // Send email with reset link + const resetLink = `http://localhost:3000/reset-password/${resetToken}`; + const mailOptions = { + to: user.email, + subject: "Password Reset", + text: `Click on the following link to reset your password: ${resetLink}`, + }; + transporter.sendMail(mailOptions, (emailError, info) => { + if (emailError) { + console.error("Error sending email:", emailError); + const error = + "An error occurred during the password reset process."; + res.render("forgot-password", { error, success: null }); + } else { + console.log("Email sent: " + info.response); + const success = + "Password reset email sent successfully. Check your inbox."; + res.render("forgot-password", { error: null, success }); + + // Log the successful sending of the reset link in the database + logPasswordResetActivity(user.username,"link sent successfully"); + } + }); + } } + ); } + } ); -}); + }); // Function to log the password reset activity in the database function logPasswordResetActivity(username, activity) { @@ -558,73 +587,78 @@ function logPasswordResetActivity(username, activity) { app.post("/reset-password/:token", async (req, res) => { const { token } = req.params; const { password, confirmPassword } = req.body; - + + // Sanitize the inputs + const sanitizedToken = validator.escape(token); + const sanitizedPassword = validator.escape(password); + const sanitizedConfirmPassword = validator.escape(confirmPassword); + // Find user with matching reset token and not expired const selectQuery = - "SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()"; - connection.query(selectQuery, [token], async (selectErr, selectResults) => { - if (selectErr) { - console.error("Error querying reset token:", selectErr); - return res.status(500).json({ error: "Error querying reset token" }); - } - - if (selectResults.length === 0) { - // Pass the error to the template when rendering the reset-password page - return res.render("reset-password", { - token, - resetError: "Invalid or expired reset token", - }); - } - - // Check if passwords match - if (password !== confirmPassword) { - // Pass the error to the template when rendering the reset-password page - return res.render("reset-password", { - token, - resetError: "Passwords do not match", - }); - } - - // Check if the new password meets complexity requirements - if (!isStrongPassword(password)) { - // Pass the error to the template when rendering the reset-password page - return res.render("reset-password", { - token, - resetError: - "Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.", - }); - } - - // Hash the new password - const hashedPassword = await bcrypt.hash(password, 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, token], 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"); - } + "SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()"; + connection.query(selectQuery, [sanitizedToken], async (selectErr, selectResults) => { + if (selectErr) { + console.error("Error querying reset token:", selectErr); + return res.status(500).json({ error: "Error querying reset token" }); + } + + if (selectResults.length === 0) { + // Pass the error to the template when rendering the reset-password page + return res.render("reset-password", { + token, + resetError: "Invalid or expired reset token", }); + } + + // Check if passwords match + if (sanitizedPassword !== sanitizedConfirmPassword) { + // Pass the error to the template when rendering the reset-password page + return res.render("reset-password", { + token, + resetError: "Passwords do not match", + }); + } + + // Check if the new password meets complexity requirements + if (!isStrongPassword(sanitizedPassword)) { + // Pass the error to the template when rendering the reset-password page + return res.render("reset-password", { + token, + resetError: + "Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.", + }); + } + + // Hash the new password + const hashedPassword = await bcrypt.hash(sanitizedPassword, 10); + + // Update user's password and clear reset token + const updateQuery = + "UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE reset_token = ?"; + connection.query(updateQuery, [hashedPassword, sanitizedToken], async (updateErr, updateResults) => { + if (updateErr) { + console.error("Error updating password:", updateErr); + // Pass the error to the template when rendering the reset-password page + res.render("reset-password", { + token, + resetError: "Error updating password", + }); + } else { + // Log password reset activity + const username = selectResults[0].username; // Assuming 'username' is the column name + const logQuery = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, 'Password Reseted successfully', NOW())"; + connection.query(logQuery, [username], (logErr) => { + if (logErr) { + console.error("Error logging password reset:", logErr); + // You might want to handle the logging error here + } + }); + // Redirect to the success page upon successful password reset + res.redirect("/success"); + } + }); }); -}); + }); app.get("/success", (req, res) => { res.render("success"); @@ -643,15 +677,20 @@ app.get("/reset-password/:token", (req, res) => { }); app.post("/reset-password", async (req, res) => { const { username, password, confirmPassword } = req.body; - const creatorUsername = req.session.username; + const creatorUsername = req.session.username; + + // Sanitize the inputs + const sanitizedUsername = validator.escape(username); + const sanitizedPassword = validator.escape(password); + const sanitizedConfirmPassword = validator.escape(confirmPassword); // Check if passwords match - if (password !== confirmPassword) { + if (sanitizedPassword !== sanitizedConfirmPassword) { return res.status(400).json({ error: "Passwords do not match" }); } // Check if the new password meets complexity requirements - if (!isStrongPassword(password)) { + if (!isStrongPassword(sanitizedPassword)) { return res.status(400).json({ error: "Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.", @@ -659,10 +698,10 @@ app.post("/reset-password", async (req, res) => { } // Hash the new password - const hashedPassword = await bcrypt.hash(password, 10); + const hashedPassword = await bcrypt.hash(sanitizedPassword, 10); // Check if the user exists in the database before updating the password - const userExists = await checkIfUserExists(username); + const userExists = await checkIfUserExists(sanitizedUsername); if (!userExists) { return res.status(404).json({ error: "User does not exist" }); @@ -670,7 +709,7 @@ app.post("/reset-password", async (req, res) => { // Update user's password based on the username const updateQuery = "UPDATE users SET password = ? WHERE username = ?"; - connection.query(updateQuery, [hashedPassword, username], async (updateErr, updateResults) => { + 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" }); @@ -680,7 +719,7 @@ app.post("/reset-password", async (req, res) => { 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 ${username}`; + const logActivity = `Password has been reset for ${sanitizedUsername}`; connection.query(logQuery, [creatorUsername, logActivity], (logErr) => { if (logErr) { console.error("Error logging password reset:", logErr); @@ -712,9 +751,13 @@ async function checkIfUserExists(username) { } 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, [username], (err, results) => { + connection.query(query, [sanitizedUsername], (err, results) => { if (err) { console.error('MySQL query error:', err); res.status(500).json({ success: false, error: 'Internal Server Error' }); diff --git a/package-lock.json b/package-lock.json index 859192e..fe67e20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -323,6 +323,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, "define-data-property": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", @@ -363,6 +368,39 @@ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -401,11 +439,21 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -734,6 +782,17 @@ "function-bind": "^1.1.2" } }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -792,6 +851,11 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -993,6 +1057,11 @@ } } }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1102,6 +1171,11 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1127,11 +1201,26 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" }, + "postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1214,6 +1303,19 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==", + "requires": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -1344,6 +1446,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, "sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", diff --git a/package.json b/package.json index f59d111..c71e5a7 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "otp-generator": "^4.0.1", "otplib": "^12.0.1", "qrcode": "^1.5.3", + "sanitize-html": "^2.11.0", "sequelize": "^6.35.2", "validator": "^13.11.0" }