Ship an Astro middleware (src/middleware.ts) that runs on every request
and requires a valid Heady session for anything other than the login
flow itself. Previously any anonymous visitor could load /machines,
/users, /acls, /dns, /settings and the mock /api/users endpoint over
plaintext -- the SSR templates rendered as public shells and only the
front-end auth guard blocked interaction. That's a defense-in-depth
gap now that data endpoints are being wired up.
- src/middleware.ts: onRequest handler validates the heady_session
cookie via HeadySessionManager.validateSession(). Public allowlist:
/api/auth/*, /login, static assets. Unauthenticated /api/* returns
401 JSON; unauthenticated pages redirect to /api/auth/login with a
return_to query param so the OIDC round-trip lands back on the
original URL. Authenticated requests get context.locals.user set so
downstream pages can read Astro.locals.user without a second lookup.
- src/env.d.ts: type App.Locals.user as SessionUser so pages / APIs get
IDE feedback on typos.
- src/pages/{index,machines,acls,dns,users}.astro: 'export const
prerender = false' -- these pages render per-user data, they must
hit middleware on every request rather than being served as a
static file baked at build time. Without this, middleware runs
once during prerender (with no cookie) and writes a redirect as
the static HTML for the route.
- src/pages/api/{acls,users,dns/magic,dns/tailnet,settings/auth-keys}.ts:
same reason. API endpoints must be SSR so the middleware can gate
them at request time.