# 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. ```