6.2 KiB
6.2 KiB
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()ycurrent_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) yorg_admin(acceso a su org). - Roles van como custom claims en el JWT:
app_roleyorg_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:
-- 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:
{ "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
:emaily:passwordcon instrucciones de cómo ejecutarlo: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
20260627000001aplicada en VPS — verificar con:docker exec -i supabase-db psql -U postgres -d postgres -c "\d user_roles" - Script
scripts/setup_first_admin.sqlexiste y está documentado - Hook JWT verificado — el token incluye
app_role(cómo verificarlo: reportar el método) /loginfunciona — email/password → sesión activa → redirect a /admin/responses/admin/responsessin sesión → redirect a/login/admin/responsescon 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.