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%
2.4 KB · 63 lines typescript
Raw Blame History
1import { NextResponse } from "next/server";2import { z } from "zod";3import { apiError, parseBody } from "@/lib/api.ts";4import { verifyPassword } from "@/lib/auth/password.ts";5import { rateLimit } from "@/lib/auth/rate-limit.ts";6import { SESSION_COOKIE, assertSameOrigin, createSession, logAuthEvent } from "@/lib/auth/session.ts";7import { get, run } from "@/lib/db/index.ts";89const schema = z.object({10  username: z.string().min(1).max(100),11  password: z.string().min(1).max(200),12});1314export async function POST(req: Request) {15  try {16    await assertSameOrigin();17    const { username, password } = await parseBody(req, schema);18    const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "local";1920    const rl = rateLimit(`login:${ip}:${username.toLowerCase()}`, 8, 15 * 60 * 1000);21    if (!rl.ok) {22      logAuthEvent("login-rate-limited", { username, ip });23      return NextResponse.json(24        { error: `Trop de tentatives. Réessayez dans ${Math.ceil(rl.retryAfterS / 60)} min.` },25        { status: 429 }26      );27    }2829    const user = get<{ id: number; password_hash: string; disabled: number; must_change_password: number; role: string }>(30      "SELECT id, password_hash, disabled, must_change_password, role FROM users WHERE username = ?",31      username32    );33    if (!user || !verifyPassword(password, user.password_hash)) {34      logAuthEvent("login-failed", { username, ip });35      return NextResponse.json({ error: "Identifiant ou mot de passe incorrect." }, { status: 401 });36    }37    if (user.disabled) {38      logAuthEvent("login-disabled", { userId: user.id, username, ip });39      return NextResponse.json({ error: "Ce compte est désactivé. Contactez le professeur." }, { status: 403 });40    }4142    const { token, expiresAt } = createSession(user.id, ip, req.headers.get("user-agent") ?? undefined);43    run("UPDATE users SET last_login_at = datetime('now') WHERE id = ?", user.id);44    logAuthEvent("login-success", { userId: user.id, username, ip });4546    const res = NextResponse.json({47      ok: true,48      mustChangePassword: !!user.must_change_password,49      role: user.role,50    });51    res.cookies.set(SESSION_COOKIE, token, {52      httpOnly: true,53      sameSite: "lax",54      secure: process.env.NODE_ENV === "production" && (process.env.APP_URL ?? "").startsWith("https"),55      expires: expiresAt,56      path: "/",57    });58    return res;59  } catch (e) {60    return apiError(e);61  }62}63