"use strict"; /** * AuthService - Servicio de Autenticación * * Gestiona login, logout, refresh tokens y validación de JWT. * Implementa patrón multi-tenant con verificación de tenant_id. * * @module Auth */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthService = void 0; const jwt = __importStar(require("jsonwebtoken")); const bcrypt = __importStar(require("bcryptjs")); class AuthService { userRepository; tenantRepository; refreshTokenRepository; jwtSecret; jwtExpiresIn; jwtRefreshExpiresIn; constructor(userRepository, tenantRepository, refreshTokenRepository) { this.userRepository = userRepository; this.tenantRepository = tenantRepository; this.refreshTokenRepository = refreshTokenRepository; this.jwtSecret = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production-minimum-32-chars'; this.jwtExpiresIn = process.env.JWT_EXPIRES_IN || '1d'; this.jwtRefreshExpiresIn = process.env.JWT_REFRESH_EXPIRES_IN || '7d'; } /** * Login de usuario */ async login(dto) { // Buscar usuario por email const user = await this.userRepository.findOne({ where: { email: dto.email, deletedAt: null }, relations: ['userRoles', 'userRoles.role'], }); if (!user) { throw new Error('Invalid credentials'); } // Verificar password const isPasswordValid = await bcrypt.compare(dto.password, user.passwordHash); if (!isPasswordValid) { throw new Error('Invalid credentials'); } // Verificar que el usuario esté activo if (!user.isActive) { throw new Error('User is not active'); } // Obtener tenant const tenantId = dto.tenantId || user.defaultTenantId; if (!tenantId) { throw new Error('No tenant specified'); } const tenant = await this.tenantRepository.findOne({ where: { id: tenantId, isActive: true, deletedAt: null }, }); if (!tenant) { throw new Error('Tenant not found or inactive'); } // Obtener roles del usuario const roles = user.userRoles?.map((ur) => ur.role.code) || []; // Generar tokens const accessToken = this.generateAccessToken(user, tenantId, roles); const refreshToken = await this.generateRefreshToken(user.id); // Actualizar último login await this.userRepository.update(user.id, { lastLoginAt: new Date() }); return { accessToken, refreshToken, expiresIn: this.getExpiresInSeconds(this.jwtExpiresIn), user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, roles, }, tenant: { id: tenant.id, name: tenant.name, }, }; } /** * Registro de usuario */ async register(dto) { // Verificar si el email ya existe const existingUser = await this.userRepository.findOne({ where: { email: dto.email }, }); if (existingUser) { throw new Error('Email already registered'); } // Verificar que el tenant existe const tenant = await this.tenantRepository.findOne({ where: { id: dto.tenantId, isActive: true }, }); if (!tenant) { throw new Error('Tenant not found'); } // Hash del password const passwordHash = await bcrypt.hash(dto.password, 12); // Crear usuario const user = await this.userRepository.save(this.userRepository.create({ email: dto.email, passwordHash, firstName: dto.firstName, lastName: dto.lastName, defaultTenantId: dto.tenantId, isActive: true, })); // Generar tokens (rol default: user) const roles = ['user']; const accessToken = this.generateAccessToken(user, dto.tenantId, roles); const refreshToken = await this.generateRefreshToken(user.id); return { accessToken, refreshToken, expiresIn: this.getExpiresInSeconds(this.jwtExpiresIn), user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, roles, }, tenant: { id: tenant.id, name: tenant.name, }, }; } /** * Refresh de token */ async refresh(dto) { // Validar refresh token const validation = this.validateToken(dto.refreshToken, 'refresh'); if (!validation.valid || !validation.payload) { throw new Error('Invalid refresh token'); } // Verificar que el token no está revocado const storedToken = await this.refreshTokenRepository.findOne({ where: { token: dto.refreshToken, revokedAt: null }, }); if (!storedToken || storedToken.expiresAt < new Date()) { throw new Error('Refresh token expired or revoked'); } // Obtener usuario const user = await this.userRepository.findOne({ where: { id: validation.payload.sub, deletedAt: null }, relations: ['userRoles', 'userRoles.role'], }); if (!user || !user.isActive) { throw new Error('User not found or inactive'); } // Obtener tenant const tenant = await this.tenantRepository.findOne({ where: { id: validation.payload.tenantId, isActive: true }, }); if (!tenant) { throw new Error('Tenant not found or inactive'); } const roles = user.userRoles?.map((ur) => ur.role.code) || []; // Revocar token anterior await this.refreshTokenRepository.update(storedToken.id, { revokedAt: new Date() }); // Generar nuevos tokens const accessToken = this.generateAccessToken(user, tenant.id, roles); const refreshToken = await this.generateRefreshToken(user.id); return { accessToken, refreshToken, expiresIn: this.getExpiresInSeconds(this.jwtExpiresIn), user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, roles, }, tenant: { id: tenant.id, name: tenant.name, }, }; } /** * Logout - Revocar refresh token */ async logout(refreshToken) { await this.refreshTokenRepository.update({ token: refreshToken }, { revokedAt: new Date() }); } /** * Cambiar password */ async changePassword(userId, dto) { const user = await this.userRepository.findOne({ where: { id: userId }, }); if (!user) { throw new Error('User not found'); } const isCurrentValid = await bcrypt.compare(dto.currentPassword, user.passwordHash); if (!isCurrentValid) { throw new Error('Current password is incorrect'); } const newPasswordHash = await bcrypt.hash(dto.newPassword, 12); await this.userRepository.update(userId, { passwordHash: newPasswordHash }); // Revocar todos los refresh tokens del usuario await this.refreshTokenRepository.update({ userId }, { revokedAt: new Date() }); } /** * Validar access token */ validateAccessToken(token) { return this.validateToken(token, 'access'); } /** * Validar token */ validateToken(token, expectedType) { try { const payload = jwt.verify(token, this.jwtSecret); if (payload.type !== expectedType) { return { valid: false, error: 'Invalid token type' }; } return { valid: true, payload }; } catch (error) { if (error instanceof jwt.TokenExpiredError) { return { valid: false, error: 'Token expired' }; } if (error instanceof jwt.JsonWebTokenError) { return { valid: false, error: 'Invalid token' }; } return { valid: false, error: 'Token validation failed' }; } } /** * Generar access token */ generateAccessToken(user, tenantId, roles) { const payload = { sub: user.id, email: user.email, tenantId, roles, type: 'access', }; return jwt.sign(payload, this.jwtSecret, { expiresIn: this.jwtExpiresIn, }); } /** * Generar refresh token */ async generateRefreshToken(userId) { const payload = { sub: userId, type: 'refresh', }; const token = jwt.sign(payload, this.jwtSecret, { expiresIn: this.jwtRefreshExpiresIn, }); // Almacenar en DB const expiresAt = new Date(); expiresAt.setDate(expiresAt.getDate() + 7); // 7 días await this.refreshTokenRepository.save(this.refreshTokenRepository.create({ userId, token, expiresAt, })); return token; } /** * Convertir expiresIn a segundos */ getExpiresInSeconds(expiresIn) { const match = expiresIn.match(/^(\d+)([dhms])$/); if (!match) return 86400; // default 1 día const value = parseInt(match[1]); const unit = match[2]; switch (unit) { case 'd': return value * 86400; case 'h': return value * 3600; case 'm': return value * 60; case 's': return value; default: return 86400; } } } exports.AuthService = AuthService; //# sourceMappingURL=auth.service.js.map