Merge pull request #4 from Newtbot/XSS-FOR-ALL-DONE

XSS PREVENTION FOR ALL DONE
This commit is contained in:
noot
2024-01-04 13:32:59 +08:00
committed by GitHub
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',
[
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; 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
@ -475,71 +501,74 @@ app.get("/forgot-password", (req, res) => {
// Handle the submission of the forgot password form // Handle the submission of the forgot password form
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 });
} 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 }); res.render("forgot-password", { error, success: null });
} else if (checkResults.length === 0) { } else {
const error = "Username or email not found."; // Send email with reset link
res.render("forgot-password", { error, success: null }); const resetLink = `http://localhost:3000/reset-password/${resetToken}`;
} else { const mailOptions = {
// Assuming the user exists, generate a reset token and send an email to: user.email,
const user = checkResults[0]; subject: "Password Reset",
const resetToken = crypto.randomBytes(20).toString("hex"); text: `Click on the following link to reset your password: ${resetLink}`,
const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour };
transporter.sendMail(mailOptions, (emailError, info) => {
// Update user with reset token and expiry if (emailError) {
const updateQuery = console.error("Error sending email:", emailError);
"UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?"; const error =
connection.query( "An error occurred during the password reset process.";
updateQuery, res.render("forgot-password", { error, success: null });
[resetToken, resetTokenExpiry, user.id], } else {
(updateError) => { console.log("Email sent: " + info.response);
if (updateError) { const success =
console.error("Error updating reset token:", updateError); "Password reset email sent successfully. Check your inbox.";
const error = res.render("forgot-password", { error: null, success });
"An error occurred during the password reset process.";
res.render("forgot-password", { error, success: null }); // Log the successful sending of the reset link in the database
} else { logPasswordResetActivity(user.username,"link sent successfully");
// 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 to log the password reset activity in the database
function logPasswordResetActivity(username, activity) { function logPasswordResetActivity(username, activity) {
@ -558,73 +587,78 @@ function logPasswordResetActivity(username, activity) {
app.post("/reset-password/:token", async (req, res) => { 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"
} }