import { cookies } from "next/headers"; import { NextResponse } from "next/server"; import { isSessionTokenRevoked, isActionTokenUsed } from "./token-blacklist"; const encoder = new TextEncoder(); interface AdminSession { id: string; email: string; name: string; exp: number; } interface ActionToken { anfrageId: string; status: "in_bearbeitung" | "abgeschlossen"; exp: number; } function toBase64Url(buffer: ArrayBuffer | Uint8Array): string { const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function fromBase64Url(str: string): ArrayBuffer { const b64 = str.replace(/-/g, "+").replace(/_/g, "/"); const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); return bytes.buffer as ArrayBuffer; } async function getKey(secret: string): Promise { return crypto.subtle.importKey( "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"] ); } export async function createSessionToken( session: Omit ): Promise { const secret = process.env.ADMIN_SESSION_TOKEN!; const payload: AdminSession = { ...session, exp: Math.floor(Date.now() / 1000) + 60 * 60 * 2, }; const data = toBase64Url(encoder.encode(JSON.stringify(payload))); const key = await getKey(secret); const sig = toBase64Url(await crypto.subtle.sign("HMAC", key, encoder.encode(data))); return `${data}.${sig}`; } export async function requireAdmin(): Promise { const cookieStore = await cookies(); const token = cookieStore.get("admin_session")?.value; if (!token) { return NextResponse.json({ error: "Nicht authentifiziert" }, { status: 401 }); } const session = await verifySessionToken(token); if (!session) { return NextResponse.json({ error: "Session ungültig" }, { status: 401 }); } return session; } export async function verifySessionToken(token: string): Promise { try { const secret = process.env.ADMIN_SESSION_TOKEN!; const [data, sig] = token.split("."); if (!data || !sig) return null; if (await isSessionTokenRevoked(sig)) return null; const key = await getKey(secret); const valid = await crypto.subtle.verify( "HMAC", key, fromBase64Url(sig), encoder.encode(data) ); if (!valid) return null; const session: AdminSession = JSON.parse( new TextDecoder().decode(new Uint8Array(fromBase64Url(data))) ); if (session.exp < Math.floor(Date.now() / 1000)) return null; return session; } catch { return null; } } export async function createActionToken( anfrageId: string, status: "in_bearbeitung" | "abgeschlossen", ablaufTage = 7 ): Promise { const secret = process.env.ADMIN_SESSION_TOKEN!; const payload: ActionToken = { anfrageId, status, exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * ablaufTage, }; const data = toBase64Url(encoder.encode(JSON.stringify(payload))); const key = await getKey(secret); const sig = toBase64Url(await crypto.subtle.sign("HMAC", key, encoder.encode(data))); return `${data}.${sig}`; } export async function verifyActionToken( token: string ): Promise<{ anfrageId: string; status: string } | null> { try { const secret = process.env.ADMIN_SESSION_TOKEN!; const [data, sig] = token.split("."); if (!data || !sig) return null; if (await isActionTokenUsed(sig)) return null; const key = await getKey(secret); const valid = await crypto.subtle.verify( "HMAC", key, fromBase64Url(sig), encoder.encode(data) ); if (!valid) return null; const actionToken: ActionToken = JSON.parse( new TextDecoder().decode(new Uint8Array(fromBase64Url(data))) ); if (actionToken.exp < Math.floor(Date.now() / 1000)) return null; return { anfrageId: actionToken.anfrageId, status: actionToken.status }; } catch { return null; } }