Files
klarbild/src/middleware.ts
T
till ae91f4bfd5 feat: llms.txt (AI-readable docs), job delete, friendly missing-source error
- /llms.txt: public machine-readable capability/API/MCP doc for agents.
- Job delete action + queue button for terminal jobs; cleans objects.
- process.ts: clear 'Quelle nicht mehr vorhanden' message instead of raw
  EISDIR/ENOENT when a source was purged.
- Docs corrected: Picdrop 'missing images' was web-UI sorting, not PNG;
  JPG delivery kept as size/perf improvement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
2026-07-24 08:57:31 +00:00

43 lines
1.9 KiB
TypeScript

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<any | null> {
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();
});