feat(app): Next.js 15 + Auth.js v5 portal skeleton #4
Reference in New Issue
Block a user
Delete Branch "feat/skeleton"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
src/lib/(the only testable pure code so far).pnpm lint/typecheck/test --coverage/build.Why
Unblock M5.1 portal work for local dev. Pairs with
platform/orca-platform#4(dev stack) andplatform/tenant-registry#4(Go skeleton). Once all three merge,make dev-up+make dev× 2 +open http://acme.localhost:3000completes a real OIDC dance and renders/acme/dashboardwith session claims propagated.Linked milestone: M5.1 (skeleton)
How
src/middleware.tscallsparseHost(request.headers.get('host'))fromsrc/lib/host.tsand rewrites the request to inject the slug as a path prefix. URL bar is unchanged.backstage.<apex>is reserved and rewrites to/__backstage__/*(route doesn't exist yet — backstage shell lands in M5.2).APEX_HOSTSis iterated longest-first sostage.breakpilot.commatches beforebreakpilot.com— caught by a test (acme.stage.breakpilot.com→{kind:tenant, slug:acme}).clientSecretfield even for public clients; we pass a placeholderunused-public-clientand rely on PKCE for the code grant. The realm'sdev-portalclient haspublicClient: true+pkce.code.challenge.method: S256. Token / session callbacks lift the breakpilot-dev realm's custom claims (tenant_id,tenant_slug,org_roles,products,plan,tenant_status) into the session so downstream pages can read them offauth().signIn/signOuton/[slug]/dashboard— no client component needed.src/lib/invitest.config.ts— the rest ofsrc/is Next.js pages/middleware that vitest doesn't run. Same 100% requirement; the scope just narrows what we measure. When real logic lands outsidesrc/lib/, widen the include.Test plan
pnpm lint(next lint, max-warnings 0) ✅pnpm typecheck(tsc --noEmit) ✅pnpm test— 13 tests, 100% coverage of src/lib/ ✅pnpm build— compiles all routes, output: standalone ✅http://acme.localhost:3000, complete the Keycloak login, land on/acme/dashboardwith the test user's session claims visible. (Will do once all 3 PRs merge.)Risk
Blast radius: dev only. No prod manifest references this image yet.
What could break:
Record<string, unknown>casts to keep types honest while letting the runtime do its thing.pnpm install --frozen-lockfilein CI requires the committedpnpm-lock.yamlto matchpackage.jsonexactly. If a future PR forgets to commit a lockfile bump, CI fails fast — which is the point.unused-public-clientclientSecret is a load-bearing tiny lie. If anyone copies this pattern into prod with a confidential client, the auth flow breaks loudly (Keycloak rejects). The placeholder is local-only; prod gets a real secret per Infisical.Rollback plan: revert the PR. Nothing in prod or stage depends on this yet.
Checklist
src/lib/coverage).env.exampledocuments what dev needs.trivy fs scan failed the M0.2 CI gate on the skeleton commit because next 15.0.3 has 9 known vulns (CRITICAL CVE-2025-29927 auth bypass in middleware, plus 7 HIGH advisories). 16.2.6 is current latest and covers every fixed-version range trivy listed. Side effects of the major bump: - next 16 dropped 'next lint' — switched the lint script to call eslint directly ('eslint . --max-warnings 0'). - eslint-config-next 16 ships flat-config exports natively, so eslint.config.mjs imports core-web-vitals + typescript directly (no FlatCompat shim, no @eslint/eslintrc dep). - Typed vi.fn<typeof fetch>() in tenant-registry.test to satisfy stricter tuple inference under the new types. All 4 gates green locally: pnpm lint / typecheck / test --coverage (100% on src/lib) / build Refs: M5.1 (skeleton)cb91109b66toc051ae0626Two CI bugs the M0.2 ci-typescript.yaml template carried into portal: 1. 'pnpm test --coverage' is parsed as a pnpm option, not script args ('Unknown option: coverage'). Drop the extra flag; the package.json test script already runs 'vitest run --coverage'. 2. 'next build' requires AUTH_SECRET at compile time because Auth.js v5 reads it during route generation. Inject a per-build dummy secret in CI (production gets the real one via Orca env from Infisical). Refs: M5.1