Aktueller Stand
This commit is contained in:
303
app/api/users/route.ts
Normal file
303
app/api/users/route.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import {
|
||||
isAdminSession,
|
||||
isSuperAdminSession,
|
||||
requireSession
|
||||
} from "../../../lib/auth-helpers";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { session } = await requireSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!isAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const status = searchParams.get("status");
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
where: status ? { status } : undefined,
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
status: true,
|
||||
role: true,
|
||||
emailVerified: true,
|
||||
createdAt: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!isSuperAdminSession(session)) {
|
||||
return NextResponse.json(users);
|
||||
}
|
||||
|
||||
const emails = users.map((user) => user.email).filter(Boolean);
|
||||
const attempts = emails.length
|
||||
? await prisma.loginAttempt.findMany({
|
||||
where: { email: { in: emails } }
|
||||
})
|
||||
: [];
|
||||
|
||||
const stats = attempts.reduce<Record<string, {
|
||||
attempts: number;
|
||||
lastAttempt: Date | null;
|
||||
lockedUntil: Date | null;
|
||||
}>>((acc, attempt) => {
|
||||
const current = acc[attempt.email] || {
|
||||
attempts: 0,
|
||||
lastAttempt: null,
|
||||
lockedUntil: null
|
||||
};
|
||||
current.attempts += attempt.attempts;
|
||||
if (!current.lastAttempt || attempt.lastAttempt > current.lastAttempt) {
|
||||
current.lastAttempt = attempt.lastAttempt;
|
||||
}
|
||||
if (!current.lockedUntil || (attempt.lockedUntil && attempt.lockedUntil > current.lockedUntil)) {
|
||||
current.lockedUntil = attempt.lockedUntil;
|
||||
}
|
||||
acc[attempt.email] = current;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const enriched = users.map((user) => ({
|
||||
...user,
|
||||
loginStats: stats[user.email] || {
|
||||
attempts: 0,
|
||||
lastAttempt: null,
|
||||
lockedUntil: null
|
||||
}
|
||||
}));
|
||||
|
||||
return NextResponse.json(enriched);
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { session } = await requireSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!isAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const {
|
||||
email,
|
||||
name,
|
||||
password,
|
||||
role,
|
||||
status,
|
||||
emailVerified
|
||||
} = body || {};
|
||||
|
||||
if (!email || !password) {
|
||||
return NextResponse.json(
|
||||
{ error: "E-Mail und Passwort sind erforderlich." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedEmail = String(email).trim().toLowerCase();
|
||||
const allowedRoles = ["USER", "ADMIN", "SUPERADMIN"];
|
||||
const allowedStatuses = ["ACTIVE", "PENDING", "DISABLED"];
|
||||
const isSuperAdmin = isSuperAdminSession(session);
|
||||
const nextRole = isSuperAdmin && allowedRoles.includes(role) ? role : "USER";
|
||||
const nextStatus = allowedStatuses.includes(status) ? status : "PENDING";
|
||||
|
||||
if (!normalizedEmail) {
|
||||
return NextResponse.json({ error: "Ungültige E-Mail." }, { status: 400 });
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(String(password), 10);
|
||||
|
||||
try {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: normalizedEmail,
|
||||
name: name ? String(name).trim() : null,
|
||||
passwordHash,
|
||||
role: nextRole,
|
||||
status: nextStatus,
|
||||
emailVerified: Boolean(emailVerified)
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
status: true,
|
||||
emailVerified: true,
|
||||
createdAt: true
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json(user, { status: 201 });
|
||||
} catch (err) {
|
||||
return NextResponse.json(
|
||||
{ error: "Benutzer konnte nicht angelegt werden." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
const { session } = await requireSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!isAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const {
|
||||
userId,
|
||||
status,
|
||||
role,
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
emailVerified,
|
||||
resetLoginAttempts
|
||||
} = body || {};
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: "Ungültige Anfrage." }, { status: 400 });
|
||||
}
|
||||
|
||||
const target = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { role: true }
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
return NextResponse.json({ error: "Benutzer nicht gefunden." }, { status: 404 });
|
||||
}
|
||||
|
||||
if (resetLoginAttempts) {
|
||||
if (!isSuperAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
const userRecord = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { email: true }
|
||||
});
|
||||
if (userRecord?.email) {
|
||||
await prisma.loginAttempt.deleteMany({ where: { email: userRecord.email } });
|
||||
}
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
if (target.role === "SUPERADMIN" && !isSuperAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const allowedStatuses = ["ACTIVE", "PENDING", "DISABLED"];
|
||||
const data: Record<string, any> = {};
|
||||
|
||||
if (status) {
|
||||
if (!allowedStatuses.includes(status)) {
|
||||
return NextResponse.json({ error: "Ungültiger Status." }, { status: 400 });
|
||||
}
|
||||
data.status = status;
|
||||
}
|
||||
|
||||
if (name !== undefined) {
|
||||
data.name = name ? String(name).trim() : null;
|
||||
}
|
||||
|
||||
if (email) {
|
||||
data.email = String(email).trim().toLowerCase();
|
||||
}
|
||||
|
||||
if (emailVerified !== undefined) {
|
||||
data.emailVerified = Boolean(emailVerified);
|
||||
}
|
||||
|
||||
if (password) {
|
||||
data.passwordHash = await bcrypt.hash(String(password), 10);
|
||||
}
|
||||
|
||||
if (role) {
|
||||
if (!isSuperAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
if (!["USER", "ADMIN", "SUPERADMIN"].includes(role)) {
|
||||
return NextResponse.json({ error: "Ungültige Rolle." }, { status: 400 });
|
||||
}
|
||||
data.role = role;
|
||||
}
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data,
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
status: true,
|
||||
emailVerified: true,
|
||||
createdAt: true
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({ id: user.id, status: user.status });
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const { session } = await requireSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!isAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const userId = searchParams.get("id");
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: "Ungültige Anfrage." }, { status: 400 });
|
||||
}
|
||||
|
||||
if (session.user?.id === userId) {
|
||||
return NextResponse.json(
|
||||
{ error: "Eigenes Konto kann nicht gelöscht werden." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const target = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { role: true }
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
return NextResponse.json({ error: "Benutzer nicht gefunden." }, { status: 404 });
|
||||
}
|
||||
|
||||
if (target.role !== "USER" && !isSuperAdminSession(session)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
await prisma.session.deleteMany({ where: { userId } });
|
||||
await prisma.account.deleteMany({ where: { userId } });
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { status: "DISABLED", emailVerified: false }
|
||||
});
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
Reference in New Issue
Block a user