feat(api): API-Tokens, kurze Anfragen und OpenAPI für die CLI

- ApiToken (nur SHA-256), Bearer-Anmeldung, POST /auth/token für proxdeploy login, Verwaltung in den Einstellungen
- anfrageErgaenzen: Storage, Bridge, Node, Benutzer und Schlüssel aus Server-Standards, Verbindungstest und Anmeldung – auch beim Template-Bau
- Abschlussbox serverseitig (abschluss), Web und CLI zeigen dieselben Zeilen
- Antwortschemas als zod, daraus /api/v1/openapi.json und cli/api/openapi.json (pnpm openapi)

Refs #013

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:08:27 +02:00
co-authored by Claude Opus 5.5
parent 1e195f9bca
commit 9fdced62db
27 changed files with 1386 additions and 58 deletions
+9 -2
View File
@@ -6,6 +6,7 @@ import { prisma } from "@/lib/db";
import { SITZUNG_COOKIE, sitzungLesen } from "@/lib/sitzung";
import { darf, type Recht } from "@/server/auth/rechte";
import { keinRecht, nichtAngemeldet } from "@/server/fehler";
import { benutzerAusToken } from "@/server/services/tokens";
/**
* Wer fragt an? Rolle und Aktiv-Status kommen **je Anfrage aus der Datenbank**, nie aus dem
@@ -45,10 +46,16 @@ function cookieAusKopf(kopf: string | null, name: string): string | undefined {
}
/**
* Für Route Handler: Sitzungscookie (Web). Bearer-Tokens der CLI kommen mit #013 dazu und
* landen hier, damit kein Handler zwei Wege kennen muss.
* Für Route Handler: Bearer-Token (CLI) oder Sitzungscookie (Web) – hier an einer Stelle, damit
* kein Handler zwei Wege kennen muss. Ein angegebenes, aber ungültiges Token gilt als nicht
* angemeldet, auch wenn zusätzlich ein Cookie mitkommt.
*/
export async function anfrageAufrufer(anfrage: Request): Promise<Aufrufer | null> {
const kopf = anfrage.headers.get("authorization");
if (kopf?.startsWith("Bearer ")) {
const ausToken = await benutzerAusToken(kopf.slice("Bearer ".length).trim());
return ausToken ? { benutzer: ausToken, quelle: "CLI" } : null;
}
const benutzer = await benutzerAusSitzung(cookieAusKopf(anfrage.headers.get("cookie"), SITZUNG_COOKIE));
return benutzer ? { benutzer, quelle: "WEB" } : null;
}