se agrega los requerimientos de la LODPD con firma de documentos
This commit is contained in:
@@ -9,7 +9,18 @@ const getContext = (req) => ({
|
||||
|
||||
const register = async (req, res, next) => {
|
||||
try {
|
||||
const result = await authService.register(req.body);
|
||||
if (!req.file) {
|
||||
const error = new Error('Debes cargar un certificado P12 o PFX.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
const result = await authService.register({
|
||||
email: req.body.email,
|
||||
password: req.body.password,
|
||||
certPassword: req.body.certPassword,
|
||||
certificateAlias: req.body.certificateAlias,
|
||||
certificateFile: req.file,
|
||||
}, getContext(req));
|
||||
return sendSuccess(res, {
|
||||
message: 'Registration successful. Please check your email to verify your account.',
|
||||
userId: result.userId,
|
||||
|
||||
@@ -98,6 +98,10 @@ const updatePassword = async (userId, passwordHash) => {
|
||||
);
|
||||
};
|
||||
|
||||
const deleteUser = async (userId) => {
|
||||
await db.execute('DELETE FROM users WHERE id = ?', [userId]);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
findByEmail,
|
||||
findById,
|
||||
@@ -112,4 +116,5 @@ module.exports = {
|
||||
setResetToken,
|
||||
findByResetToken,
|
||||
updatePassword,
|
||||
deleteUser,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const express = require('express');
|
||||
const authController = require('./auth.controller');
|
||||
const { validate, registerSchema, loginSchema, refreshSchema, changePasswordSchema } = require('./auth.validator');
|
||||
const { validate, validateRegisterMultipart, loginSchema, refreshSchema, changePasswordSchema } = require('./auth.validator');
|
||||
const authenticate = require('../../middlewares/authenticate');
|
||||
const { uploadTempCertificate } = require('../../config/multer');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/register', validate(registerSchema), authController.register);
|
||||
router.post('/register', uploadTempCertificate.single('certificateFile'), validateRegisterMultipart, authController.register);
|
||||
router.post('/login', validate(loginSchema), authController.login);
|
||||
router.post('/refresh', validate(refreshSchema), authController.refresh);
|
||||
router.post('/logout', authController.logout);
|
||||
|
||||
@@ -5,7 +5,12 @@ const passwordChangeRepository = require('./password-change.repository');
|
||||
const auditService = require('../audit/audit.service');
|
||||
const usersService = require('../users/users.service');
|
||||
const mailService = require('../mail/mail.service');
|
||||
const certificatesService = require('../certificates/certificates.service');
|
||||
const legalService = require('../legal/legal.service');
|
||||
const { getCertificatePath } = require('../../utils/storagePath');
|
||||
const { signAccess, signRefresh } = require('../../utils/jwt');
|
||||
const fs = require('fs/promises');
|
||||
const path = require('path');
|
||||
|
||||
const SALT_ROUNDS = 12;
|
||||
|
||||
@@ -37,7 +42,7 @@ const issueVerificationEmail = async (user) => {
|
||||
};
|
||||
};
|
||||
|
||||
const register = async ({ email, password }) => {
|
||||
const register = async ({ email, password, certPassword, certificateFile, certificateAlias }, context = {}) => {
|
||||
const existing = await authRepository.findByEmail(email);
|
||||
if (existing) {
|
||||
const err = new Error('Email already registered.');
|
||||
@@ -57,17 +62,56 @@ const register = async ({ email, password }) => {
|
||||
verificationToken,
|
||||
verificationTokenExpires,
|
||||
});
|
||||
let mailResult;
|
||||
|
||||
let uploadedCertificate = null;
|
||||
let legalDocuments = [];
|
||||
|
||||
try {
|
||||
mailResult = await mailService.sendVerificationEmail({
|
||||
const certificateDir = await getCertificatePath(userId);
|
||||
const certificateFilename = path.basename(certificateFile.path);
|
||||
const finalCertificatePath = path.join(certificateDir, certificateFilename);
|
||||
|
||||
await fs.rename(certificateFile.path, finalCertificatePath);
|
||||
|
||||
uploadedCertificate = await certificatesService.upload({
|
||||
...certificateFile,
|
||||
path: finalCertificatePath,
|
||||
filename: certificateFilename,
|
||||
}, certPassword, userId, certificateAlias || certificateFile.originalname, context);
|
||||
|
||||
const p12Buffer = await fs.readFile(finalCertificatePath);
|
||||
legalDocuments = await legalService.issueInitialLegalPackage({
|
||||
userId,
|
||||
userName: name,
|
||||
userEmail: email,
|
||||
signerName: uploadedCertificate.subject_cn || name,
|
||||
p12Buffer,
|
||||
p12Password: certPassword,
|
||||
context,
|
||||
});
|
||||
|
||||
const attachments = legalDocuments.map((item) => ({
|
||||
filename: item.signedFilename,
|
||||
path: item.signedPath,
|
||||
}));
|
||||
|
||||
await mailService.sendVerificationEmail({
|
||||
name,
|
||||
email,
|
||||
verificationToken,
|
||||
attachments,
|
||||
});
|
||||
} catch (error) {
|
||||
const err = new Error('The account was created, but the verification email could not be sent. Please contact support or try again later.');
|
||||
err.statusCode = 503;
|
||||
err.code = 'MAIL_SEND_FAILED';
|
||||
await authRepository.deleteUser(userId);
|
||||
try {
|
||||
if (certificateFile?.path) {
|
||||
await fs.unlink(certificateFile.path);
|
||||
}
|
||||
} catch {}
|
||||
const err = new Error('No se pudo completar el registro legal inicial. Revisa el certificado, la contraseña del P12 y vuelve a intentarlo.');
|
||||
err.statusCode = error.statusCode || 503;
|
||||
err.code = error.code || 'REGISTER_LEGAL_FLOW_FAILED';
|
||||
err.message = error.message || err.message;
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -77,10 +121,16 @@ const register = async ({ email, password }) => {
|
||||
email,
|
||||
metadata: {
|
||||
verificationEmailSent: true,
|
||||
legalPackageCreated: true,
|
||||
legalVersion: '1.0.1',
|
||||
},
|
||||
});
|
||||
|
||||
return { userId, verificationToken, verificationUrl: mailResult.verificationUrl };
|
||||
return {
|
||||
userId,
|
||||
verificationToken,
|
||||
legalDocuments: legalDocuments.map(({ signedPath, originalPath, ...item }) => item),
|
||||
};
|
||||
};
|
||||
|
||||
const login = async ({ email, password }, context = {}) => {
|
||||
|
||||
@@ -17,10 +17,17 @@ const isStrongPassword = (value, helpers) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
const registerSchema = Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().max(100).required().custom(isStrongPassword),
|
||||
});
|
||||
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(),
|
||||
@@ -36,6 +43,42 @@ const changePasswordSchema = Joi.object({
|
||||
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) {
|
||||
@@ -45,4 +88,4 @@ const validate = (schema) => (req, res, next) => {
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = { validate, registerSchema, loginSchema, refreshSchema, changePasswordSchema };
|
||||
module.exports = { validate, validateRegisterMultipart, loginSchema, refreshSchema, changePasswordSchema };
|
||||
|
||||
Reference in New Issue
Block a user