Persist biometric access

This commit is contained in:
2026-04-23 10:54:19 -05:00
parent ae5649f408
commit 8194c4dee4
18 changed files with 269 additions and 55 deletions
+76
View File
@@ -1,5 +1,6 @@
const bcrypt = require('bcrypt');
const { v4: uuidv4 } = require('uuid');
const crypto = require('crypto');
const authRepository = require('./auth.repository');
const passwordChangeRepository = require('./password-change.repository');
const auditService = require('../audit/audit.service');
@@ -15,6 +16,7 @@ const env = require('../../config/env');
const SALT_ROUNDS = 12;
const MAX_FAILED_LOGIN_ATTEMPTS = 3;
const createBiometricToken = () => crypto.randomBytes(48).toString('hex');
const buildDisplayNameFromEmail = (email) => {
const localPart = String(email).split('@')[0] || 'Usuario';
@@ -300,11 +302,80 @@ const refresh = async (token) => {
return { accessToken, refreshToken: newRefreshToken };
};
const issueBiometricToken = async (userId) => {
const user = await authRepository.findAuthUserById(userId);
if (!user || !user.is_active) {
const err = new Error('User not found.');
err.statusCode = 404;
throw err;
}
const biometricToken = createBiometricToken();
const expiresAt = new Date(Date.now() + env.jwt.biometricExpiresMs);
await authRepository.saveBiometricToken(userId, biometricToken, expiresAt);
return { biometricToken };
};
const biometricLogin = async (token) => {
if (!token) {
const err = new Error('La sesión biométrica expiró. Inicia sesión con correo y contraseña.');
err.statusCode = 401;
err.code = 'BIOMETRIC_EXPIRED';
throw err;
}
const record = await authRepository.findBiometricToken(token);
if (!record) {
await authRepository.deleteBiometricToken(token);
const err = new Error('La sesión biométrica expiró. Inicia sesión con correo y contraseña.');
err.statusCode = 401;
err.code = 'BIOMETRIC_EXPIRED';
throw err;
}
if (!record.is_active) {
await authRepository.deleteUserBiometricTokens(record.user_id);
const err = new Error('La sesión biométrica fue invalidada. Inicia sesión con correo y contraseña.');
err.statusCode = 401;
err.code = 'BIOMETRIC_INVALIDATED';
throw err;
}
await authRepository.deleteBiometricToken(token);
const payload = { sub: record.uid, email: record.email, role: record.role, name: record.name };
const accessToken = signAccess(payload);
const refreshToken = signRefresh(payload);
const biometricToken = createBiometricToken();
await authRepository.saveRefreshToken(record.uid, refreshToken, new Date(Date.now() + env.jwt.refreshExpiresMs));
await authRepository.saveBiometricToken(record.uid, biometricToken, new Date(Date.now() + env.jwt.biometricExpiresMs));
const signaturePreferences = await usersService.getSignaturePreferences(record.uid);
return {
accessToken,
refreshToken,
biometricToken,
user: {
id: record.uid,
name: record.name,
email: record.email,
role: record.role,
signaturePreferences,
},
};
};
const logout = async (token) => {
if (!token) return;
await authRepository.deleteRefreshToken(token);
};
const revokeBiometricToken = async (userId) => {
await authRepository.deleteUserBiometricTokens(userId);
};
const changePassword = async (userId, { currentPassword, newPassword }, context = {}) => {
const user = await authRepository.findAuthUserById(userId);
if (!user || !user.is_active) {
@@ -449,6 +520,8 @@ const confirmPasswordChange = async (token) => {
}
await authRepository.updatePassword(request.userId, request.passwordHash);
await authRepository.deleteUserRefreshTokens(request.userId);
await authRepository.deleteUserBiometricTokens(request.userId);
if (request.source === 'account_unlock') {
await authRepository.clearAccountLock(request.userId);
}
@@ -485,7 +558,10 @@ module.exports = {
register,
login,
refresh,
issueBiometricToken,
biometricLogin,
logout,
revokeBiometricToken,
verifyEmail,
changePassword,
confirmPasswordChange,