813 lines
13 KiB
Markdown
813 lines
13 KiB
Markdown
# ET-AUTH-004: Especificación Técnica - API Endpoints
|
|
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2025-12-05
|
|
**Estado:** ✅ Implementado
|
|
**Épica:** [OQI-001](../_MAP.md)
|
|
|
|
---
|
|
|
|
## Resumen
|
|
|
|
Esta especificación detalla todos los endpoints de la API de autenticación de OrbiQuant IA.
|
|
|
|
---
|
|
|
|
## Base URL
|
|
|
|
```
|
|
Production: https://api.orbiquant.com/api/v1
|
|
Development: http://localhost:3000/api/v1
|
|
```
|
|
|
|
---
|
|
|
|
## Endpoints Overview
|
|
|
|
| Método | Endpoint | Descripción | Auth |
|
|
|--------|----------|-------------|------|
|
|
| POST | `/auth/register` | Registro con email | ❌ |
|
|
| POST | `/auth/login` | Login con email/password | ❌ |
|
|
| POST | `/auth/logout` | Cerrar sesión | ✅ |
|
|
| POST | `/auth/refresh` | Renovar tokens | ❌ |
|
|
| GET | `/auth/me` | Usuario actual | ✅ |
|
|
| GET | `/auth/oauth/:provider/url` | URL de OAuth | ❌ |
|
|
| POST | `/auth/oauth/:provider` | Callback OAuth | ❌ |
|
|
| DELETE | `/auth/oauth/:provider` | Desvincular OAuth | ✅ |
|
|
| POST | `/auth/phone/send` | Enviar OTP | ❌ |
|
|
| POST | `/auth/phone/verify` | Verificar OTP | ❌ |
|
|
| POST | `/auth/2fa/setup` | Configurar 2FA | ✅ |
|
|
| POST | `/auth/2fa/enable` | Activar 2FA | ✅ |
|
|
| POST | `/auth/2fa/verify` | Verificar 2FA | ❌ |
|
|
| POST | `/auth/2fa/disable` | Desactivar 2FA | ✅ |
|
|
| POST | `/auth/forgot-password` | Solicitar reset | ❌ |
|
|
| POST | `/auth/reset-password` | Cambiar password | ❌ |
|
|
| POST | `/auth/verify-email` | Verificar email | ❌ |
|
|
| GET | `/auth/sessions` | Listar sesiones | ✅ |
|
|
| DELETE | `/auth/sessions/:id` | Revocar sesión | ✅ |
|
|
| DELETE | `/auth/sessions` | Revocar todas | ✅ |
|
|
|
|
---
|
|
|
|
## Detalle de Endpoints
|
|
|
|
### POST /auth/register
|
|
|
|
Registro de nuevo usuario con email y contraseña.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "usuario@example.com",
|
|
"password": "SecurePass123!",
|
|
"firstName": "Juan",
|
|
"lastName": "Pérez",
|
|
"acceptTerms": true
|
|
}
|
|
```
|
|
|
|
**Response 201:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Registro exitoso. Revisa tu email para verificar tu cuenta.",
|
|
"data": {
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "usuario@example.com",
|
|
"firstName": "Juan",
|
|
"lastName": "Pérez",
|
|
"role": "investor",
|
|
"status": "pending_verification",
|
|
"emailVerified": false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 400 | VALIDATION_ERROR | Datos inválidos |
|
|
| 409 | EMAIL_EXISTS | Email ya registrado |
|
|
| 429 | RATE_LIMIT | Demasiadas solicitudes |
|
|
|
|
---
|
|
|
|
### POST /auth/login
|
|
|
|
Inicio de sesión con email y contraseña.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "usuario@example.com",
|
|
"password": "SecurePass123!"
|
|
}
|
|
```
|
|
|
|
**Response 200 (sin 2FA):**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "usuario@example.com",
|
|
"firstName": "Juan",
|
|
"lastName": "Pérez",
|
|
"role": "investor",
|
|
"status": "active"
|
|
},
|
|
"tokens": {
|
|
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"expiresIn": 900,
|
|
"tokenType": "Bearer"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response 200 (con 2FA):**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"requires2FA": true,
|
|
"tempToken": "temp_token_for_2fa_verification",
|
|
"methods": ["totp", "backup_code"]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 401 | INVALID_CREDENTIALS | Email o contraseña incorrectos |
|
|
| 403 | EMAIL_NOT_VERIFIED | Email no verificado |
|
|
| 403 | ACCOUNT_LOCKED | Cuenta bloqueada temporalmente |
|
|
| 403 | ACCOUNT_SUSPENDED | Cuenta suspendida |
|
|
|
|
---
|
|
|
|
### POST /auth/logout
|
|
|
|
Cerrar sesión actual.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/logout
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Sesión cerrada exitosamente"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/refresh
|
|
|
|
Renovar access token usando refresh token.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/refresh
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"refreshToken": "eyJhbGciOiJSUzI1NiIs..."
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"tokens": {
|
|
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"expiresIn": 900,
|
|
"tokenType": "Bearer"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 401 | INVALID_TOKEN | Token inválido o expirado |
|
|
| 401 | TOKEN_REVOKED | Token revocado |
|
|
|
|
---
|
|
|
|
### GET /auth/me
|
|
|
|
Obtener información del usuario actual.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
GET /api/v1/auth/me
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "usuario@example.com",
|
|
"phone": "+525512345678",
|
|
"firstName": "Juan",
|
|
"lastName": "Pérez",
|
|
"role": "investor",
|
|
"status": "active",
|
|
"emailVerified": true,
|
|
"phoneVerified": true,
|
|
"twoFactorEnabled": false,
|
|
"createdAt": "2025-01-15T10:00:00Z",
|
|
"profile": {
|
|
"displayName": "JuanPerez",
|
|
"avatarUrl": "https://...",
|
|
"preferredLanguage": "es",
|
|
"timezone": "America/Mexico_City"
|
|
},
|
|
"oauthProviders": ["google", "github"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /auth/oauth/:provider/url
|
|
|
|
Obtener URL de autorización OAuth.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
GET /api/v1/auth/oauth/google/url?redirectTo=/dashboard
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"authUrl": "https://accounts.google.com/o/oauth2/v2/auth?...",
|
|
"state": "random_state_token"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/oauth/:provider
|
|
|
|
Procesar callback de OAuth.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/oauth/google
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"code": "authorization_code_from_provider",
|
|
"state": "state_token_from_url"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "usuario@gmail.com",
|
|
"firstName": "Juan",
|
|
"lastName": "Pérez",
|
|
"isNewUser": false
|
|
},
|
|
"tokens": {
|
|
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"expiresIn": 900,
|
|
"tokenType": "Bearer"
|
|
},
|
|
"redirectTo": "/dashboard"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 400 | INVALID_STATE | State token inválido |
|
|
| 400 | CODE_EXPIRED | Código de autorización expirado |
|
|
| 502 | PROVIDER_ERROR | Error con el proveedor OAuth |
|
|
|
|
---
|
|
|
|
### DELETE /auth/oauth/:provider
|
|
|
|
Desvincular proveedor OAuth.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
DELETE /api/v1/auth/oauth/github
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "GitHub desvinculado exitosamente"
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 400 | LAST_AUTH_METHOD | No puedes eliminar tu único método de auth |
|
|
| 404 | PROVIDER_NOT_LINKED | Proveedor no vinculado |
|
|
|
|
---
|
|
|
|
### POST /auth/phone/send
|
|
|
|
Enviar OTP por SMS o WhatsApp.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/phone/send
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"phone": "+525512345678",
|
|
"channel": "whatsapp"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Código enviado a tu WhatsApp",
|
|
"data": {
|
|
"expiresAt": "2025-12-05T10:05:00Z",
|
|
"retryAfter": 60
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 400 | INVALID_PHONE | Número de teléfono inválido |
|
|
| 429 | RATE_LIMIT | Demasiadas solicitudes |
|
|
| 502 | SMS_ERROR | Error al enviar mensaje |
|
|
|
|
---
|
|
|
|
### POST /auth/phone/verify
|
|
|
|
Verificar OTP y autenticar.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/phone/verify
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"phone": "+525512345678",
|
|
"code": "123456"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"phone": "+525512345678",
|
|
"firstName": "Usuario",
|
|
"lastName": "Nuevo",
|
|
"isNewUser": true
|
|
},
|
|
"tokens": {
|
|
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"expiresIn": 900,
|
|
"tokenType": "Bearer"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
|
|
| Code | Error | Descripción |
|
|
|------|-------|-------------|
|
|
| 401 | INVALID_CODE | Código incorrecto |
|
|
| 401 | CODE_EXPIRED | Código expirado |
|
|
| 429 | TOO_MANY_ATTEMPTS | Demasiados intentos |
|
|
|
|
---
|
|
|
|
### POST /auth/2fa/setup
|
|
|
|
Generar secreto TOTP para configuración.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/2fa/setup
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"secret": "JBSWY3DPEHPK3PXP",
|
|
"qrCode": "data:image/png;base64,iVBORw0KGgo...",
|
|
"otpauthUrl": "otpauth://totp/OrbiQuant:usuario@example.com?..."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/2fa/enable
|
|
|
|
Activar 2FA después de verificar código.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/2fa/enable
|
|
Authorization: Bearer {accessToken}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"code": "123456"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "2FA activado exitosamente",
|
|
"data": {
|
|
"backupCodes": [
|
|
"A1B2C3D4",
|
|
"E5F6G7H8",
|
|
"I9J0K1L2",
|
|
"M3N4O5P6",
|
|
"Q7R8S9T0",
|
|
"U1V2W3X4",
|
|
"Y5Z6A7B8",
|
|
"C9D0E1F2",
|
|
"G3H4I5J6",
|
|
"K7L8M9N0"
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/2fa/verify
|
|
|
|
Verificar código 2FA durante login.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/2fa/verify
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"tempToken": "temp_token_from_login",
|
|
"code": "123456"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"user": { ... },
|
|
"tokens": {
|
|
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJSUzI1NiIs...",
|
|
"expiresIn": 900,
|
|
"tokenType": "Bearer"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/2fa/disable
|
|
|
|
Desactivar 2FA.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/2fa/disable
|
|
Authorization: Bearer {accessToken}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"code": "123456"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "2FA desactivado exitosamente"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/forgot-password
|
|
|
|
Solicitar recuperación de contraseña.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/forgot-password
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "usuario@example.com"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Si el email existe, recibirás instrucciones para recuperar tu contraseña"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/reset-password
|
|
|
|
Cambiar contraseña con token de recuperación.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/reset-password
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"token": "reset_token_from_email",
|
|
"password": "NewSecurePass123!"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Contraseña actualizada exitosamente"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /auth/verify-email
|
|
|
|
Verificar email con token.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
POST /api/v1/auth/verify-email
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"token": "verification_token_from_email"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Email verificado exitosamente"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /auth/sessions
|
|
|
|
Listar sesiones activas.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
GET /api/v1/auth/sessions
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"sessions": [
|
|
{
|
|
"id": "session-uuid-1",
|
|
"device": {
|
|
"type": "desktop",
|
|
"browser": "Chrome",
|
|
"browserVersion": "120.0.0",
|
|
"os": "Windows",
|
|
"osVersion": "11"
|
|
},
|
|
"location": {
|
|
"city": "Ciudad de México",
|
|
"country": "México",
|
|
"countryCode": "MX"
|
|
},
|
|
"ipAddress": "189.xxx.xxx.xxx",
|
|
"lastActivity": "2025-12-05T10:00:00Z",
|
|
"createdAt": "2025-12-01T08:00:00Z",
|
|
"isCurrent": true
|
|
},
|
|
{
|
|
"id": "session-uuid-2",
|
|
"device": {
|
|
"type": "mobile",
|
|
"browser": "Safari",
|
|
"os": "iOS",
|
|
"osVersion": "17.1"
|
|
},
|
|
"location": {
|
|
"city": "Guadalajara",
|
|
"country": "México"
|
|
},
|
|
"ipAddress": "187.xxx.xxx.xxx",
|
|
"lastActivity": "2025-12-04T15:30:00Z",
|
|
"createdAt": "2025-11-28T10:00:00Z",
|
|
"isCurrent": false
|
|
}
|
|
],
|
|
"totalCount": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### DELETE /auth/sessions/:id
|
|
|
|
Revocar sesión específica.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
DELETE /api/v1/auth/sessions/session-uuid-2
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Sesión cerrada exitosamente"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### DELETE /auth/sessions
|
|
|
|
Revocar todas las demás sesiones.
|
|
|
|
**Request:**
|
|
|
|
```http
|
|
DELETE /api/v1/auth/sessions
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
**Response 200:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Todas las demás sesiones han sido cerradas",
|
|
"data": {
|
|
"revokedCount": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Formato de Errores
|
|
|
|
Todos los errores siguen el formato estándar:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": "ERROR_CODE",
|
|
"message": "Descripción legible del error",
|
|
"details": {
|
|
"field": "Detalle específico del campo si aplica"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Rate Limiting Headers
|
|
|
|
```http
|
|
X-RateLimit-Limit: 100
|
|
X-RateLimit-Remaining: 95
|
|
X-RateLimit-Reset: 1701792060
|
|
```
|
|
|
|
---
|
|
|
|
## Referencias
|
|
|
|
- [OpenAPI Spec](../../../98-standards/openapi/auth.yaml)
|
|
- [Postman Collection](../../../96-quick-reference/postman/OrbiQuant-Auth.json)
|