import { defineMiddleware } from 'astro:middleware'; import { ensureInit } from './lib/init'; import { readSession } from './lib/auth'; import { one } from './lib/db'; const PUBLIC_PATHS = [/^\/login/, /^\/api\/auth\/login/, /^\/api\/health/, /^\/g\//, /^\/api\/telegram\/webhook/, /^\/llms\.txt/]; /** API-Token (Authorization: Bearer …) → synthetischer Admin-Nutzer für /api/*. */ async function tokenUser(header: string | null): Promise { const m = /^Bearer\s+(.+)$/i.exec(header || ''); if (!m) return null; const s = await one<{ api_token: string | null }>('SELECT api_token FROM settings WHERE id=1'); if (!s?.api_token || s.api_token !== m[1].trim()) return null; const admin = await one<{ id: string }>(`SELECT id FROM users WHERE role='admin' ORDER BY created_at LIMIT 1`); return { uid: admin?.id || null, role: 'admin', name: 'API', username: 'api' }; } export const onRequest = defineMiddleware(async (ctx, next) => { // Health/Webhook dürfen laufen, auch wenn Init noch hakt — sonst blockiert nichts. try { await ensureInit(); } catch (e) { if (ctx.url.pathname !== '/api/health') throw e; } let user = readSession(ctx.request.headers.get('cookie')); // Programmatischer Zugriff per API-Token (nur für /api/*, außer Admin-Bereich). if (!user && ctx.url.pathname.startsWith('/api/') && !ctx.url.pathname.startsWith('/api/admin')) { user = await tokenUser(ctx.request.headers.get('authorization')); } ctx.locals.user = user; const path = ctx.url.pathname; const isPublic = PUBLIC_PATHS.some((r) => r.test(path)); if (!isPublic && !user) { if (path.startsWith('/api/')) return new Response('Unauthorized', { status: 401 }); return ctx.redirect('/login'); } if (path.startsWith('/api/admin') || path.startsWith('/admin')) { if (user?.role !== 'admin') { if (path.startsWith('/api/')) return new Response('Forbidden', { status: 403 }); return ctx.redirect('/'); } } return next(); });