106 lines
4.5 KiB
Markdown
106 lines
4.5 KiB
Markdown
# 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`
|