Files
motor-de-encuestas/sprints/sprint-4/ETAPA_AUTH_PROMPT.md

148 lines
6.2 KiB
Markdown

# ETAPA AUTH — Supabase Auth + protección /admin
## 1. Objetivo
Implementar autenticación email/password con Supabase Auth y proteger
la ruta /admin/*. El dashboard y las vistas de admin quedan detrás de login.
Sin esta etapa no se avanza a vista admin mejorada ni vista detallada.
## 2. Contexto
- `current_app_role()` y `current_org_id()` ya leen del JWT — el esquema está listo.
- No hay middleware.ts ni sistema de auth hoy — /admin/responses está abierto.
- Organizations existentes: Empresa Demo (…0001), InQuality (…0002),
Fundación Solidaridad (…0003).
- No hay usuarios en auth.users — hay que crear el primero como parte de esta etapa.
- La app usa anon key para el formulario público — eso NO cambia.
- /admin/* usa service_role hoy — eso SÍ cambia: pasa a usar sesión del usuario.
## 3. DECISIONES YA TOMADAS — no consultar
- Supabase Auth nativo, email/password. Sin OAuth por ahora.
- Dos roles: `platform_admin` (acceso total) y `org_admin` (acceso a su org).
- Roles van como custom claims en el JWT: `app_role` y `org_id`.
- Middleware Next.js protege /admin/* — redirige a /login si no hay sesión válida.
- Página de login en /login — simple, solo email/password, sin registro público.
- El formulario público (/encuesta/*) NO se toca — sigue con anon key.
- Las RPCs del dashboard usan current_app_role() y current_org_id() del JWT —
cuando el usuario esté autenticado, RLS funciona automáticamente.
## 4. Tareas
### 4.1 Diagnóstico previo — mecanismo de custom claims
ANTES de implementar, reportar qué mecanismo de JWT customization está disponible
en esta versión de Supabase self-hosted. Puede ser:
- Hook SQL en `auth.hooks`
- Override de función `auth.jwt()`
- Edge Function hook
No implementar nada hasta confirmar el mecanismo y reportarlo.
### 4.2 Migración — tabla user_roles + custom claims hook
Migración `20260627000001_auth_user_roles.sql`:
```sql
-- Tabla de roles
CREATE TABLE public.user_roles (
user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
app_role text NOT NULL CHECK (app_role IN ('platform_admin','org_admin')),
org_id uuid REFERENCES public.organizations(id)
);
-- RLS: solo el propio usuario puede ver su rol (y platform_admin ve todos)
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;
```
Implementar el hook de custom claims según el mecanismo disponible confirmado en 4.1.
El JWT resultante debe incluir:
```json
{ "app_role": "platform_admin", "org_id": "10000000-0000-0000-0000-000000000002" }
```
### 4.3 Script de setup — primer usuario admin
Script operativo (NO migración) en `scripts/setup_first_admin.sql`:
- Crea el usuario en auth.users vía INSERT directo o Supabase admin API
- Inserta en user_roles con app_role='platform_admin' y org_id de InQuality
- NO hardcodear credenciales — usar variables `:email` y `:password` con instrucciones
de cómo ejecutarlo:
```bash
docker exec -i supabase-db psql -U postgres -d postgres \
-v email='admin@ejemplo.com' -v password='CAMBIAR' \
< scripts/setup_first_admin.sql
```
- Documentar en el script cómo cambiar la contraseña después del primer login
### 4.4 Middleware Next.js
`middleware.ts` en la raíz del proyecto:
- Protege `/admin/*` y `/dashboard/*`
- Si no hay sesión válida → redirect a `/login`
- Si hay sesión → deja pasar
- `/`, `/encuesta/*`, `/login` — sin restricción
### 4.5 Página de login
`app/login/page.tsx` — Client Component:
- Formulario email/password
- Llama `supabase.auth.signInWithPassword()`
- En éxito: redirect a `/admin/responses`
- En error: mensaje claro ("Email o contraseña incorrectos"), nunca trace crudo
- Sin registro público
- Usar clases Tailwind existentes — sin nuevo sistema de diseño
### 4.6 Actualizar /admin/responses
- Reemplazar `getSupabaseAdmin()` (service_role) por cliente de sesión del usuario
- RLS de Postgres filtra según app_role y org_id del JWT automáticamente
- Agregar botón "Cerrar sesión" (`supabase.auth.signOut()` → redirect a `/login`)
## 5. DoD antes de reportar
- [ ] `git log --name-only -1` — listar todos los archivos tocados
- [ ] Diagnóstico de mecanismo de custom claims reportado (4.1) antes de implementar
- [ ] Migración `20260627000001` aplicada en VPS — verificar con:
```bash
docker exec -i supabase-db psql -U postgres -d postgres -c "\d user_roles"
```
- [ ] Script `scripts/setup_first_admin.sql` existe y está documentado
- [ ] Hook JWT verificado — el token incluye `app_role` (cómo verificarlo: reportar el método)
- [ ] `/login` funciona — email/password → sesión activa → redirect a /admin/responses
- [ ] `/admin/responses` sin sesión → redirect a `/login`
- [ ] `/admin/responses` con sesión → carga datos correctamente
- [ ] Formulario público sin cambios (verificar que /encuesta/* sigue funcionando)
- [ ] Build sin errores: `npm run build`
- [ ] Commit + push a Gitea
## 6. Qué reportar
```
Diagnóstico previo (4.1):
- Mecanismo de custom claims disponible: [cuál]
- Alternativas descartadas: [por qué]
Verificado:
- Migración user_roles aplicada: ✓ — output de \d user_roles
- Hook JWT: app_role presente en token ✓/✗ — método de verificación usado
- /login funciona: ✓/✗
- /admin sin sesión → /login: ✓/✗
- /admin con sesión → datos: ✓/✗
- Formulario público sin cambios: ✓/✗
- Build: ✓/✗
- git log --name-only -1: [lista completa de archivos]
Asumido / decisiones:
- [mecanismo de hook y razonamiento]
Hallazgos:
- [lo que aparezca]
```
## 7. Qué NO hacer
- ❌ No tocar el formulario público ni la anon key
- ❌ No hardcodear credenciales en ningún archivo commiteado
- ❌ No implementar registro público de usuarios
- ❌ No usar service_role en rutas que ya tienen sesión de usuario
- ❌ No avanzar a vista admin mejorada — eso es la etapa siguiente
- ❌ No hacer deploy — solo commit y push a Gitea
- ❌ No implementar el hook sin reportar primero el diagnóstico de 4.1
## 8. Versionado
```
feat(auth): Supabase Auth email/password + middleware + login [auth]
Protege /admin/* con Supabase Auth. Tabla user_roles con custom claims
(app_role, org_id) en JWT. Middleware Next.js redirige a /login sin sesión.
Página /login con email/password. /admin/responses migrado de service_role
a sesión de usuario autenticado.
```