ae91f4bfd5
- /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
43 lines
1.9 KiB
TypeScript
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();
|
|
});
|