diff --git a/AGENTS.md b/AGENTS.md index a2716d0..6b33a3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,3 +147,10 @@ Fases 3, 4/5 y 6 tocan carpetas de rutas distintas (`src/pages/alumno/*`, `src/p - **Preview local vía Cloudflare tunnel efímero**: se probó con `cloudflared tunnel --url http://localhost:4321` + flag temporal `?preview=alumno|admin` en el middleware (que impersonaba con service_role bajo `import.meta.env.DEV`). El flag se removió antes del deploy — el middleware en prod ya no lo lee. También se removió el `server.allowedHosts` del `astro.config.mjs` que se había abierto solo para el túnel. - **Deploy**: `git push` → `ssh buglabs "cd ~/labre-web && git pull && docker compose up -d --build"`. Container `labre-web` recreado, healthy en ~15s. Smoke test público: `/login` → 200, `/` → 302, `/api/prestamos` → 302. Downtime real observado: ninguno perceptible. - Pendiente: usuario debe visitar `https://prestamos.buglabs.dev/api/whoami` en prod y compartir el JSON (o pegar el objeto `debug` que aparece en la respuesta del "Marcar devuelto") para diagnosticar el 403 residual. + +- **2026-08-16 — Bug del 403 resuelto: era Astro `security.checkOrigin`**. + - Causa raíz: Astro 5+ activa por default `security.checkOrigin`, que compara `Origin` con `Host` en peticiones POST/PATCH/PUT/DELETE. Detrás de Cloudflare Tunnel esos headers no matchean literalmente, así que Astro corta con 403 y texto plano `"Cross-site POST form submissions are forbidden"` — nunca llega al handler. Por eso los `console.log` del endpoint no salían y `debug` del body no aparecía: Astro respondía antes. + - Fix: `security: { checkOrigin: false }` en `astro.config.mjs`. Los endpoints ya están protegidos por cookies `HttpOnly` + `SameSite=Lax` y flow OAuth con PKCE, así que la protección CSRF de Astro es redundante y solo estorba en este stack. + - Confirmado en prod por el usuario: "Marcar devuelto" ahora funciona. El mismo fix cubre aprobar/rechazar y cualquier otro POST admin que hubiera fallado con el mismo síntoma. + - Limpieza: removido `/api/whoami` (endpoint diagnóstico) y el bloque `debug` que devolvía email+rol en el 403 del endpoint devolver. Ya no aplican. + - Aprendizaje: cuando un endpoint responde 403 con **texto plano** ("Cross-site POST..."), es Astro; cuando responde 403 con **JSON** (`{"error":"no autorizado"}`), es lógica del endpoint. Ese matiz habría acelerado el diagnóstico. diff --git a/src/pages/api/admin/prestamos/[id]/devolver.ts b/src/pages/api/admin/prestamos/[id]/devolver.ts index f8b5b9b..f0945a7 100644 --- a/src/pages/api/admin/prestamos/[id]/devolver.ts +++ b/src/pages/api/admin/prestamos/[id]/devolver.ts @@ -5,12 +5,7 @@ const json = (body: unknown, status = 200) => export const POST: APIRoute = async ({ params, locals }) => { if (!locals.user) return json({ error: 'no autenticado' }, 401); - if (locals.profile?.rol !== 'admin') { - return json({ - error: 'no autorizado', - debug: { email: locals.user.email, profile_rol: locals.profile?.rol ?? null }, - }, 403); - } + if (locals.profile?.rol !== 'admin') return json({ error: 'no autorizado' }, 403); const id = Number(params.id); if (!Number.isInteger(id) || id <= 0) return json({ error: 'id inválido' }, 400); diff --git a/src/pages/api/whoami.ts b/src/pages/api/whoami.ts deleted file mode 100644 index dd4fb1f..0000000 --- a/src/pages/api/whoami.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { APIRoute } from 'astro'; - -export const GET: APIRoute = async ({ locals }) => { - return new Response( - JSON.stringify({ - user: locals.user ? { id: locals.user.id, email: locals.user.email } : null, - profile: locals.profile, - }, null, 2), - { headers: { 'content-type': 'application/json' } }, - ); -};