-
Notifications
You must be signed in to change notification settings - Fork 0
Modulo Auth
Jose Navarro edited this page Aug 12, 2026
·
1 revision
AuthController (/api/auth/**, permitAll() a nivel de filtro). No hay validación local de credenciales ni tabla de usuarios propia para login: todo se delega a Supabase Auth (GoTrue) vía RestClient con la anon key. AuthServiceImpl no invoca ningún repositorio JPA.
| Método | Path | Rol | Request | Response |
|---|---|---|---|---|
| POST | /api/auth/login |
público | CuerpoLogin{username,password} |
RespuestaLogin{token,refreshToken,rol,nombreCompleto,username} |
| POST | /api/auth/refresh |
público | CuerpoRefresh{refreshToken} |
RespuestaLogin |
| POST | /api/auth/recuperar |
público | CuerpoRecuperar{email} |
200 vacío (siempre, exista o no el email) |
| POST | /api/auth/restablecer |
público | CuerpoRestablecer{accessToken,password} |
200 vacío |
flowchart TD
LOGIN["POST /api/auth/login"] --> SUPA_PWD["Supabase: POST /auth/v1/token?grant_type=password"]
SUPA_PWD -->|"error HTTP"| ERR1["CredencialesInvalidasException (401)"]
SUPA_PWD -->|"ok, sin access_token (defensivo)"| ERR1
SUPA_PWD -->|"ok"| MAP1["mapear(): extrae app_metadata.rol (default OPERARIO),\nuser_metadata.nombre_completo, username"]
MAP1 --> OUT1["RespuestaLogin"]
REFRESH["POST /api/auth/refresh"] --> SUPA_REF["Supabase: POST /auth/v1/token?grant_type=refresh_token"]
SUPA_REF -->|"error"| ERR2["SesionExpiradaException (401)"]
SUPA_REF -->|"ok"| MAP2["mapear() — rota el refresh token en Supabase\n(el usado queda invalidado)"]
MAP2 --> OUT1
NOTE1["Sin deduplicación de refresh in-flight en el backend\n(confirmado: no hay lock/cache/mutex en AuthServiceImpl).\nSi el frontend dispara refresh en paralelo,\ncada request es independiente."]
RECUP["POST /api/auth/recuperar"] --> SUPA_RECUP["Supabase: POST /auth/v1/recover\nredirect_to=<primer origen CORS>/restablecer"]
SUPA_RECUP -->|"429"| ERR3["ReglaNegocioException\n'Ya se envio un correo hace poco'"]
SUPA_RECUP -->|"cualquier otro error"| SWALLOW["log WARN, responde 200 igual\n(por diseño: no revela si el email existe)"]
SUPA_RECUP -->|"ok"| OK3["200 vacío"]
RESTAB["POST /api/auth/restablecer\nBearer accessToken"] --> SUPA_RESTAB["Supabase: PUT /auth/v1/user {password}"]
SUPA_RESTAB -->|"401/403"| ERR4["SesionExpiradaException"]
SUPA_RESTAB -->|"422/400"| ERR5["ReglaNegocioException\n'contraseña no cumple requisitos'"]
SUPA_RESTAB -->|"otro error"| ERR6["log ERROR + ReglaNegocioException genérica"]
SUPA_RESTAB -->|"ok"| OK4["200 vacío"]
style NOTE1 fill:#fde9c8
style SWALLOW fill:#fde9c8
sequenceDiagram
participant C as Cliente (SPA)
participant RL as FiltroRateLimit
participant AC as AuthController
participant AS as AuthServiceImpl
participant SUPA as Supabase Auth (GoTrue)
C->>RL: POST /api/auth/login
Note over RL: límite estricto: 10 intentos/min por IP en /api/auth/**
RL-->>C: 429 si excede
RL->>AC: request (permitAll, no pasa por JWT filter)
AC->>AS: login(cuerpo)
AS->>SUPA: POST /auth/v1/token?grant_type=password
alt credenciales inválidas
SUPA-->>AS: error HTTP
AS-->>AC: throw CredencialesInvalidasException
AC-->>C: 401
else ok
SUPA-->>AS: access_token (JWT ES256) + refresh_token
Note over SUPA: JWT contiene app_metadata.rol,<br/>app_metadata.empresa_schema,<br/>user_metadata.nombre_completo/username
AS-->>AC: RespuestaLogin
AC-->>C: 200 {token, refreshToken, rol, nombreCompleto, username}
end
Note over C: guarda token en localStorage (sicofar_token)
C->>+RL: request autenticado (ej. GET /api/inventario)\nAuthorization: Bearer <token>
RL->>RL: valida rate limit global
RL->>+JWTV: (siguiente filtro en la cadena de Security)
Note right of JWTV: oauth2ResourceServer valida firma<br/>contra JWKS de Supabase — 401 si expiró
JWTV-->>-C: 401 si expiró
JWTV->>C: (si válido, continúa a Authorization + Tenant + Controller)
deactivate RL
C->>AC: POST /api/auth/refresh {refreshToken}
AC->>AS: refrescar(cuerpo)
AS->>SUPA: POST /auth/v1/token?grant_type=refresh_token
alt refresh inválido/expirado
SUPA-->>AS: error
AS-->>C: 401 SesionExpiradaException → frontend limpia sicofar_* y redirige a "/"
else ok
SUPA-->>AS: nuevo access_token + refresh_token (el anterior queda rotado/invalidado)
AS-->>C: 200 RespuestaLogin
end
- No existe filtro JWT custom: la validación de firma/expiración la hace
oauth2ResourceServer.jwt()de Spring Security contra las JWKS públicas de Supabase (application.yaml, algoritmo ES256). - El rol viaja siempre en el JWT (
app_metadata.rol); el backend nunca lo persiste ni lo deriva de otra fuente en el flujo de login. -
recuperaryrestablecernunca revelan si un email existe en el sistema — comportamiento intencional contra enumeración de usuarios.
Generada a partir de docs/ en el repo — cualquier cambio de arquitectura real debe actualizar primero el código y CLAUDE.md, y luego reflejarse aquí.