Files
nebula/src/app/api/account/sessions/route.ts
T
florian.wildeandClaude Opus 5 cf4103a46c 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>
2026-07-25 21:30:33 +02:00

45 lines
1.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import { requireUserApi } from "@/lib/auth/guard";
import { createSession, destroyAllSessions, setSessionCookie } from "@/lib/auth/session";
import { prisma } from "@/lib/db";
import { clientIp } from "@/lib/security/request";
export const dynamic = "force-dynamic";
/** Uebersicht der eigenen aktiven Sitzungen. */
export async function GET() {
const auth = await requireUserApi();
if (!auth.ok) return auth.response;
const sessions = await prisma.session.findMany({
where: { userId: auth.user.id, expiresAt: { gt: new Date() } },
select: {
id: true,
userAgent: true,
ip: true,
createdAt: true,
lastSeenAt: true,
expiresAt: true,
},
orderBy: { lastSeenAt: "desc" },
});
return NextResponse.json({ sessions });
}
/** Ueberall abmelden – das aktuelle Geraet bleibt angemeldet. */
export async function DELETE(request: Request) {
const auth = await requireUserApi();
if (!auth.ok) return auth.response;
await destroyAllSessions(auth.user.id);
const session = await createSession(auth.user.id, {
userAgent: request.headers.get("user-agent"),
ip: clientIp(request),
});
const response = NextResponse.json({ ok: true });
setSessionCookie(response, session.token, session.expiresAt);
return response;
}