workspace-v1/shared/catalog/auth/_reference/auth.service.reference.ts
rckrdmrd cb4c0681d3 feat(workspace): Add new projects and update architecture
New projects created:
- michangarrito (marketplace mobile)
- template-saas (SaaS template)
- clinica-dental (dental ERP)
- clinica-veterinaria (veterinary ERP)

Architecture updates:
- Move catalog from core/ to shared/
- Add MCP servers structure and templates
- Add git management scripts
- Update SUBREPOSITORIOS.md with 15 new repos
- Update .gitignore for new projects

Repository infrastructure:
- 4 main repositories
- 11 subrepositorios
- Gitea remotes configured

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:43:28 -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;
}