Limpieza post-diagnóstico del 403 (checkOrigin resuelto)

- Elimina /api/whoami (endpoint diagnóstico temporal).
- Elimina bloque debug del 403 en /api/admin/prestamos/[id]/devolver.
- Bitácora con la causa raíz y el fix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 14:22:10 -07:00
parent 311f14b0eb
commit 927d99c262
3 changed files with 8 additions and 17 deletions
+7
View File
@@ -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.
@@ -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);
-11
View File
@@ -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' } },
);
};