feat(admin): Kontoverwaltung und Admin-Einstellungen
"Mein Konto": Profil, Passwort setzen oder wechseln, Passkeys anlegen, umbenennen und entfernen, aktive Sitzungen einsehen und ueberall abmelden. Der letzte Passkey laesst sich nicht entfernen, solange kein Passwort gesetzt ist - sonst waere das Konto ausgesperrt. Admin-Einstellungen: Betriebsart umschalten, Selbstregistrierung freigeben, Konten anlegen, bearbeiten, deaktivieren und loeschen. Die Instanzwerte aus der .env werden angezeigt, damit sichtbar ist, warum Passkeys gegebenenfalls nicht verfuegbar sind. Schutzregeln: - Der Wechsel in den Einzelbetrieb wird abgelehnt, solange mehr als ein Konto existiert. - Der letzte aktive Administrator kann weder herabgestuft noch deaktiviert oder geloescht werden. - Das eigene Konto laesst sich nicht loeschen oder deaktivieren. - Deaktivieren oder ein neues Passwort beenden sofort alle Sitzungen des Kontos. Administratoren verwalten Konten, keine Inhalte: fremde Projekte sehen auch sie nicht. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireAdminApi } from "@/lib/auth/guard";
|
||||
import { hashPassword, validatePasswordStrength } from "@/lib/auth/password";
|
||||
import { getInstanceState } from "@/lib/auth/settings";
|
||||
import { userSafeSelect } from "@/lib/auth/users";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { jsonError, readJsonBody } from "@/lib/security/request";
|
||||
import { adminUserCreateSchema, formatZodError } from "@/lib/validation";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const auth = await requireAdminApi();
|
||||
if (!auth.ok) return auth.response;
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
select: userSafeSelect,
|
||||
orderBy: [{ role: "asc" }, { username: "asc" }],
|
||||
});
|
||||
|
||||
return NextResponse.json({ users });
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const auth = await requireAdminApi();
|
||||
if (!auth.ok) return auth.response;
|
||||
|
||||
const state = await getInstanceState();
|
||||
if (!state.multiUser) {
|
||||
return jsonError(
|
||||
"Im Einzelbetrieb kann kein weiteres Konto angelegt werden. " +
|
||||
"Bitte zuerst in den Mehrbenutzerbetrieb wechseln.",
|
||||
409,
|
||||
);
|
||||
}
|
||||
|
||||
const body = await readJsonBody(request);
|
||||
if (!body.ok) return body.response;
|
||||
|
||||
const parsed = adminUserCreateSchema.safeParse(body.data);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: "Validierung fehlgeschlagen", fields: formatZodError(parsed.error) },
|
||||
{ status: 422 },
|
||||
);
|
||||
}
|
||||
|
||||
const { username, displayName, email, password, role } = parsed.data;
|
||||
|
||||
const weak = validatePasswordStrength(password);
|
||||
if (weak) {
|
||||
return NextResponse.json({ error: weak, fields: { password: weak } }, { status: 422 });
|
||||
}
|
||||
|
||||
const taken = await prisma.user.findUnique({ where: { username }, select: { id: true } });
|
||||
if (taken) {
|
||||
return NextResponse.json(
|
||||
{ error: "Dieser Benutzername ist bereits vergeben.", fields: { username: "Bereits vergeben" } },
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
if (email) {
|
||||
const emailTaken = await prisma.user.findUnique({ where: { email }, select: { id: true } });
|
||||
if (emailTaken) {
|
||||
return NextResponse.json(
|
||||
{ error: "Diese E-Mail-Adresse wird bereits verwendet.", fields: { email: "Bereits vergeben" } },
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
displayName: displayName ?? null,
|
||||
email: email ?? null,
|
||||
passwordHash: await hashPassword(password),
|
||||
role,
|
||||
},
|
||||
select: userSafeSelect,
|
||||
});
|
||||
|
||||
return NextResponse.json({ user }, { status: 201 });
|
||||
}
|
||||
Reference in New Issue
Block a user