Files
recordalexia/docs/adr/adr-001-autenticacion-propia-sesion-familia.md

106 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-001: Autenticación propia por sesión de familia (sin Keycloak)
| Metadata | Valor |
|----------|-------|
| **Estado** | Aceptado (implementado) |
| **Fecha** | 2026-07-12 |
| **Autor(es)** | Jaume Garriga Maestre |
| **Supersede** | — |
## Contexto
recordaLexia es una app **doméstica/homelab** para niños con TDAH que corre en una
tablet en modo kiosko. Fuera del stack corporativo ECA por diseño: no hay Gravitee,
ni Kubernetes, ni infraestructura gestionada donde apoyar un IdP.
Requisitos que condicionan la autenticación:
- **Fricción cero para el niño**: abre la tablet y ve su día. Un niño de 7 años con
TDAH no puede depender de contraseñas ni de flujos de login.
- **Multi-tenant real**: varias familias en la misma instancia, con aislamiento
estricto de datos (un niño de la familia A no existe para la familia B).
- **Zona de configuración protegida**: el panel de padres debe quedar fuera del
alcance del niño sin romper el kiosko.
- **Operación doméstica**: un contenedor más en docker-compose es coste asumible;
operar un Keycloak (memoria, actualizaciones, backups del realm) no lo es.
## Opciones Consideradas
### Opción A: Keycloak OAuth2 (estándar corporativo)
| Pros | Contras |
|------|---------|
| Estándar Asepeyo, flujos probados | Un servicio pesado más que operar en un homelab |
| SSO, federación, MFA disponibles | Fricción de login incompatible con el kiosko infantil |
| Separación IdP/aplicación | Sobredimensionado: no hay federación ni múltiples clientes |
### Opción B: Auth propia por sesión de familia + PIN (elegida)
| Pros | Contras |
|------|---------|
| Cero fricción para el niño (la sesión vive en el dispositivo) | Implementación propia: superficie de error en código nuestro |
| Dos niveles con un mecanismo (rol FAMILY / rol PARENT vía PIN) | Sin SSO ni MFA (irrelevantes en el contexto doméstico) |
| Nada extra que operar; BCrypt + tabla de sesiones | Rotación/expiración de sesión hay que diseñarla a mano |
## Decisión
**Opción B.** El adulto se registra/loguea una vez; el dispositivo conserva una
sesión de familia (`FamilySession`, handle opaco + expiración) que da el rol
`FAMILY` a toda la API del kiosko. El panel de padres (`/api/parents/**`) exige el
rol `PARENT`, que se obtiene desbloqueando con PIN (`POST /api/parents/unlock`) y
caduca solo. Contraseña y PIN con hash BCrypt. Todo scopeado a
`FamilyContext.currentFamilyId()`.
La clase `AuthService` queda **encapsulada** precisamente para poder sustituirla por
un IdP externo sin tocar el resto del código, si el contexto cambia.
```mermaid
sequenceDiagram
participant A as Adulto
participant K as Tablet kiosko
participant API as Backend
A->>API: register/login (una vez)
API-->>K: sesión de familia (rol FAMILY)
Note over K: el niño usa la app sin loguearse
A->>API: PIN → unlock (rol PARENT, temporal)
API-->>A: panel de padres
```
## Trade-offs
### Optimizamos para:
- **Usabilidad del niño**: el requisito nº 1 del producto; cualquier fricción de
auth rompe el caso de uso.
- **Operabilidad doméstica**: menos piezas que mantener en un homelab.
### Sacrificamos:
- **Robustez de un IdP maduro**: aceptable porque la superficie expuesta es la red
local doméstica y los datos, aunque personales, no salen de casa.
- **Estándares corporativos (Keycloak)**: desviación consciente y documentada;
el proyecto está explícitamente fuera del ECA.
### Asunciones que podrían invalidar esta decisión:
- Si la app se expusiera a Internet o se ofreciera como SaaS multi-hogar,
reconsiderar: haría falta IdP real, MFA y hardening de sesiones.
- Si Asepeyo integrara este producto en su cartera, aplicaría el stack ECA
(Keycloak + Gravitee) y este ADR quedaría supersedido.
## Consecuencias
- (+) Kiosko sin fricción; multi-tenant con una sola tabla extra.
- (+) CORS/CSRF simplificados (API stateless con cabecera de sesión propia).
- (−) El equipo asume el mantenimiento del código de auth → mitigado con tests de
integración (`AuthIT`: registro, login fallido, gate por PIN, aislamiento entre
familias).
## Criterios de Éxito
- [x] El niño usa la app sin credenciales tras el primer login del adulto.
- [x] `/api/parents/**` inaccesible sin PIN (test verde).
- [x] Una familia no puede ver datos de otra (test verde, 404 para no filtrar existencia).
## Referencias
- `backend/src/main/java/es/asepeyo/recordalexia/security/` (SecurityConfig, AuthService, SessionAuthFilter, FamilyContext)
- `backend/src/test/java/es/asepeyo/recordalexia/web/AuthIT.java`