Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 4m25s
next build versuchte /admin statisch zu rendern. currentAdmin() rief dabei getAuth() vor headers() auf und scheiterte ohne BETTER_AUTH_SECRET im Build-Container. Jetzt werden zuerst die Request-Header gelesen, wodurch alle Admin-Routen dynamisch sind und Auth erst zur Laufzeit geladen wird. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
48 lines
2.0 KiB
TypeScript
48 lines
2.0 KiB
TypeScript
/**
|
|
* Serverseitige Zugriffskontrolle fuer den Adminbereich. Wird von JEDER
|
|
* Admin-Seite, Server-Action und jedem Admin-Endpunkt aufgerufen — der
|
|
* Proxy (src/proxy.ts) leitet nur vorab um und ist keine Sicherheitsgrenze.
|
|
*/
|
|
import { headers } from "next/headers";
|
|
import { redirect } from "next/navigation";
|
|
import type { Actor } from "./audit";
|
|
import { getAuth } from "./auth";
|
|
|
|
export type AdminUser = { id: string; email: string; name: string; twoFactorEnabled: boolean };
|
|
|
|
async function currentAdmin(): Promise<AdminUser | null> {
|
|
// Zuerst die Request-Header lesen: das macht jede Admin-Route dynamisch.
|
|
// Sonst versucht "next build" sie vorab zu rendern und scheitert ohne
|
|
// Secrets im Container (getAuth() braucht BETTER_AUTH_SECRET).
|
|
const requestHeaders = await headers();
|
|
const session = await getAuth().api.getSession({ headers: requestHeaders });
|
|
if (!session) return null;
|
|
const user = session.user as typeof session.user & { twoFactorEnabled?: boolean | null };
|
|
return { id: user.id, email: user.email, name: user.name, twoFactorEnabled: Boolean(user.twoFactorEnabled) };
|
|
}
|
|
|
|
/** Fuer Seiten: leitet zur Anmeldung bzw. zur 2FA-Einrichtung um. */
|
|
export async function requireAdminPage(options: { allowWithout2fa?: boolean } = {}): Promise<AdminUser> {
|
|
const admin = await currentAdmin();
|
|
if (!admin) redirect("/admin/anmelden");
|
|
if (!admin.twoFactorEnabled && !options.allowWithout2fa) redirect("/admin/sicherheit");
|
|
return admin;
|
|
}
|
|
|
|
/** Fuer Server-Actions und Endpunkte: wirft statt umzuleiten. */
|
|
export async function requireAdminAction(): Promise<AdminUser> {
|
|
const admin = await currentAdmin();
|
|
if (!admin || !admin.twoFactorEnabled) throw new Error("Nicht berechtigt.");
|
|
return admin;
|
|
}
|
|
|
|
/** Fuer Route-Handler: null statt Ausnahme. */
|
|
export async function adminOrNull(): Promise<AdminUser | null> {
|
|
const admin = await currentAdmin();
|
|
return admin?.twoFactorEnabled ? admin : null;
|
|
}
|
|
|
|
export function adminActor(admin: AdminUser): Actor {
|
|
return { type: "ADMIN", id: admin.id };
|
|
}
|