nest-authify
Version:
Complete authentication and authorization package for NestJS - Monolith and Microservices ready with OAuth, JWT, Redis sessions
320 lines • 13.2 kB
JavaScript
"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