erp-construccion-backend/dist/modules/auth/services/auth.service.js
rckrdmrd 9bddee7320 chore: Remove dist/ from git tracking (now in .gitignore)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 11:47:55 -06:00

330 lines
11 KiB
JavaScript

"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,
userId: 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