first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

88
docs/05-autenticacion.md Normal file
View File

@@ -0,0 +1,88 @@
# 5. Autenticación (Pireon)
Los usuarios pertenecen a **Pireon** (`https://oauth.pireon.dev`, OAuth2 con Laravel Passport). TaskStream no guarda
contraseñas: solo valida tokens y guarda una copia local de nombre y email para mostrarlos.
## 5.1 Cómo valida la API un token
```mermaid
sequenceDiagram
participant C as Cliente
participant G as AuthGuard (presenters)
participant Q as ResolveSessionQuery
participant R as Redis (caché)
participant P as Pireon
C->>G: Authorization: Bearer <token>
G->>Q: resolveSession(token)
Q->>R: GET auth:session:sha256(token)
alt en caché
R-->>Q: usuario
else no está
Q->>P: GET /api/user
P-->>Q: usuario + roles
Q->>R: SET (AUTH_SESSION_TTL_SECONDS)
end
Q-->>G: User (o 401)
```
- Se cachea el **hash SHA-256** del token, nunca el token en claro.
- Si Pireon marca al usuario como inactivo, el token se rechaza.
- El guard es global: todo requiere token salvo lo marcado con `@Public()` (health, configuración de login, proxy de
tokens y webhook).
- En subscriptions, el token se valida una vez al abrir el WebSocket (`connection_init`).
- `@RequireAppRoles('…')` permite exigir roles del cliente OAuth en Pireon (los admins globales de Pireon los tienen todos).
## 5.2 Iniciar sesión desde el navegador
Pireon **no permite CORS** en `/oauth/token` y el `client_secret` no debe llegar al navegador, así que la web pasa por
la API: `POST /api/auth/token` hace de proxy hacia Pireon.
| Método | Necesita | Flujo |
| ------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Entrar con Pireon** (Authorization Code + PKCE) | `PIREON_CLIENT_ID`, `PIREON_CLIENT_SECRET` y la redirect URI registrada | Web → `/oauth/authorize` de Pireon → `/auth/callback` → `POST /api/auth/token` (`authorization_code` + `code_verifier`) |
| **Email y contraseña** | El mismo cliente, con `PIREON_PASSWORD_GRANT=true` | `POST /api/auth/token` (`password`) |
Las dos opciones necesitan el cliente OAuth: sin `PIREON_CLIENT_ID` la API responde `503` y el login avisa de que no
está configurado. El email y la contraseña son los de la cuenta de Pireon; TaskStream no los guarda, solo los reenvía.
(El login con token de acceso personal se ha retirado de la web por ahora; la API sigue aceptando esos tokens como
`Bearer`.)
### Registrar TaskStream como cliente en Pireon
1. Crea el cliente: `POST https://oauth.pireon.dev/api/clients` (con un token tuyo de Pireon, scope `account:write`).
2. Registra como redirect URIs:
- `http://localhost:4200/auth/callback` (desarrollo)
- `http://localhost:8080/auth/callback` (Docker)
- la de producción
3. Pon `PIREON_CLIENT_ID` y `PIREON_CLIENT_SECRET` en `.env` (o en el entorno del contenedor).
`GET /api/auth/config` devuelve a la web lo necesario para iniciar el flujo (`authorizeUrl`, `clientId`, `scopes`,
`passwordGrant`), o `null` si no hay cliente configurado; en ese caso el login muestra el aviso y no deja entrar.
### Sesión en la web
- La sesión (`accessToken`, `refreshToken`, caducidad) se guarda en `localStorage` y se restaura al arrancar.
- Un minuto antes de caducar se renueva con `refresh_token`. Si llegan varias peticiones a la vez, se renueva una sola vez.
- Si la API responde `UNAUTHENTICATED`, se cierra la sesión y se vuelve al login.
- El parámetro `state` del flujo PKCE protege contra CSRF; el `code_verifier` vive en `sessionStorage` solo durante la redirección.
## 5.3 Webhooks de Pireon (opcional)
Para que la desactivación de un usuario o la revocación de un token surtan efecto al instante (sin esperar a que
caduque la caché):
1. Registra `https://<tu-api>/api/webhooks/pireon` con los eventos `user.deactivated` y `token.revoked`
(`POST /api/clients/{client}/webhooks`).
2. Pon el secret que devuelve en `PIREON_WEBHOOK_SECRET`.
La API verifica la firma `X-Webhook-Signature` (HMAC-SHA256 del cuerpo), marca al usuario como inactivo y borra sus
sesiones cacheadas. Sin `PIREON_WEBHOOK_SECRET`, el endpoint rechaza todas las llamadas.
## 5.4 Usuarios en TaskStream
- Al validar un token, el usuario se guarda (nombre, email, activo) en la colección `users` de MongoDB.
- `users(search)` sincroniza antes el directorio de Pireon con el token del usuario, así que se puede añadir a gente
que aún no ha entrado en TaskStream (requiere el scope `users:read`).
- Los read models guardan el nombre de los usuarios desnormalizado: si alguien cambia su nombre en Pireon, las entradas
antiguas mantienen el nombre anterior.