const Joi = require('joi'); const isStrongPassword = (value, helpers) => { const rules = [ { test: value.length >= 8, message: 'at least 8 characters' }, { test: /[A-Z]/.test(value), message: 'one uppercase letter' }, { test: /[a-z]/.test(value), message: 'one lowercase letter' }, { test: /\d/.test(value), message: 'one number' }, { test: /[^A-Za-z0-9]/.test(value), message: 'one special character' }, ]; const missing = rules.filter((rule) => !rule.test).map((rule) => rule.message); if (missing.length > 0) { return helpers.message(`Password must include ${missing.join(', ')}.`); } return value; }; const passwordRuleCheckers = [ { test: (value) => value.length >= 8, label: 'al menos 8 caracteres' }, { test: (value) => /[A-Z]/.test(value), label: 'una mayúscula' }, { test: (value) => /[a-z]/.test(value), label: 'una minúscula' }, { test: (value) => /\d/.test(value), label: 'un número' }, { test: (value) => /[^A-Za-z0-9]/.test(value), label: 'un carácter especial' }, ]; const getPasswordIssues = (value = '') => passwordRuleCheckers .filter((rule) => !rule.test(value)) .map((rule) => rule.label); const loginSchema = Joi.object({ email: Joi.string().email().required(), password: Joi.string().required(), }); const refreshSchema = Joi.object({ refreshToken: Joi.string().optional(), }); const changePasswordSchema = Joi.object({ currentPassword: Joi.string().required(), newPassword: Joi.string().max(100).required().custom(isStrongPassword), }); const unlockAccountSchema = Joi.object({ certPassword: Joi.string().required(), newPassword: Joi.string().max(100).required().custom(isStrongPassword), }); const validateRegisterMultipart = (req, res, next) => { const { email, password, certPassword, consentAccepted } = req.body; const issues = []; if (!email || Joi.string().email().validate(email).error) { issues.push('Correo electrónico inválido'); } if (!password) { issues.push('La contraseña es obligatoria'); } else { const passwordIssues = getPasswordIssues(password); if (passwordIssues.length > 0) { issues.push(`La contraseña debe incluir ${passwordIssues.join(', ')}`); } } if (!certPassword) { issues.push('La contraseña del certificado es obligatoria para firmar los documentos legales'); } if (!req.file) { issues.push('Debes cargar un certificado P12 o PFX'); } if (!['true', true, 'on', '1', 1].includes(consentAccepted)) { issues.push('Debes aceptar los documentos legales para completar el registro'); } if (issues.length > 0) { return res.status(400).json({ success: false, data: null, error: issues.join('; ') }); } return next(); }; const validate = (schema) => (req, res, next) => { const { error } = schema.validate(req.body, { abortEarly: false }); if (error) { const messages = error.details.map((d) => d.message); return res.status(400).json({ success: false, data: null, error: messages.join('; ') }); } next(); }; module.exports = { validate, validateRegisterMultipart, loginSchema, refreshSchema, changePasswordSchema, unlockAccountSchema, };