import { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import { authService } from './auth.service.js'; import { ApiResponse, AuthenticatedRequest, ValidationError } from '../../shared/types/index.js'; // Validation schemas const loginSchema = z.object({ email: z.string().email('Email inválido'), password: z.string().min(6, 'La contraseña debe tener al menos 6 caracteres'), }); const registerSchema = z.object({ email: z.string().email('Email inválido'), password: z.string().min(8, 'La contraseña debe tener al menos 8 caracteres'), // Soporta ambos formatos: full_name (legacy) o firstName+lastName (frontend) full_name: z.string().min(2, 'El nombre debe tener al menos 2 caracteres').optional(), firstName: z.string().min(2, 'Nombre debe tener al menos 2 caracteres').optional(), lastName: z.string().min(2, 'Apellido debe tener al menos 2 caracteres').optional(), tenant_id: z.string().uuid('Tenant ID inválido').optional(), companyName: z.string().optional(), }).refine( (data) => data.full_name || (data.firstName && data.lastName), { message: 'Se requiere full_name o firstName y lastName', path: ['full_name'] } ); const changePasswordSchema = z.object({ current_password: z.string().min(1, 'Contraseña actual requerida'), new_password: z.string().min(8, 'La nueva contraseña debe tener al menos 8 caracteres'), }); const refreshTokenSchema = z.object({ refresh_token: z.string().min(1, 'Refresh token requerido'), }); export class AuthController { async login(req: Request, res: Response, next: NextFunction): Promise { try { const validation = loginSchema.safeParse(req.body); if (!validation.success) { throw new ValidationError('Datos inválidos', validation.error.errors); } // Extract request metadata for session tracking const metadata = { ipAddress: req.ip || req.socket.remoteAddress || 'unknown', userAgent: req.get('User-Agent') || 'unknown', }; const result = await authService.login({ ...validation.data, metadata, }); const response: ApiResponse = { success: true, data: result, message: 'Inicio de sesión exitoso', }; res.json(response); } catch (error) { next(error); } } async register(req: Request, res: Response, next: NextFunction): Promise { try { const validation = registerSchema.safeParse(req.body); if (!validation.success) { throw new ValidationError('Datos inválidos', validation.error.errors); } const result = await authService.register(validation.data); const response: ApiResponse = { success: true, data: result, message: 'Usuario registrado exitosamente', }; res.status(201).json(response); } catch (error) { next(error); } } async refreshToken(req: Request, res: Response, next: NextFunction): Promise { try { const validation = refreshTokenSchema.safeParse(req.body); if (!validation.success) { throw new ValidationError('Datos inválidos', validation.error.errors); } // Extract request metadata for session tracking const metadata = { ipAddress: req.ip || req.socket.remoteAddress || 'unknown', userAgent: req.get('User-Agent') || 'unknown', }; const tokens = await authService.refreshToken(validation.data.refresh_token, metadata); const response: ApiResponse = { success: true, data: { tokens }, message: 'Token renovado exitosamente', }; res.json(response); } catch (error) { next(error); } } async changePassword(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = changePasswordSchema.safeParse(req.body); if (!validation.success) { throw new ValidationError('Datos inválidos', validation.error.errors); } const userId = req.user!.userId; await authService.changePassword( userId, validation.data.current_password, validation.data.new_password ); const response: ApiResponse = { success: true, message: 'Contraseña actualizada exitosamente', }; res.json(response); } catch (error) { next(error); } } async getProfile(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const userId = req.user!.userId; const profile = await authService.getProfile(userId); const response: ApiResponse = { success: true, data: profile, }; res.json(response); } catch (error) { next(error); } } async logout(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { // sessionId can come from body (sent by client after login) const sessionId = req.body?.sessionId; if (sessionId) { await authService.logout(sessionId); } const response: ApiResponse = { success: true, message: 'Sesión cerrada exitosamente', }; res.json(response); } catch (error) { next(error); } } async logoutAll(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const userId = req.user!.userId; const sessionsRevoked = await authService.logoutAll(userId); const response: ApiResponse = { success: true, data: { sessionsRevoked }, message: 'Todas las sesiones han sido cerradas', }; res.json(response); } catch (error) { next(error); } } } export const authController = new AuthController();