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