XSS PREVENTION FOR ALL DONE

This commit is contained in:
BIG2EYEZ 2024-01-03 18:11:45 +08:00
parent 7de5b55ce8
commit 89c720bd30
3 changed files with 431 additions and 280 deletions

View File

@ -7,6 +7,7 @@ const crypto = require("crypto");
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const otpGenerator = require('otp-generator'); const otpGenerator = require('otp-generator');
const { body, validationResult } = require('express-validator'); const { body, validationResult } = require('express-validator');
const validator = require('validator');
const { transporter } = require("./modules/nodeMailer"); const { transporter } = require("./modules/nodeMailer");
const { connection } = require("./modules/mysql"); const { connection } = require("./modules/mysql");
@ -296,173 +297,198 @@ const logUserCreationActivity = async (creatorUsername, success, message) => {
} }
}; };
app.post("/createUser", async (req, res) => { app.post(
try { '/createUser',
const { name, username, email, password, jobTitle } = req.body; [
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 // 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 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)) { if (!isStrongPassword(password)) {
return res return res
.status(400) .status(400)
.json({ error: "Password does not meet complexity requirements" }); .json({ error: "Password does not meet complexity requirements" });
} }
// Check if the username is already taken // Check if the username is already taken
const checkUsernameQuery = "SELECT * FROM users WHERE username = ?"; const checkUsernameQuery = "SELECT * FROM users WHERE username = ?";
connection.query( connection.query(
checkUsernameQuery, checkUsernameQuery,
[username], [username],
(usernameQueryErr, usernameResults) => { (usernameQueryErr, usernameResults) => {
if (usernameQueryErr) { if (usernameQueryErr) {
console.error("Error checking username:", usernameQueryErr); console.error("Error checking username:", usernameQueryErr);
return res.status(500).json({ error: "Internal Server Error" }); 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" });
});
}
);
});
});
}
);
} }
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); console.error("Error creating user:", error);
// Log unsuccessful user creation due to an 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 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" }); res.status(500).json({ error: "Internal Server Error" });
}
} }
}); );
app.get("/forgot-password", (req, res) => { app.get("/forgot-password", (req, res) => {
res.render("forgot-password"); // Assuming you have an EJS template for this res.render("forgot-password"); // Assuming you have an EJS template for this
@ -476,70 +502,73 @@ app.get("/forgot-password", (req, res) => {
app.post("/forgot-password", (req, res) => { app.post("/forgot-password", (req, res) => {
const { usernameOrEmail } = req.body; const { usernameOrEmail } = req.body;
// Sanitize the input
const sanitizedUsernameOrEmail = validator.escape(usernameOrEmail);
// Perform the logic for sending the reset password email // Perform the logic for sending the reset password email
// Check if the username or email exists in the database // Check if the username or email exists in the database
const checkUserQuery = "SELECT * FROM users WHERE username = ? OR email = ?"; const checkUserQuery = "SELECT * FROM users WHERE username = ? OR email = ?";
connection.query( connection.query(
checkUserQuery, checkUserQuery,
[usernameOrEmail, usernameOrEmail], [sanitizedUsernameOrEmail, sanitizedUsernameOrEmail],
(checkError, checkResults) => { (checkError, checkResults) => {
if (checkError) { if (checkError) {
console.error("Error checking user:", checkError); console.error("Error checking user:", checkError);
const error = "An error occurred during the password reset process."; const error = "An error occurred during the password reset process.";
res.render("forgot-password", { error, success: null }); res.render("forgot-password", { error, success: null });
} else if (checkResults.length === 0) { } else if (checkResults.length === 0) {
const error = "Username or email not found."; const error = "Username or email not found.";
res.render("forgot-password", { error, success: null }); res.render("forgot-password", { error, success: null });
} else { } else {
// Assuming the user exists, generate a reset token and send an email // Assuming the user exists, generate a reset token and send an email
const user = checkResults[0]; const user = checkResults[0];
const resetToken = crypto.randomBytes(20).toString("hex"); const resetToken = crypto.randomBytes(20).toString("hex");
const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour
// Update user with reset token and expiry // Update user with reset token and expiry
const updateQuery = const updateQuery =
"UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?"; "UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?";
connection.query( connection.query(
updateQuery, updateQuery,
[resetToken, resetTokenExpiry, user.id], [resetToken, resetTokenExpiry, user.id],
(updateError) => { (updateError) => {
if (updateError) { if (updateError) {
console.error("Error updating reset token:", updateError); console.error("Error updating reset token:", updateError);
const error = const error =
"An error occurred during the password reset process."; "An error occurred during the password reset process.";
res.render("forgot-password", { error, success: null }); res.render("forgot-password", { error, success: null });
} else { } else {
// Send email with reset link // Send email with reset link
const resetLink = `http://localhost:3000/reset-password/${resetToken}`; const resetLink = `http://localhost:3000/reset-password/${resetToken}`;
const mailOptions = { const mailOptions = {
to: user.email, to: user.email,
subject: "Password Reset", subject: "Password Reset",
text: `Click on the following link to reset your password: ${resetLink}`, text: `Click on the following link to reset your password: ${resetLink}`,
}; };
transporter.sendMail(mailOptions, (emailError, info) => { transporter.sendMail(mailOptions, (emailError, info) => {
if (emailError) { if (emailError) {
console.error("Error sending email:", emailError); console.error("Error sending email:", emailError);
const error = const error =
"An error occurred during the password reset process."; "An error occurred during the password reset process.";
res.render("forgot-password", { error, success: null }); res.render("forgot-password", { error, success: null });
} else { } else {
console.log("Email sent: " + info.response); console.log("Email sent: " + info.response);
const success = const success =
"Password reset email sent successfully. Check your inbox."; "Password reset email sent successfully. Check your inbox.";
res.render("forgot-password", { error: null, success }); res.render("forgot-password", { error: null, success });
// Log the successful sending of the reset link in the database // Log the successful sending of the reset link in the database
logPasswordResetActivity(user.username,"link sent successfully"); logPasswordResetActivity(user.username,"link sent successfully");
} }
}); });
} }
}
);
} }
);
} }
}
); );
}); });
// Function to log the password reset activity in the database // Function to log the password reset activity in the database
function logPasswordResetActivity(username, activity) { function logPasswordResetActivity(username, activity) {
@ -559,72 +588,77 @@ app.post("/reset-password/:token", async (req, res) => {
const { token } = req.params; const { token } = req.params;
const { password, confirmPassword } = req.body; 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 // Find user with matching reset token and not expired
const selectQuery = const selectQuery =
"SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()"; "SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()";
connection.query(selectQuery, [token], async (selectErr, selectResults) => { connection.query(selectQuery, [sanitizedToken], async (selectErr, selectResults) => {
if (selectErr) { if (selectErr) {
console.error("Error querying reset token:", selectErr); console.error("Error querying reset token:", selectErr);
return res.status(500).json({ error: "Error querying reset token" }); return res.status(500).json({ error: "Error querying reset token" });
} }
if (selectResults.length === 0) { if (selectResults.length === 0) {
// Pass the error to the template when rendering the reset-password page // Pass the error to the template when rendering the reset-password page
return res.render("reset-password", { return res.render("reset-password", {
token, token,
resetError: "Invalid or expired reset 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");
}
}); });
}
// 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) => { app.get("/success", (req, res) => {
res.render("success"); res.render("success");
@ -643,15 +677,20 @@ app.get("/reset-password/:token", (req, res) => {
}); });
app.post("/reset-password", async (req, res) => { app.post("/reset-password", async (req, res) => {
const { username, password, confirmPassword } = req.body; 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 // Check if passwords match
if (password !== confirmPassword) { if (sanitizedPassword !== sanitizedConfirmPassword) {
return res.status(400).json({ error: "Passwords do not match" }); return res.status(400).json({ error: "Passwords do not match" });
} }
// Check if the new password meets complexity requirements // Check if the new password meets complexity requirements
if (!isStrongPassword(password)) { if (!isStrongPassword(sanitizedPassword)) {
return res.status(400).json({ return res.status(400).json({
error: 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.", "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 // 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 // Check if the user exists in the database before updating the password
const userExists = await checkIfUserExists(username); const userExists = await checkIfUserExists(sanitizedUsername);
if (!userExists) { if (!userExists) {
return res.status(404).json({ error: "User does not exist" }); 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 // Update user's password based on the username
const updateQuery = "UPDATE users SET password = ? WHERE 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) { if (updateErr) {
console.error("Error updating password:", updateErr); console.error("Error updating password:", updateErr);
return res.status(500).json({ error: "Error updating password" }); return res.status(500).json({ error: "Error updating password" });
@ -680,7 +719,7 @@ app.post("/reset-password", async (req, res) => {
if (updateResults.affectedRows > 0) { if (updateResults.affectedRows > 0) {
// Log password reset activity // Log password reset activity
const logQuery = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, NOW())"; 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) => { connection.query(logQuery, [creatorUsername, logActivity], (logErr) => {
if (logErr) { if (logErr) {
console.error("Error logging password reset:", logErr); console.error("Error logging password reset:", logErr);
@ -712,9 +751,13 @@ async function checkIfUserExists(username) {
} }
app.get('/searchUser', (req, res) => { app.get('/searchUser', (req, res) => {
const { username } = req.query; const { username } = req.query;
// Sanitize the input
const sanitizedUsername = validator.escape(username);
const query = 'SELECT * FROM users WHERE username = ?'; const query = 'SELECT * FROM users WHERE username = ?';
connection.query(query, [username], (err, results) => { connection.query(query, [sanitizedUsername], (err, results) => {
if (err) { if (err) {
console.error('MySQL query error:', err); console.error('MySQL query error:', err);
res.status(500).json({ success: false, error: 'Internal Server Error' }); res.status(500).json({ success: false, error: 'Internal Server Error' });

107
package-lock.json generated
View File

@ -323,6 +323,11 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" "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": { "define-data-property": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", "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", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" "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": { "dotenv": {
"version": "16.3.1", "version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "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", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" "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": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" "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": { "etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@ -734,6 +782,17 @@
"function-bind": "^1.1.2" "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": { "http-errors": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" "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": { "is-property": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "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": { "negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "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", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" "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": { "parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "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", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" "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": { "pngjs": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" "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": { "proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "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", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "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": { "semver": {
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "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", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" "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": { "sqlstring": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",

View File

@ -31,6 +31,7 @@
"otp-generator": "^4.0.1", "otp-generator": "^4.0.1",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"sanitize-html": "^2.11.0",
"sequelize": "^6.35.2", "sequelize": "^6.35.2",
"validator": "^13.11.0" "validator": "^13.11.0"
} }