From 5fd56e3f67fece2891951f5015ac2c76cee36609 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Tue, 5 May 2026 14:38:42 +0200 Subject: [PATCH] feat(ST-111ci8t4): admin user-actions (delete, updateRoles, setMustResetPassword) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/session.ts: isAdmin: boolean toegevoegd aan SessionData - lib/auth-guard.ts: requireAdmin() toegevoegd (redirect /dashboard bij !isAdmin) - actions/admin/users.ts: deleteUserAction (zelfbescherming), updateUserRolesAction (Zod z.nativeEnum, eigen ADMIN-rol-beveiliging, transactie), setMustResetPasswordAction — alle drie 'use server', revalidatePath('/admin/users') --- actions/admin/users.ts | 43 ++++++++++++++++++++++++++++++++++++++++++ lib/auth-guard.ts | 8 ++++++++ lib/session.ts | 1 + 3 files changed, 52 insertions(+) create mode 100644 actions/admin/users.ts diff --git a/actions/admin/users.ts b/actions/admin/users.ts new file mode 100644 index 0000000..c7698fa --- /dev/null +++ b/actions/admin/users.ts @@ -0,0 +1,43 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { z } from 'zod' +import { Role } from '@prisma/client' +import { prisma } from '@/lib/prisma' +import { requireAdmin } from '@/lib/auth-guard' + +export async function deleteUserAction(userId: string) { + const session = await requireAdmin() + if (userId === session.userId) { + throw new Error('Zelfverwijdering niet toegestaan') + } + await prisma.user.delete({ where: { id: userId } }) + revalidatePath('/admin/users') +} + +const rolesSchema = z.array(z.nativeEnum(Role)) + +export async function updateUserRolesAction(userId: string, roles: Role[]) { + const session = await requireAdmin() + + const parsed = rolesSchema.safeParse(roles) + if (!parsed.success) { + throw new Error('Ongeldige rol-waarden') + } + + if (userId === session.userId && !parsed.data.includes(Role.ADMIN)) { + throw new Error('Kan eigen ADMIN-rol niet verwijderen') + } + + await prisma.$transaction([ + prisma.userRole.deleteMany({ where: { user_id: userId } }), + ...parsed.data.map((role) => prisma.userRole.create({ data: { user_id: userId, role } })), + ]) + revalidatePath('/admin/users') +} + +export async function setMustResetPasswordAction(userId: string, value: boolean) { + await requireAdmin() + await prisma.user.update({ where: { id: userId }, data: { must_reset_password: value } }) + revalidatePath('/admin/users') +} diff --git a/lib/auth-guard.ts b/lib/auth-guard.ts index 8b6baf5..e82a568 100644 --- a/lib/auth-guard.ts +++ b/lib/auth-guard.ts @@ -22,3 +22,11 @@ export async function requireSession() { return session } + +export async function requireAdmin() { + const session = await getSession() + if (!session.userId || !session.isAdmin) { + redirect('/dashboard') + } + return session +} diff --git a/lib/session.ts b/lib/session.ts index bf1f9a9..5d7c587 100644 --- a/lib/session.ts +++ b/lib/session.ts @@ -3,6 +3,7 @@ import { SessionOptions } from 'iron-session' export interface SessionData { userId: string isDemo: boolean + isAdmin: boolean // ST-1002 (M10) — gezet door /api/auth/pair/claim na een succesvolle QR-pairing. // Beide velden zijn optioneel zodat bestaande wachtwoord-sessies onveranderd blijven. paired?: boolean