Updated profile and post capabilities

This commit is contained in:
Sven laptop
2026-07-29 22:35:36 +02:00
parent eaeb0a29bc
commit 494b583cbc
9 changed files with 485 additions and 34 deletions
+121 -11
View File
@@ -57,45 +57,149 @@ function validateCommonFields(values, { requireName = true } = {}) {
}
}
function validateRegistrationInput(values) {
validateCommonFields(values);
function validatePasswordRules(password, { username, email }) {
if (!password || password.length > LIMITS.password) {
throw new Error(`password must be between 1 and ${LIMITS.password} characters`);
}
if (values.password.length < MIN_PASSWORD_LENGTH) {
if (password.length < MIN_PASSWORD_LENGTH) {
throw new Error(`password must be at least ${MIN_PASSWORD_LENGTH} characters`);
}
const passwordLower = values.password.toLowerCase();
const passwordLower = password.toLowerCase();
const normalizedPassword = passwordLower.replace(/[^a-z0-9]/g, '');
const emailLocalPart = values.email.split('@')[0];
const emailLocalPart = email.split('@')[0];
if (
COMMON_PASSWORDS.has(normalizedPassword) ||
passwordLower.includes(values.username.toLowerCase()) ||
passwordLower.includes(username.toLowerCase()) ||
passwordLower.includes(emailLocalPart)
) {
throw new Error('password is too common or contains account details');
}
if (!/[a-z]/.test(values.password) || !/[A-Z]/.test(values.password)) {
if (!/[a-z]/.test(password) || !/[A-Z]/.test(password)) {
throw new Error('password must contain lowercase and uppercase letters');
}
if (!/[0-9]/.test(values.password)) {
if (!/[0-9]/.test(password)) {
throw new Error('password must contain a number');
}
if (!/[^a-zA-Z0-9\s]/.test(values.password)) {
if (!/[^a-zA-Z0-9\s]/.test(password)) {
throw new Error('password must contain a special character');
}
if (/(.)\1\1/.test(values.password) || /(0123|1234|2345|3456|4567|5678|6789|abcd|bcde|cdef)/i.test(values.password)) {
if (/(.)\1\1/.test(password) || /(0123|1234|2345|3456|4567|5678|6789|abcd|bcde|cdef)/i.test(password)) {
throw new Error('password must not contain obvious repeated or sequential characters');
}
}
function validateRegistrationInput(values) {
validateCommonFields(values);
validatePasswordRules(values.password, {
username: values.username,
email: values.email,
});
if (isDisposableEmail(values.email)) {
throw new Error('temporary email addresses are not allowed');
}
}
function cleanProfileUpdateInput(input) {
const body = input || {};
const values = {};
for (const field of ['name', 'username', 'email', 'password', 'current_password']) {
if (body[field] !== undefined) {
const preserveWhitespace = field === 'password' || field === 'current_password';
let value = cleanText(body[field], { trim: !preserveWhitespace });
if (field === 'email') {
value = value.toLowerCase();
}
values[field] = value;
}
}
return values;
}
function validateOptionalPersonField(field, value) {
if (!value || value.length > LIMITS[field]) {
throw new Error(`${field} must be between 1 and ${LIMITS[field]} characters`);
}
if (field === 'username' && /\s/.test(value)) {
throw new Error('username must not contain spaces');
}
if (field === 'email') {
if (/\s/.test(value)) {
throw new Error('email must not contain spaces');
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
throw new Error('email must be a valid email address');
}
if (isDisposableEmail(value)) {
throw new Error('temporary email addresses are not allowed');
}
}
}
function validateProfileUpdateInput(values, { hasImageChange = false } = {}) {
const changeFields = ['name', 'username', 'email', 'password'].filter((field) => values[field] !== undefined);
if (changeFields.length === 0 && !hasImageChange) {
throw new Error('At least one profile field must be changed');
}
if (values.password !== undefined && values.current_password === undefined) {
throw new Error('current_password is required when changing password');
}
if (values.current_password !== undefined && values.password === undefined) {
throw new Error('password is required when submitting current_password');
}
for (const field of ['name', 'username', 'email']) {
if (values[field] !== undefined) {
validateOptionalPersonField(field, values[field]);
}
}
if (values.current_password !== undefined && (
!values.current_password || values.current_password.length > LIMITS.password
)) {
throw new Error(`current_password must be between 1 and ${LIMITS.password} characters`);
}
}
function sanitizeProfileUpdate(req, res, next) {
try {
const values = cleanProfileUpdateInput(req.body);
const removeImageValue = req.body?.remove_image;
const hasImageChange = Boolean(req.file) || removeImageValue === 'true';
validateProfileUpdateInput(values, { hasImageChange });
req.profileUpdate = values;
next();
}
catch (err) {
res.status(400).send(err.message);
}
}
function sanitizeAccountDelete(req, res, next) {
try {
const password = cleanText((req.body || {}).password, { trim: false });
if (!password || password.length > LIMITS.password) {
throw new Error(`password must be between 1 and ${LIMITS.password} characters`);
}
req.accountDelete = { password };
next();
}
catch (err) {
res.status(400).send(err.message);
}
}
function sanitizePersonInput(req, res, next) {
try {
const values = cleanPersonInput(req.body || {});
@@ -120,4 +224,10 @@ function sanitizeLoginInput(req, res, next) {
}
}
module.exports = { sanitizePersonInput, sanitizeLoginInput };
module.exports = {
sanitizePersonInput,
sanitizeLoginInput,
sanitizeProfileUpdate,
sanitizeAccountDelete,
validatePasswordRules,
};