SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
3.1 KB · 71 lines typescript
Raw Blame History
1import { NextResponse } from "next/server";2import { z } from "zod";3import { apiError, parseBody } from "@/lib/api.ts";4import { hashPassword } from "@/lib/auth/password.ts";5import { assertSameOrigin, destroyAllSessions, logAuthEvent, requireRole } from "@/lib/auth/session.ts";6import { all, get, run } from "@/lib/db/index.ts";7import { randomBytes } from "node:crypto";89export async function GET() {10  try {11    await requireRole("admin");12    const users = all(13      `SELECT u.id, u.username, u.display_name, u.email, u.role, u.disabled, u.must_change_password,14              u.is_initial_admin, u.created_at, u.last_login_at,15              (SELECT GROUP_CONCAT(course_code) FROM enrollments e WHERE e.user_id = u.id) as courses,16              (SELECT COUNT(*) FROM sessions s WHERE s.user_id = u.id) as sessions17       FROM users u ORDER BY u.id`18    );19    const events = all("SELECT * FROM auth_events ORDER BY id DESC LIMIT 100");20    return NextResponse.json({ users, events });21  } catch (e) {22    return apiError(e);23  }24}2526const schema = z.object({27  userId: z.number().int().positive(),28  role: z.enum(["student", "instructor", "admin"]).optional(),29  disabled: z.boolean().optional(),30  resetPassword: z.boolean().optional(),31  courses: z.array(z.enum(["IMM1003", "IMM1033"])).optional(),32  revokeSessions: z.boolean().optional(),33});3435export async function PATCH(req: Request) {36  try {37    await assertSameOrigin();38    const admin = await requireRole("admin");39    const body = await parseBody(req, schema);40    const target = get<{ id: number; username: string; role: string }>("SELECT id, username, role FROM users WHERE id = ?", body.userId);41    if (!target) return NextResponse.json({ error: "Utilisateur introuvable." }, { status: 404 });42    if (target.id === admin.id && (body.disabled || (body.role && body.role !== "admin"))) {43      return NextResponse.json({ error: "Impossible de rétrograder ou désactiver votre propre compte." }, { status: 400 });44    }4546    let tempPassword: string | null = null;47    if (body.role) run("UPDATE users SET role = ? WHERE id = ?", body.role, target.id);48    if (body.disabled !== undefined) {49      run("UPDATE users SET disabled = ? WHERE id = ?", body.disabled ? 1 : 0, target.id);50      if (body.disabled) destroyAllSessions(target.id);51    }52    if (body.resetPassword) {53      tempPassword = "uqo-" + randomBytes(5).toString("hex");54      run("UPDATE users SET password_hash = ?, must_change_password = 1 WHERE id = ?", hashPassword(tempPassword), target.id);55      destroyAllSessions(target.id);56    }57    if (body.revokeSessions) destroyAllSessions(target.id);58    if (body.courses) {59      run("DELETE FROM enrollments WHERE user_id = ?", target.id);60      for (const c of body.courses) run("INSERT OR IGNORE INTO enrollments (user_id, course_code) VALUES (?, ?)", target.id, c);61    }62    logAuthEvent("admin-user-update", {63      userId: target.id, username: target.username,64      detail: JSON.stringify({ by: admin.username, role: body.role, disabled: body.disabled, reset: !!body.resetPassword }),65    });66    return NextResponse.json({ ok: true, tempPassword });67  } catch (e) {68    return apiError(e);69  }70}71