workspace-v1/shared/libs/auth/_reference/auth.service.reference.ts
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

230 lines
6.3 KiB
TypeScript

/**
* AUTH SERVICE - REFERENCE IMPLEMENTATION
*
* @description Implementación de referencia para servicio de autenticación JWT.
* Este archivo muestra los patrones básicos sin dependencias específicas de proyecto.
*
* @usage Copiar y adaptar según necesidades del proyecto.
* @origin gamilit/apps/backend/src/modules/auth/services/auth.service.ts
*/
import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
// Adaptar imports según proyecto
// import { User, Profile, UserSession } from '../entities';
// import { RegisterUserDto } from '../dto';
/**
* Constantes de configuración
* Mover a variables de entorno en producción
*/
const BCRYPT_COST = 10;
const ACCESS_TOKEN_EXPIRES = '15m';
const REFRESH_TOKEN_EXPIRES = '7d';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User, 'auth')
private readonly userRepository: Repository<User>,
@InjectRepository(Profile, 'auth')
private readonly profileRepository: Repository<Profile>,
@InjectRepository(UserSession, 'auth')
private readonly sessionRepository: Repository<UserSession>,
private readonly jwtService: JwtService,
) {}
/**
* Registro de nuevo usuario
*
* @pattern
* 1. Validar email único
* 2. Hashear password (bcrypt)
* 3. Crear usuario
* 4. Crear perfil
* 5. Generar tokens
*/
async register(dto: RegisterUserDto): Promise<AuthResponse> {
// 1. Validar email único
const existingUser = await this.userRepository.findOne({
where: { email: dto.email },
});
if (existingUser) {
throw new ConflictException('Email ya registrado');
}
// 2. Hashear password
const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_COST);
// 3. Crear usuario
const user = this.userRepository.create({
email: dto.email,
encrypted_password: hashedPassword,
role: dto.role || 'user',
});
await this.userRepository.save(user);
// 4. Crear perfil
const profile = this.profileRepository.create({
id: user.id,
user_id: user.id,
email: user.email,
// ...otros campos del perfil
});
await this.profileRepository.save(profile);
// 5. Generar tokens y crear sesión
return this.createAuthResponse(user);
}
/**
* Login de usuario
*
* @pattern
* 1. Buscar usuario por email
* 2. Validar password
* 3. Crear sesión
* 4. Generar tokens
*/
async login(email: string, password: string): Promise<AuthResponse> {
// 1. Buscar usuario
const user = await this.userRepository.findOne({
where: { email },
select: ['id', 'email', 'encrypted_password', 'role'],
});
if (!user) {
throw new UnauthorizedException('Credenciales inválidas');
}
// 2. Validar password
const isValid = await bcrypt.compare(password, user.encrypted_password);
if (!isValid) {
throw new UnauthorizedException('Credenciales inválidas');
}
// 3-4. Crear sesión y generar tokens
return this.createAuthResponse(user);
}
/**
* Refresh de tokens
*
* @pattern
* 1. Buscar sesión por refresh token hash
* 2. Validar que no esté expirada
* 3. Generar nuevos tokens
* 4. Actualizar sesión
*/
async refreshToken(refreshToken: string): Promise<AuthResponse> {
const tokenHash = this.hashToken(refreshToken);
const session = await this.sessionRepository.findOne({
where: { refresh_token: tokenHash },
relations: ['user'],
});
if (!session || session.is_revoked || new Date() > session.expires_at) {
throw new UnauthorizedException('Token inválido o expirado');
}
// Generar nuevos tokens
return this.createAuthResponse(session.user, session);
}
/**
* Logout
*
* @pattern Revocar sesión actual
*/
async logout(sessionId: string): Promise<void> {
await this.sessionRepository.update(sessionId, { is_revoked: true });
}
// ============ HELPERS PRIVADOS ============
/**
* Generar respuesta de autenticación completa
*/
private async createAuthResponse(user: User, existingSession?: UserSession): Promise<AuthResponse> {
const payload = {
sub: user.id,
email: user.email,
role: user.role,
};
const accessToken = this.jwtService.sign(payload, { expiresIn: ACCESS_TOKEN_EXPIRES });
const refreshToken = crypto.randomBytes(32).toString('hex');
// Crear o actualizar sesión
if (existingSession) {
existingSession.refresh_token = this.hashToken(refreshToken);
existingSession.expires_at = this.calculateExpiry(REFRESH_TOKEN_EXPIRES);
await this.sessionRepository.save(existingSession);
} else {
const session = this.sessionRepository.create({
user_id: user.id,
refresh_token: this.hashToken(refreshToken),
expires_at: this.calculateExpiry(REFRESH_TOKEN_EXPIRES),
});
await this.sessionRepository.save(session);
}
return {
accessToken,
refreshToken,
user: {
id: user.id,
email: user.email,
role: user.role,
},
};
}
/**
* Hash de token para almacenamiento seguro
*/
private hashToken(token: string): string {
return crypto.createHash('sha256').update(token).digest('hex');
}
/**
* Calcular fecha de expiración
*/
private calculateExpiry(duration: string): Date {
const match = duration.match(/^(\d+)([mhd])$/);
if (!match) throw new Error('Invalid duration format');
const [, value, unit] = match;
const multipliers = { m: 60000, h: 3600000, d: 86400000 };
return new Date(Date.now() + parseInt(value) * multipliers[unit]);
}
}
// ============ TIPOS ============
interface AuthResponse {
accessToken: string;
refreshToken: string;
user: {
id: string;
email: string;
role: string;
};
}
interface RegisterUserDto {
email: string;
password: string;
role?: string;
}