import { NextResponse } from "next/server"; import { z } from "zod"; import { apiError, parseBody } from "@/lib/api.ts"; import { hashPassword } from "@/lib/auth/password.ts"; import { assertSameOrigin, destroyAllSessions, logAuthEvent, requireRole } from "@/lib/auth/session.ts"; import { all, get, run } from "@/lib/db/index.ts"; import { randomBytes } from "node:crypto"; export async function GET() { try { await requireRole("admin"); const users = all( `SELECT u.id, u.username, u.display_name, u.email, u.role, u.disabled, u.must_change_password, u.is_initial_admin, u.created_at, u.last_login_at, (SELECT GROUP_CONCAT(course_code) FROM enrollments e WHERE e.user_id = u.id) as courses, (SELECT COUNT(*) FROM sessions s WHERE s.user_id = u.id) as sessions FROM users u ORDER BY u.id` ); const events = all("SELECT * FROM auth_events ORDER BY id DESC LIMIT 100"); return NextResponse.json({ users, events }); } catch (e) { return apiError(e); } } const schema = z.object({ userId: z.number().int().positive(), role: z.enum(["student", "instructor", "admin"]).optional(), disabled: z.boolean().optional(), resetPassword: z.boolean().optional(), courses: z.array(z.enum(["IMM1003", "IMM1033"])).optional(), revokeSessions: z.boolean().optional(), }); export async function PATCH(req: Request) { try { await assertSameOrigin(); const admin = await requireRole("admin"); const body = await parseBody(req, schema); const target = get<{ id: number; username: string; role: string }>("SELECT id, username, role FROM users WHERE id = ?", body.userId); if (!target) return NextResponse.json({ error: "Utilisateur introuvable." }, { status: 404 }); if (target.id === admin.id && (body.disabled || (body.role && body.role !== "admin"))) { return NextResponse.json({ error: "Impossible de rétrograder ou désactiver votre propre compte." }, { status: 400 }); } let tempPassword: string | null = null; if (body.role) run("UPDATE users SET role = ? WHERE id = ?", body.role, target.id); if (body.disabled !== undefined) { run("UPDATE users SET disabled = ? WHERE id = ?", body.disabled ? 1 : 0, target.id); if (body.disabled) destroyAllSessions(target.id); } if (body.resetPassword) { tempPassword = "uqo-" + randomBytes(5).toString("hex"); run("UPDATE users SET password_hash = ?, must_change_password = 1 WHERE id = ?", hashPassword(tempPassword), target.id); destroyAllSessions(target.id); } if (body.revokeSessions) destroyAllSessions(target.id); if (body.courses) { run("DELETE FROM enrollments WHERE user_id = ?", target.id); for (const c of body.courses) run("INSERT OR IGNORE INTO enrollments (user_id, course_code) VALUES (?, ?)", target.id, c); } logAuthEvent("admin-user-update", { userId: target.id, username: target.username, detail: JSON.stringify({ by: admin.username, role: body.role, disabled: body.disabled, reset: !!body.resetPassword }), }); return NextResponse.json({ ok: true, tempPassword }); } catch (e) { return apiError(e); } }