fix: CSRF host-compare behind proxy + default port = creation order
All checks were successful
Build & Push Docker Images / lint (push) Successful in 3m1s
Build & Push Docker Images / build-and-push (push) Successful in 7m30s

Two prod-only breakages found after go-live:

1. CSRF guard rejected EVERY /api/v1 mutation ("Cross-origin state-changing
   request rejected", 403) — making the CRM read-only. It compared the
   browser Origin (https://crm.portnimara.com) against request.nextUrl.origin,
   but TLS terminates at nginx so the app sees http://127.0.0.1 → protocol
   mismatch. Compare hosts instead (Host header survives the proxy; a
   cross-site attacker can't forge the browser-set Origin host).

2. Post-login landed on port-amador (empty tenant), not port-nimara. Three
   queries ordered ports by name (alphabetical → Amador first): the bare
   /dashboard redirect (app/dashboard/page.tsx), the dashboard layout's
   defaultPortId, and /api/v1/me/ports. Order by createdAt so the primary
   (first-seeded) port — Port Nimara — leads, matching listPorts().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 03:38:12 +02:00
parent 23a5811342
commit d485695357
4 changed files with 18 additions and 11 deletions

View File

@@ -38,7 +38,7 @@ export default async function DashboardLayout({ children }: { children: React.Re
});
const ports = profile?.isSuperAdmin
? await db.query.ports.findMany({ orderBy: portsTable.name })
? await db.query.ports.findMany({ orderBy: portsTable.createdAt })
: portRoles.map((pr) => pr.port);
// Prefer a previously-resolved tier from the client's cookie so the