ETAPA_CALIDAD: Nota de discrepancia: las 5 clases de P1 usan var(--color-primary), no var(--color-re-teal) como decía el prompt — ambas resuelven al mismo hex #43BBC8. Hice el reemplazo por nombre de clase (igual intención: texto teal → carbón), no por el literal del grep.
This commit is contained in:
148
sprints/sprint-4/ETAPA_AUTH_PROMPT.md
Normal file
148
sprints/sprint-4/ETAPA_AUTH_PROMPT.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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.
|
||||
```
|
||||
Reference in New Issue
Block a user