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
734 lines
19 KiB
Markdown
734 lines
19 KiB
Markdown
# Guía de Implementación: Autenticación
|
|
|
|
**Versión:** 1.0.0
|
|
**Tiempo estimado:** 2-4 horas (adaptación), 8-16 horas (desde cero)
|
|
**Complejidad:** Media-Alta
|
|
|
|
---
|
|
|
|
## Pre-requisitos
|
|
|
|
Antes de implementar, asegurar:
|
|
|
|
- [ ] NestJS configurado con TypeORM
|
|
- [ ] PostgreSQL con schemas creados
|
|
- [ ] Variables de entorno configuradas
|
|
- [ ] Dependencias npm instaladas
|
|
|
|
---
|
|
|
|
## Paso 1: Instalar Dependencias
|
|
|
|
```bash
|
|
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
|
|
npm install -D @types/passport-jwt @types/bcrypt
|
|
npm install class-validator class-transformer
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 2: Crear DDL de Base de Datos
|
|
|
|
### 2.1 Schema auth.users
|
|
|
|
```sql
|
|
-- Schema: auth (si no existe)
|
|
CREATE SCHEMA IF NOT EXISTS auth;
|
|
|
|
-- Extensión para UUIDs
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- Tabla de usuarios
|
|
CREATE TABLE auth.users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email TEXT NOT NULL UNIQUE,
|
|
encrypted_password TEXT NOT NULL,
|
|
role VARCHAR(50) DEFAULT 'user',
|
|
status VARCHAR(50) DEFAULT 'active',
|
|
email_confirmed_at TIMESTAMPTZ,
|
|
phone TEXT,
|
|
phone_confirmed_at TIMESTAMPTZ,
|
|
is_super_admin BOOLEAN DEFAULT FALSE,
|
|
banned_until TIMESTAMPTZ,
|
|
last_sign_in_at TIMESTAMPTZ,
|
|
raw_user_meta_data JSONB DEFAULT '{}',
|
|
deleted_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_auth_users_email ON auth.users(email);
|
|
CREATE INDEX idx_auth_users_role ON auth.users(role);
|
|
CREATE INDEX idx_auth_users_status ON auth.users(status);
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE auth.users IS 'Tabla principal de usuarios del sistema';
|
|
COMMENT ON COLUMN auth.users.encrypted_password IS 'Password hasheado con bcrypt';
|
|
```
|
|
|
|
### 2.2 Schema auth_management.user_sessions
|
|
|
|
```sql
|
|
-- Schema: auth_management
|
|
CREATE SCHEMA IF NOT EXISTS auth_management;
|
|
|
|
-- Tabla de sesiones
|
|
CREATE TABLE auth_management.user_sessions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
tenant_id UUID,
|
|
session_token TEXT NOT NULL UNIQUE,
|
|
refresh_token TEXT NOT NULL,
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
device_type VARCHAR(50),
|
|
browser VARCHAR(100),
|
|
os VARCHAR(100),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
last_activity_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_user_sessions_user_id ON auth_management.user_sessions(user_id);
|
|
CREATE INDEX idx_user_sessions_refresh_token ON auth_management.user_sessions(refresh_token);
|
|
CREATE INDEX idx_user_sessions_expires_at ON auth_management.user_sessions(expires_at);
|
|
|
|
COMMENT ON TABLE auth_management.user_sessions IS 'Sesiones activas de usuarios';
|
|
```
|
|
|
|
### 2.3 Tabla auth_attempts
|
|
|
|
```sql
|
|
CREATE TABLE auth_management.auth_attempts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email TEXT NOT NULL,
|
|
success BOOLEAN NOT NULL,
|
|
ip_address INET NOT NULL,
|
|
user_agent TEXT,
|
|
failure_reason TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_auth_attempts_email ON auth_management.auth_attempts(email);
|
|
CREATE INDEX idx_auth_attempts_ip ON auth_management.auth_attempts(ip_address);
|
|
CREATE INDEX idx_auth_attempts_created_at ON auth_management.auth_attempts(created_at);
|
|
|
|
COMMENT ON TABLE auth_management.auth_attempts IS 'Log de intentos de autenticación';
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 3: Crear Entities
|
|
|
|
### 3.1 User Entity
|
|
|
|
```typescript
|
|
// src/modules/auth/entities/user.entity.ts
|
|
import {
|
|
Entity,
|
|
PrimaryGeneratedColumn,
|
|
Column,
|
|
CreateDateColumn,
|
|
UpdateDateColumn,
|
|
Index,
|
|
} from 'typeorm';
|
|
import { Exclude } from 'class-transformer';
|
|
|
|
@Entity({ schema: 'auth', name: 'users' })
|
|
@Index('idx_auth_users_email', ['email'])
|
|
export class User {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id!: string;
|
|
|
|
@Column({ type: 'text', unique: true })
|
|
email!: string;
|
|
|
|
@Column({ type: 'text', name: 'encrypted_password' })
|
|
@Exclude() // No serializar en respuestas
|
|
encrypted_password!: string;
|
|
|
|
@Column({ type: 'varchar', length: 50, default: 'user' })
|
|
role!: string;
|
|
|
|
@Column({ type: 'varchar', length: 50, default: 'active' })
|
|
status!: string;
|
|
|
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
|
email_confirmed_at?: Date;
|
|
|
|
@Column({ type: 'boolean', default: false })
|
|
is_super_admin!: boolean;
|
|
|
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
|
banned_until?: Date;
|
|
|
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
|
last_sign_in_at?: Date;
|
|
|
|
@Column({ type: 'jsonb', default: {} })
|
|
raw_user_meta_data!: Record<string, any>;
|
|
|
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
|
deleted_at?: Date;
|
|
|
|
@CreateDateColumn({ type: 'timestamp with time zone' })
|
|
created_at!: Date;
|
|
|
|
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
|
updated_at!: Date;
|
|
}
|
|
```
|
|
|
|
### 3.2 UserSession Entity
|
|
|
|
```typescript
|
|
// src/modules/auth/entities/user-session.entity.ts
|
|
import {
|
|
Entity,
|
|
PrimaryGeneratedColumn,
|
|
Column,
|
|
CreateDateColumn,
|
|
UpdateDateColumn,
|
|
ManyToOne,
|
|
JoinColumn,
|
|
} from 'typeorm';
|
|
import { User } from './user.entity';
|
|
|
|
@Entity({ schema: 'auth_management', name: 'user_sessions' })
|
|
export class UserSession {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id!: string;
|
|
|
|
@Column({ type: 'uuid' })
|
|
user_id!: string;
|
|
|
|
@Column({ type: 'uuid', nullable: true })
|
|
tenant_id?: string;
|
|
|
|
@Column({ type: 'text', unique: true })
|
|
session_token!: string;
|
|
|
|
@Column({ type: 'text' })
|
|
refresh_token!: string; // Hasheado con SHA256
|
|
|
|
@Column({ type: 'inet', nullable: true })
|
|
ip_address?: string;
|
|
|
|
@Column({ type: 'text', nullable: true })
|
|
user_agent?: string;
|
|
|
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
|
device_type?: string;
|
|
|
|
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
browser?: string;
|
|
|
|
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
os?: string;
|
|
|
|
@Column({ type: 'boolean', default: true })
|
|
is_active!: boolean;
|
|
|
|
@Column({ type: 'timestamp with time zone' })
|
|
expires_at!: Date;
|
|
|
|
@Column({ type: 'timestamp with time zone', default: () => 'NOW()' })
|
|
last_activity_at!: Date;
|
|
|
|
@CreateDateColumn({ type: 'timestamp with time zone' })
|
|
created_at!: Date;
|
|
|
|
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
|
updated_at!: Date;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 4: Crear DTOs
|
|
|
|
### 4.1 Login DTO
|
|
|
|
```typescript
|
|
// src/modules/auth/dto/login.dto.ts
|
|
import { ApiProperty } from '@nestjs/swagger';
|
|
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
|
|
|
|
export class LoginDto {
|
|
@ApiProperty({
|
|
description: 'Email del usuario',
|
|
example: 'usuario@example.com',
|
|
})
|
|
@IsEmail({}, { message: 'Email inválido' })
|
|
@IsNotEmpty({ message: 'Email es requerido' })
|
|
email!: string;
|
|
|
|
@ApiProperty({
|
|
description: 'Contraseña del usuario',
|
|
example: 'MySecurePassword123!',
|
|
minLength: 8,
|
|
})
|
|
@IsString()
|
|
@MinLength(8, { message: 'Password debe tener al menos 8 caracteres' })
|
|
@IsNotEmpty({ message: 'Password es requerido' })
|
|
password!: string;
|
|
}
|
|
```
|
|
|
|
### 4.2 Register DTO
|
|
|
|
```typescript
|
|
// src/modules/auth/dto/register-user.dto.ts
|
|
import { IsEmail, IsString, MinLength, IsOptional, IsObject } from 'class-validator';
|
|
|
|
export class RegisterUserDto {
|
|
@IsEmail({}, { message: 'El email debe ser válido' })
|
|
email!: string;
|
|
|
|
@IsString()
|
|
@MinLength(8, { message: 'La contraseña debe tener al menos 8 caracteres' })
|
|
password!: string;
|
|
|
|
@IsObject()
|
|
@IsOptional()
|
|
raw_user_meta_data?: Record<string, any>;
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
first_name?: string;
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
last_name?: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 5: Crear JWT Strategy
|
|
|
|
```typescript
|
|
// src/modules/auth/strategies/jwt.strategy.ts
|
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { PassportStrategy } from '@nestjs/passport';
|
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { AuthService } from '../services/auth.service';
|
|
|
|
@Injectable()
|
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
constructor(
|
|
private readonly configService: ConfigService,
|
|
private readonly authService: AuthService,
|
|
) {
|
|
super({
|
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
ignoreExpiration: false,
|
|
secretOrKey: configService.get<string>('JWT_SECRET') || 'dev-secret',
|
|
});
|
|
}
|
|
|
|
async validate(payload: any) {
|
|
const { sub: userId } = payload;
|
|
const user = await this.authService.validateUser(userId);
|
|
|
|
if (!user) {
|
|
throw new UnauthorizedException('Usuario no encontrado o inactivo');
|
|
}
|
|
|
|
return {
|
|
id: user.id,
|
|
sub: user.id,
|
|
email: user.email,
|
|
role: user.role,
|
|
is_active: !user.deleted_at,
|
|
email_verified: !!user.email_confirmed_at,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 6: Crear Guards
|
|
|
|
### 6.1 JWT Auth Guard
|
|
|
|
```typescript
|
|
// src/modules/auth/guards/jwt-auth.guard.ts
|
|
import { Injectable, ExecutionContext } from '@nestjs/common';
|
|
import { AuthGuard } from '@nestjs/passport';
|
|
|
|
@Injectable()
|
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
canActivate(context: ExecutionContext) {
|
|
return super.canActivate(context);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.2 Roles Guard
|
|
|
|
```typescript
|
|
// src/modules/auth/guards/roles.guard.ts
|
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
|
|
@Injectable()
|
|
export class RolesGuard implements CanActivate {
|
|
constructor(private reflector: Reflector) {}
|
|
|
|
canActivate(context: ExecutionContext): boolean {
|
|
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
|
|
context.getHandler(),
|
|
context.getClass(),
|
|
]);
|
|
|
|
if (!requiredRoles || requiredRoles.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
const { user } = context.switchToHttp().getRequest();
|
|
|
|
if (!user) {
|
|
return false;
|
|
}
|
|
|
|
return requiredRoles.some((role) => user.role === role);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.3 Roles Decorator
|
|
|
|
```typescript
|
|
// src/modules/auth/decorators/roles.decorator.ts
|
|
import { SetMetadata } from '@nestjs/common';
|
|
|
|
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 7: Crear AuthService
|
|
|
|
```typescript
|
|
// 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';
|
|
import { User } from '../entities/user.entity';
|
|
import { UserSession } from '../entities/user-session.entity';
|
|
import { LoginDto, RegisterUserDto } from '../dto';
|
|
|
|
@Injectable()
|
|
export class AuthService {
|
|
constructor(
|
|
@InjectRepository(User)
|
|
private readonly userRepository: Repository<User>,
|
|
|
|
@InjectRepository(UserSession)
|
|
private readonly sessionRepository: Repository<UserSession>,
|
|
|
|
private readonly jwtService: JwtService,
|
|
) {}
|
|
|
|
async register(dto: RegisterUserDto, ip?: string, userAgent?: string) {
|
|
// Verificar email único
|
|
const existing = await this.userRepository.findOne({
|
|
where: { email: dto.email },
|
|
});
|
|
|
|
if (existing) {
|
|
throw new ConflictException('Email ya registrado');
|
|
}
|
|
|
|
// Hashear password
|
|
const hashedPassword = await bcrypt.hash(dto.password, 10);
|
|
|
|
// Crear usuario
|
|
const user = this.userRepository.create({
|
|
email: dto.email,
|
|
encrypted_password: hashedPassword,
|
|
role: 'user',
|
|
raw_user_meta_data: dto.raw_user_meta_data || {},
|
|
});
|
|
await this.userRepository.save(user);
|
|
|
|
// Generar tokens
|
|
const tokens = await this.generateTokens(user);
|
|
|
|
// Crear sesión
|
|
await this.createSession(user.id, tokens.refreshToken, ip, userAgent);
|
|
|
|
return {
|
|
user: this.toUserResponse(user),
|
|
...tokens,
|
|
};
|
|
}
|
|
|
|
async login(dto: LoginDto, ip?: string, userAgent?: string) {
|
|
const user = await this.userRepository.findOne({
|
|
where: { email: dto.email },
|
|
});
|
|
|
|
if (!user) {
|
|
throw new UnauthorizedException('Credenciales inválidas');
|
|
}
|
|
|
|
const isPasswordValid = await bcrypt.compare(dto.password, user.encrypted_password);
|
|
|
|
if (!isPasswordValid) {
|
|
throw new UnauthorizedException('Credenciales inválidas');
|
|
}
|
|
|
|
if (user.deleted_at) {
|
|
throw new UnauthorizedException('Usuario no activo');
|
|
}
|
|
|
|
// Generar tokens
|
|
const tokens = await this.generateTokens(user);
|
|
|
|
// Crear sesión
|
|
await this.createSession(user.id, tokens.refreshToken, ip, userAgent);
|
|
|
|
// Actualizar último login
|
|
user.last_sign_in_at = new Date();
|
|
await this.userRepository.save(user);
|
|
|
|
return {
|
|
user: this.toUserResponse(user),
|
|
...tokens,
|
|
};
|
|
}
|
|
|
|
async validateUser(userId: string): Promise<User | null> {
|
|
const user = await this.userRepository.findOne({
|
|
where: { id: userId },
|
|
});
|
|
|
|
if (user && user.deleted_at) {
|
|
return null;
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
async refreshToken(refreshToken: string) {
|
|
try {
|
|
const payload = this.jwtService.verify(refreshToken);
|
|
const user = await this.validateUser(payload.sub);
|
|
|
|
if (!user) {
|
|
throw new UnauthorizedException('Usuario no encontrado');
|
|
}
|
|
|
|
// Verificar sesión
|
|
const hashedToken = crypto.createHash('sha256').update(refreshToken).digest('hex');
|
|
const session = await this.sessionRepository.findOne({
|
|
where: { user_id: user.id, refresh_token: hashedToken },
|
|
});
|
|
|
|
if (!session || new Date() > session.expires_at) {
|
|
throw new UnauthorizedException('Sesión expirada');
|
|
}
|
|
|
|
// Generar nuevos tokens
|
|
const tokens = await this.generateTokens(user);
|
|
|
|
// Actualizar sesión
|
|
session.refresh_token = crypto.createHash('sha256').update(tokens.refreshToken).digest('hex');
|
|
session.expires_at = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
session.last_activity_at = new Date();
|
|
await this.sessionRepository.save(session);
|
|
|
|
return tokens;
|
|
} catch {
|
|
throw new UnauthorizedException('Refresh token inválido');
|
|
}
|
|
}
|
|
|
|
private async generateTokens(user: User) {
|
|
const payload = { sub: user.id, email: user.email, role: user.role };
|
|
|
|
return {
|
|
accessToken: this.jwtService.sign(payload, { expiresIn: '15m' }),
|
|
refreshToken: this.jwtService.sign(payload, { expiresIn: '7d' }),
|
|
};
|
|
}
|
|
|
|
private async createSession(userId: string, refreshToken: string, ip?: string, userAgent?: string) {
|
|
const hashedRefreshToken = crypto.createHash('sha256').update(refreshToken).digest('hex');
|
|
const sessionToken = crypto.randomBytes(32).toString('hex');
|
|
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
|
|
const session = this.sessionRepository.create({
|
|
user_id: userId,
|
|
session_token: sessionToken,
|
|
refresh_token: hashedRefreshToken,
|
|
ip_address: ip || null,
|
|
user_agent: userAgent || null,
|
|
expires_at: expiresAt,
|
|
is_active: true,
|
|
});
|
|
|
|
return this.sessionRepository.save(session);
|
|
}
|
|
|
|
private toUserResponse(user: User) {
|
|
const { encrypted_password, ...userWithoutPassword } = user;
|
|
return {
|
|
...userWithoutPassword,
|
|
emailVerified: !!user.email_confirmed_at,
|
|
isActive: !user.deleted_at,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 8: Crear AuthModule
|
|
|
|
```typescript
|
|
// src/modules/auth/auth.module.ts
|
|
import { Module } from '@nestjs/common';
|
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
import { JwtModule } from '@nestjs/jwt';
|
|
import { PassportModule } from '@nestjs/passport';
|
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
|
|
import { User } from './entities/user.entity';
|
|
import { UserSession } from './entities/user-session.entity';
|
|
import { AuthService } from './services/auth.service';
|
|
import { AuthController } from './controllers/auth.controller';
|
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
|
|
|
@Module({
|
|
imports: [
|
|
PassportModule.register({ defaultStrategy: 'jwt' }),
|
|
|
|
JwtModule.registerAsync({
|
|
imports: [ConfigModule],
|
|
useFactory: async (configService: ConfigService) => ({
|
|
secret: configService.get<string>('JWT_SECRET') || 'dev-secret',
|
|
signOptions: {
|
|
expiresIn: configService.get<string>('JWT_EXPIRES_IN') || '15m',
|
|
},
|
|
}),
|
|
inject: [ConfigService],
|
|
}),
|
|
|
|
TypeOrmModule.forFeature([User, UserSession]),
|
|
],
|
|
controllers: [AuthController],
|
|
providers: [AuthService, JwtStrategy],
|
|
exports: [AuthService, JwtModule, PassportModule],
|
|
})
|
|
export class AuthModule {}
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 9: Crear AuthController
|
|
|
|
```typescript
|
|
// src/modules/auth/controllers/auth.controller.ts
|
|
import { Controller, Post, Body, Req, UseGuards, Get, HttpCode, HttpStatus } from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
import { Request } from 'express';
|
|
import { AuthService } from '../services/auth.service';
|
|
import { LoginDto, RegisterUserDto, RefreshTokenDto } from '../dto';
|
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
|
|
|
@ApiTags('Auth')
|
|
@Controller('auth')
|
|
export class AuthController {
|
|
constructor(private readonly authService: AuthService) {}
|
|
|
|
@Post('register')
|
|
@ApiOperation({ summary: 'Registrar nuevo usuario' })
|
|
async register(@Body() dto: RegisterUserDto, @Req() req: Request) {
|
|
const ip = req.ip;
|
|
const userAgent = req.headers['user-agent'];
|
|
return this.authService.register(dto, ip, userAgent);
|
|
}
|
|
|
|
@Post('login')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: 'Login con email y password' })
|
|
async login(@Body() dto: LoginDto, @Req() req: Request) {
|
|
const ip = req.ip;
|
|
const userAgent = req.headers['user-agent'];
|
|
return this.authService.login(dto, ip, userAgent);
|
|
}
|
|
|
|
@Post('refresh')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: 'Renovar access token' })
|
|
async refresh(@Body() dto: RefreshTokenDto) {
|
|
return this.authService.refreshToken(dto.refreshToken);
|
|
}
|
|
|
|
@Get('me')
|
|
@UseGuards(JwtAuthGuard)
|
|
@ApiBearerAuth()
|
|
@ApiOperation({ summary: 'Obtener usuario actual' })
|
|
async me(@Req() req: Request) {
|
|
return req.user;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Checklist de Implementación
|
|
|
|
- [ ] Dependencias npm instaladas
|
|
- [ ] DDL de base de datos creado
|
|
- [ ] Entities creados y alineados con DDL
|
|
- [ ] DTOs con validaciones
|
|
- [ ] JWT Strategy configurado
|
|
- [ ] Guards (JWT y Roles) creados
|
|
- [ ] AuthService con login/register/refresh
|
|
- [ ] AuthModule exporta servicios necesarios
|
|
- [ ] AuthController con endpoints
|
|
- [ ] Variables de entorno configuradas
|
|
- [ ] Build pasa sin errores
|
|
- [ ] Tests básicos funcionando
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Error: "Cannot find module 'bcrypt'"
|
|
```bash
|
|
npm install bcrypt
|
|
npm install -D @types/bcrypt
|
|
```
|
|
|
|
### Error: "JWT secret not configured"
|
|
Verificar variable de entorno `JWT_SECRET` en `.env`
|
|
|
|
### Error: "relation auth.users does not exist"
|
|
Ejecutar DDL para crear tablas antes de iniciar la aplicación
|
|
|
|
---
|
|
|
|
## Código de Referencia
|
|
|
|
Ver implementación completa en:
|
|
- `projects/gamilit/apps/backend/src/modules/auth/`
|
|
|
|
---
|
|
|
|
**Versión:** 1.0.0
|
|
**Sistema:** SIMCO Catálogo
|