SPB Git

spb/chat-spboucher Public

Private universal chat interface over the OpenRouter ecosystem — 400+ models, branching, streaming, usage tracking. Next.js 16 + SQLite, PWA, deployed on m4m64a at chat.spboucher.ai

TypeScript 78.8% CSS 15.1% JavaScript 4.9% Shell 1.2%
1.5 KB · 49 lines typescript
Raw Blame History
1// Author: Simon-Pierre Boucher2// Contact: contact@spboucher.ai3// Project: chat.spboucher.ai45import { NextResponse, type NextRequest } from "next/server";6import {7  verifyCredentials,8  createSession,9  loginRateLimited,10  recordLoginAttempt,11  sessionCookieHeader,12} from "@/lib/auth/auth";13import { clientIp } from "@/lib/auth/guard";1415export const runtime = "nodejs";1617export async function POST(req: NextRequest) {18  const ip = await clientIp();19  if (loginRateLimited(ip)) {20    return NextResponse.json(21      { error: "Too many attempts. Wait 15 minutes and try again." },22      { status: 429 }23    );24  }2526  let body: { username?: string; password?: string };27  try {28    body = await req.json();29  } catch {30    return NextResponse.json({ error: "Invalid request." }, { status: 400 });31  }32  const username = String(body.username ?? "").trim();33  const password = String(body.password ?? "");34  if (!username || !password) {35    return NextResponse.json({ error: "Enter your username and password." }, { status: 400 });36  }3738  const user = await verifyCredentials(username, password);39  recordLoginAttempt(ip, Boolean(user));40  if (!user) {41    return NextResponse.json({ error: "Wrong username or password." }, { status: 401 });42  }4344  const token = createSession(user.id, req.headers.get("user-agent") ?? undefined, ip);45  const res = NextResponse.json({ ok: true, username: user.username });46  res.headers.set("Set-Cookie", sessionCookieHeader(token, 60 * 60 * 24 * 30));47  return res;48}49