4.5 KiB
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.
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
- El niño usa la app sin credenciales tras el primer login del adulto.
/api/parents/**inaccesible sin PIN (test verde).- 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