Aktueller Stand

This commit is contained in:
2026-01-18 00:40:01 +01:00
parent 68b63b8f06
commit 31aef02558
16 changed files with 352 additions and 43 deletions

View File

@@ -3,7 +3,7 @@ import { getServerSession } from "next-auth";
import { prisma } from "../../../lib/prisma";
import { isAdminSession, requireSession } from "../../../lib/auth-helpers";
import { authOptions } from "../../../lib/auth";
import { getAccessSettings } from "../../../lib/system-settings";
import { getAccessSettings, getEmailVerificationRequired } from "../../../lib/system-settings";
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
@@ -13,7 +13,8 @@ export async function GET(request: Request) {
{ status: 403 }
);
}
if (session?.user?.emailVerified === false) {
const emailVerificationRequired = await getEmailVerificationRequired();
if (emailVerificationRequired && session?.user?.emailVerified === false) {
return NextResponse.json(
{ error: "E-Mail nicht verifiziert." },
{ status: 403 }

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import { prisma } from "../../../lib/prisma";
import { requireSession } from "../../../lib/auth-helpers";
import { sendMail } from "../../../lib/mailer";
import { getEmailVerificationRequired } from "../../../lib/system-settings";
export async function PATCH(request: Request) {
const { session } = await requireSession();
@@ -36,6 +37,7 @@ export async function PATCH(request: Request) {
}
const data: { email?: string; passwordHash?: string; emailVerified?: boolean } = {};
let emailVerificationRequired: boolean | null = null;
if (normalizedEmail && normalizedEmail !== user.email) {
const existing = await prisma.user.findUnique({ where: { email: normalizedEmail } });
@@ -46,7 +48,8 @@ export async function PATCH(request: Request) {
);
}
data.email = normalizedEmail;
data.emailVerified = false;
emailVerificationRequired = await getEmailVerificationRequired();
data.emailVerified = !emailVerificationRequired;
}
if (newPassword) {
@@ -63,6 +66,17 @@ export async function PATCH(request: Request) {
});
if (data.email) {
const verificationRequired =
emailVerificationRequired ?? (await getEmailVerificationRequired());
if (!verificationRequired) {
return NextResponse.json({
id: updated.id,
email: updated.email,
changedEmail: true,
changedPassword: Boolean(data.passwordHash)
});
}
const token = crypto.randomUUID();
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000);
await prisma.verificationToken.create({

View File

@@ -6,6 +6,7 @@ import { isAdminEmail, isSuperAdminEmail } from "../../../lib/auth";
import { sendMail } from "../../../lib/mailer";
import { checkRateLimit, getRateLimitConfig } from "../../../lib/rate-limit";
import { getClientIp } from "../../../lib/request";
import { getEmailVerificationRequired } from "../../../lib/system-settings";
export async function POST(request: Request) {
const registrationSetting = await prisma.setting.findUnique({
@@ -49,6 +50,9 @@ export async function POST(request: Request) {
const passwordHash = await bcrypt.hash(password, 10);
const superAdmin = isSuperAdminEmail(normalizedEmail);
const admin = isAdminEmail(normalizedEmail) || superAdmin;
const emailVerificationRequired = await getEmailVerificationRequired();
const shouldVerifyEmail = emailVerificationRequired && !admin;
const emailVerified = admin || !emailVerificationRequired;
const user = await prisma.user.create({
data: {
@@ -57,7 +61,7 @@ export async function POST(request: Request) {
passwordHash,
role: superAdmin ? "SUPERADMIN" : admin ? "ADMIN" : "USER",
status: admin ? "ACTIVE" : "PENDING",
emailVerified: admin
emailVerified
}
});
@@ -82,7 +86,7 @@ export async function POST(request: Request) {
});
}
if (!admin) {
if (shouldVerifyEmail) {
const token = randomUUID();
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000);
await prisma.verificationToken.create({

View File

@@ -4,9 +4,15 @@ import { prisma } from "../../../../lib/prisma";
export const dynamic = "force-dynamic";
export async function GET() {
const setting = await prisma.setting.findUnique({
where: { key: "registration_enabled" }
const settings = await prisma.setting.findMany({
where: { key: { in: ["registration_enabled", "email_verification_required"] } }
});
const map = new Map(settings.map((record) => [record.key, record.value]));
const registrationEnabled = map.get("registration_enabled") !== "false";
const emailVerificationRequired =
map.get("email_verification_required") !== "false";
return NextResponse.json({
registrationEnabled,
emailVerificationRequired
});
const registrationEnabled = setting?.value !== "false";
return NextResponse.json({ registrationEnabled });
}

View File

@@ -27,7 +27,8 @@ export async function POST(request: Request) {
apiKey,
provider,
registrationEnabled,
publicAccessEnabled
publicAccessEnabled,
emailVerificationRequired
} = body || {};
if (!provider || !["google", "osm"].includes(provider)) {
@@ -59,6 +60,14 @@ export async function POST(request: Request) {
create: { key: "registration_enabled", value: registrationValue }
});
const verificationValue =
emailVerificationRequired === false ? "false" : "true";
await prisma.setting.upsert({
where: { key: "email_verification_required" },
update: { value: verificationValue },
create: { key: "email_verification_required", value: verificationValue }
});
const existing = await getSystemSettings();
const nextPublicAccessEnabled =
typeof publicAccessEnabled === "boolean"
@@ -79,6 +88,7 @@ export async function POST(request: Request) {
apiKey: apiKeySetting.value,
provider: providerSetting.value,
registrationEnabled: registrationValue !== "false",
publicAccessEnabled: nextPublicAccessEnabled
publicAccessEnabled: nextPublicAccessEnabled,
emailVerificationRequired: verificationValue !== "false"
});
}

View File

@@ -266,6 +266,7 @@ export async function DELETE(request: Request) {
const { searchParams } = new URL(request.url);
const userId = searchParams.get("id");
const hardDelete = searchParams.get("hard") === "true";
if (!userId) {
return NextResponse.json({ error: "Ungültige Anfrage." }, { status: 400 });
@@ -291,6 +292,71 @@ export async function DELETE(request: Request) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
if (hardDelete) {
if (!isSuperAdminSession(session)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const user = await prisma.user.findUnique({
where: { id: userId },
select: { email: true }
});
if (!user) {
return NextResponse.json({ error: "Benutzer nicht gefunden." }, { status: 404 });
}
const events = await prisma.event.findMany({
where: { createdById: userId },
select: { id: true }
});
const eventIds = events.map((event) => event.id);
const views = await prisma.userView.findMany({
where: { userId },
select: { id: true }
});
const viewIds = views.map((view) => view.id);
await prisma.$transaction(async (tx) => {
if (eventIds.length > 0) {
await tx.userViewItem.deleteMany({
where: { eventId: { in: eventIds } }
});
await tx.userViewExclusion.deleteMany({
where: { eventId: { in: eventIds } }
});
await tx.event.deleteMany({
where: { id: { in: eventIds } }
});
}
if (viewIds.length > 0) {
await tx.userViewItem.deleteMany({
where: { viewId: { in: viewIds } }
});
await tx.userViewCategory.deleteMany({
where: { viewId: { in: viewIds } }
});
await tx.userViewExclusion.deleteMany({
where: { viewId: { in: viewIds } }
});
await tx.userView.deleteMany({
where: { id: { in: viewIds } }
});
}
await tx.session.deleteMany({ where: { userId } });
await tx.account.deleteMany({ where: { userId } });
await tx.passwordResetToken.deleteMany({ where: { userId } });
await tx.loginAttempt.deleteMany({ where: { email: user.email } });
await tx.verificationToken.deleteMany({ where: { identifier: user.email } });
await tx.user.delete({ where: { id: userId } });
});
return NextResponse.json({ ok: true, deleted: true });
}
await prisma.session.deleteMany({ where: { userId } });
await prisma.account.deleteMany({ where: { userId } });