auth: stateless PKCE state, simplify OIDC handlers

- src/lib/auth/oidc-state.ts (new): pack {state, codeVerifier} into a
  short-lived signed JWT, store in an httpOnly cookie. Replaces the
  process-local Map that broke under multi-worker deployments where
  /api/auth/login and /api/auth/callback would land on different workers.
- src/pages/api/auth/{login,callback,logout,profile,status}.ts: drop the
  Map-based state-store calls; use oidc-state for set/get/clear.
- src/lib/auth/oidc-client.ts, session-manager.ts, config/authentik.ts:
  small adjustments to fit the new state surface.
- src/components/auth/AuthenticatedLayout.astro, src/layouts/Layout.astro:
  trim a lot of layout boilerplate (~125 lines each).
- astro.config.mjs, docker-compose.local.yml: minor cleanup.
- authentik-blueprints/heady-oidc.yaml (new): declarative provider +
  application blueprint to ship alongside Heady deployments.
This commit is contained in:
Ryan Malloy 2026-06-06 14:11:51 -06:00
parent 0b8812d864
commit 21175c5b7a
14 changed files with 373 additions and 527 deletions

View file

@ -9,28 +9,11 @@ import type { APIRoute } from 'astro';
import { getCapabilitiesForRole } from '../../../lib/auth/role-mapper.js';
import { getSessionManager } from '../../../lib/auth/session-manager.js';
// Force SSR — this route reads env vars and must not be prerendered
export const prerender = false;
export const GET: APIRoute = async ({ cookies }) => {
try {
// During build/prerender, return not authenticated response
if (
process.env.NODE_ENV === 'development' &&
!process.env.CI &&
!process.env.SESSION_SECRET
) {
return new Response(
JSON.stringify({
authenticated: false,
user: null,
login_url: '/api/auth/login',
message: 'Authentication available at runtime',
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
},
);
}
const sessionMgr = getSessionManager();
// Check for authentication