import { defineMiddleware } from 'astro:middleware'; import { getSessionManager } from './lib/auth/session-manager.js'; // Public routes — must be reachable WITHOUT a valid session so the auth // flow itself can happen. Everything else requires authentication. // The dashboard at `/` is intentionally NOT public: it renders real // machine + session data. Add explicit landing/marketing paths here if // one is added later. const PUBLIC_API_PREFIX = '/api/auth/'; const PUBLIC_PAGE_PATHS = new Set([ '/login', '/login/', ]); const PUBLIC_ASSET_PREFIXES = ['/assets/', '/_astro/']; const PUBLIC_ASSET_FILES = new Set([ '/favicon.svg', '/favicon.ico', '/robots.txt', ]); function isPublicPath(pathname: string): boolean { if (pathname.startsWith(PUBLIC_API_PREFIX)) return true; if (PUBLIC_PAGE_PATHS.has(pathname)) return true; if (PUBLIC_ASSET_FILES.has(pathname)) return true; for (const prefix of PUBLIC_ASSET_PREFIXES) { if (pathname.startsWith(prefix)) return true; } return false; } export const onRequest = defineMiddleware(async (context, next) => { const { pathname } = context.url; if (isPublicPath(pathname)) { return next(); } const sessionMgr = getSessionManager(); const result = await sessionMgr.validateSession(context); if (result.valid && result.user) { context.locals.user = result.user; return next(); } // Unauthenticated: JSON for /api/*, redirect for pages. if (pathname.startsWith('/api/')) { return new Response( JSON.stringify({ authenticated: false, error: 'authentication_required', reason: result.reason ?? 'missing_cookie', login_url: '/api/auth/login', }), { status: 401, headers: { 'Content-Type': 'application/json' }, }, ); } const loginUrl = new URL('/api/auth/login', context.url); loginUrl.searchParams.set('return_to', pathname + context.url.search); return context.redirect(loginUrl.pathname + loginUrl.search, 302); });