UNPKG

nest-authify

Version:

Complete authentication and authorization package for NestJS - Monolith and Microservices ready with OAuth, JWT, Redis sessions

320 lines 13.2 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseAuthService = void 0; const common_1 = require("@nestjs/common"); const config_1 = require("@nestjs/config"); const jwt_1 = require("@nestjs/jwt"); const uuid_1 = require("uuid"); const hash_service_1 = require("./hash.service"); let BaseAuthService = class BaseAuthService { constructor(jwtService, sessionStore, hashService, repository, configService) { this.jwtService = jwtService; this.sessionStore = sessionStore; this.hashService = hashService; this.repository = repository; this.configService = configService; this.logger = new common_1.Logger("NestAuthify"); this.debugEnabled = !!(this.configService?.get('AUTHIFY_DEBUG') === 'true'); } async createJwt(user, expiresIn, sessionId) { if (this.debugEnabled) { this.logger.debug('🔑 Creando JWT', { userId: user.id, expiresIn, sessionId: sessionId || 'nuevo' }); } const payload = { sub: user.id, username: user.username, email: user.email, roles: user.roles || [], permissions: user.permissions || [], sessionId: sessionId || (0, uuid_1.v4)(), }; if (this.debugEnabled) { this.logger.debug('✅ JWT creado exitosamente'); } return this.jwtService.signAsync(payload, { expiresIn: expiresIn || this.configService?.get('JWT_EXPIRES_IN', '60m'), }); } async createRefreshToken(user, expiresIn, sessionId) { if (this.debugEnabled) { this.logger.debug('🔄 Creando refresh token', { userId: user.id, expiresIn }); } const payload = { sub: user.id, sessionId: sessionId || (0, uuid_1.v4)(), }; const token = await this.jwtService.signAsync(payload, { expiresIn: expiresIn || this.configService?.get('REFRESH_EXPIRES_IN', '7d'), }); if (this.debugEnabled) { this.logger.debug('✅ Refresh token creado'); } return token; } async createSession(user, options) { if (this.debugEnabled) { this.logger.debug('🎯 Iniciando creación de sesión', { userId: user.id, provider: options?.provider || 'local' }); } const sessionId = (0, uuid_1.v4)(); const accessToken = await this.createJwt(user, options?.expiresIn, sessionId); const refreshToken = await this.createRefreshToken(user, options?.refreshExpiresIn, sessionId); const sessionData = { userId: user.id, sessionId, createdAt: new Date().toISOString(), provider: options?.provider || 'local', metadata: options?.metadata || {}, }; const ttl = this.parseDuration(options?.refreshExpiresIn || this.configService?.get('REFRESH_EXPIRES_IN', '7d')); if (this.debugEnabled) { this.logger.debug('💾 Guardando sesión en store', { sessionId, ttl: `${ttl}s`, key: `session:${sessionId}` }); } await this.sessionStore.set(`session:${sessionId}`, sessionData, ttl); const expiresIn = this.parseDuration(options?.expiresIn || this.configService?.get('JWT_EXPIRES_IN', '60m')); if (this.debugEnabled) { this.logger.debug('✅ Sesión creada y guardada'); } return { accessToken, refreshToken, expiresIn, tokenType: 'Bearer', sub: user.id, sessionId, ...options?.metadata, }; } async verifyToken(token) { if (this.debugEnabled) { this.logger.debug('🔍 Verificando token'); } try { const payload = await this.jwtService.verifyAsync(token); if (this.debugEnabled) { this.logger.debug('✅ Token verificado', { userId: payload.sub, sessionId: payload.sessionId?.substring(0, 8) + '...' }); } if (payload.sessionId) { const sessionExists = await this.sessionStore.exists(`session:${payload.sessionId}`); if (!sessionExists) { if (this.debugEnabled) { this.logger.debug('❌ Sesión revocada'); } throw new common_1.UnauthorizedException('Session has been revoked'); } if (this.debugEnabled) { this.logger.debug('✅ Sesión válida'); } } return payload; } catch (error) { console.error(error); throw new common_1.UnauthorizedException('Invalid or expired token'); } } async refreshAccessToken(refreshToken) { try { if (this.debugEnabled) { this.logger.debug('🔄 Refrescando access token'); } const payload = await this.jwtService.verifyAsync(refreshToken); if (payload.sessionId) { const sessionExists = await this.sessionStore.exists(`session:${payload.sessionId}`); if (!sessionExists) { throw new common_1.UnauthorizedException('Session has been revoked'); } } const user = await this.getUserById(payload.sub); if (!user) { throw new common_1.UnauthorizedException('User not found'); } const accessToken = await this.createJwt(user, undefined, payload.sessionId); const expiresIn = this.parseDuration(this.configService?.get('JWT_EXPIRES_IN', '60m')); if (this.debugEnabled) { this.logger.debug('✅ Access token refrescado', { userId: user.id }); } return { accessToken, expiresIn }; } catch (error) { throw new common_1.UnauthorizedException('Invalid or expired refresh token'); } } async revokeSession(sessionId) { if (this.debugEnabled) { this.logger.debug('🗑️ Revocando sesión', { sessionId: sessionId.substring(0, 8) + '...' }); } await this.sessionStore.delete(`session:${sessionId}`); if (this.debugEnabled) { this.logger.debug('✅ Sesión revocada'); } } async revokeAllUserSessions(userId) { if (this.debugEnabled) { this.logger.debug('🗑️ Revocando TODAS las sesiones del usuario', { userId }); } const keys = await this.getAllSessionKeys(); for (const key of keys) { const session = await this.sessionStore.get(key); if (session && session.userId === userId) { await this.sessionStore.delete(key); } } if (this.debugEnabled) { this.logger.debug('✅ Todas las sesiones revocadas'); } } async register(data) { if (!this.repository) { throw new Error('Auth repository not configured'); } if (this.debugEnabled) { this.logger.debug('📝 Registrando nuevo usuario', { email: data.email }); } const existingUser = data.email ? await this.repository.findUserByUsername(data.email) : null; if (existingUser) { throw new common_1.BadRequestException('User already exists'); } const hashedPassword = await this.hashService.hash(data.password); const user = await this.repository.createUser({ ...data, password: hashedPassword, roles: data.roles || ['user'], isActive: true, provider: 'local', }); if (this.debugEnabled) { this.logger.debug('✅ Usuario registrado', { userId: user.id }); } return user; } async updateUserProfile(userId, data) { if (!this.repository) { throw new Error('Auth repository not configured'); } const user = await this.repository.findUserById(userId); if (!user) { throw new common_1.BadRequestException('User not found'); } const updatedUser = await this.repository.updateUser(userId, data); const { password, ...safeUser } = updatedUser; return safeUser; } async validateUser(identifier, password) { if (!this.repository) { throw new Error('Auth repository not configured'); } if (this.debugEnabled) { this.logger.debug('🔐 Validando usuario', { identifier: identifier.substring(0, 20) + '...' }); } const isEmail = this.isValidEmail(identifier); let user = null; if (isEmail) { user = await this.repository.findUserByEmail(identifier); } else { user = await this.repository.findUserByUsername(identifier); } if (!user) { this.logger.error('User not found'); return null; } if (this.debugEnabled) { this.logger.debug('👤 Usuario encontrado', { id: user.id, active: user.isActive }); } const isPasswordValid = await this.hashService.verify(password, user.password); if (!isPasswordValid) { console.info('Invalid password'); return null; } if (this.debugEnabled) { this.logger.debug('✅ Usuario validado correctamente'); } const { password: _, ...result } = user; return result; } isValidEmail(identifier) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(identifier); } async validateOAuthUser(provider, providerId, profile) { console.info(`Validando usuario OAuth de ${provider} con ID ${providerId}`); console.info('Perfil recibido:', profile); if (!this.repository) { throw new Error('Auth repository not configured'); } let user = await this.repository.findUserByProviderId(provider, providerId); if (!user) { const email = profile.emails?.[0]?.value || null; const username = profile.username || `${provider}_${providerId}`; user = await this.repository.createUser({ email, username, fullName: profile.displayName || profile.name, provider, providerId, isActive: true, roles: ['user'], }); } return user; } async changePassword(userId, oldPassword, newPassword) { if (!this.repository) { throw new Error('Auth repository not configured'); } const user = await this.repository.findUserById(userId); if (!user) { throw new common_1.BadRequestException('User not found'); } const isOldPasswordValid = await this.hashService.verify(oldPassword, user.password); if (!isOldPasswordValid) { throw new common_1.BadRequestException('Invalid old password'); } const hashedPassword = await this.hashService.hash(newPassword); await this.repository.updateUser(userId, { password: hashedPassword }); await this.revokeAllUserSessions(userId); } async getAllSessionKeys() { return []; } parseDuration(duration) { const match = duration.match(/^(\d+)([smhd])$/); if (!match) return 3600; const value = parseInt(match[1]); const unit = match[2]; switch (unit) { case 's': return value; case 'm': return value * 60; case 'h': return value * 3600; case 'd': return value * 86400; default: return 3600; } } }; exports.BaseAuthService = BaseAuthService; exports.BaseAuthService = BaseAuthService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [jwt_1.JwtService, Object, hash_service_1.HashService, Object, config_1.ConfigService]) ], BaseAuthService); //# sourceMappingURL=base-auth.service.js.map