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
commit db1b513ad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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,14 +297,40 @@ const logUserCreationActivity = async (creatorUsername, success, message) => {
} }
}; };
app.post("/createUser", async (req, res) => { 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 { try {
const { name, username, email, password, jobTitle } = req.body; 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)
@ -328,8 +355,7 @@ app.post("/createUser", async (req, res) => {
.status(400) .status(400)
.json({ .json({
error: "Username is already taken", error: "Username is already taken",
message: message: "Username is already taken. Please choose a different username.",
"Username is already taken. Please choose a different username.",
}); });
} }
@ -351,8 +377,7 @@ app.post("/createUser", async (req, res) => {
.status(400) .status(400)
.json({ .json({
error: "Email is already in use", error: "Email is already in use",
message: message: "Email is already in use. Please choose another email.",
"Email is already in use. Please choose another email.",
}); });
} }
@ -462,7 +487,8 @@ app.post("/createUser", async (req, res) => {
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,13 +502,16 @@ 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);
@ -559,10 +588,15 @@ 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" });
@ -577,7 +611,7 @@ app.post("/reset-password/:token", async (req, res) => {
} }
// Check if passwords match // Check if passwords match
if (password !== confirmPassword) { if (sanitizedPassword !== sanitizedConfirmPassword) {
// 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,
@ -586,7 +620,7 @@ app.post("/reset-password/:token", async (req, res) => {
} }
// Check if the new password meets complexity requirements // Check if the new password meets complexity requirements
if (!isStrongPassword(password)) { if (!isStrongPassword(sanitizedPassword)) {
// 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,
@ -596,12 +630,12 @@ app.post("/reset-password/:token", 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);
// Update user's password and clear reset token // Update user's password and clear reset token
const updateQuery = const updateQuery =
"UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE reset_token = ?"; "UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE reset_token = ?";
connection.query(updateQuery, [hashedPassword, token], async (updateErr, updateResults) => { connection.query(updateQuery, [hashedPassword, sanitizedToken], async (updateErr, updateResults) => {
if (updateErr) { if (updateErr) {
console.error("Error updating password:", updateErr); console.error("Error updating password:", updateErr);
// Pass the error to the template when rendering the reset-password page // Pass the error to the template when rendering the reset-password page
@ -645,13 +679,18 @@ 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"
} }