From 835c568552b0d97370d053061a42b83c87e9785f Mon Sep 17 00:00:00 2001 From: Oliver Bryan <04oliverbryan@gmail.com> Date: Mon, 22 Dec 2025 03:27:05 +0000 Subject: [PATCH] "/auth/login" route --- packages/backend/src/routes/auth/login.ts | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 packages/backend/src/routes/auth/login.ts diff --git a/packages/backend/src/routes/auth/login.ts b/packages/backend/src/routes/auth/login.ts new file mode 100644 index 0000000..522f48c --- /dev/null +++ b/packages/backend/src/routes/auth/login.ts @@ -0,0 +1,46 @@ +import type { BunRequest } from "bun"; +import { generateToken, verifyPassword } from "../../auth/utils"; +import { getUserByUsername } from "../../db/queries"; + +const isNonEmptyString = (value: unknown): value is string => + typeof value === "string" && value.trim().length > 0; + +export default async function login(req: BunRequest) { + if (req.method !== "POST") { + return new Response("method not allowed", { status: 405 }); + } + + let body: unknown; + try { + body = await req.json(); + } catch { + return new Response("invalid JSON", { status: 400 }); + } + + if (!body || typeof body !== "object") { + return new Response("invalid request body", { status: 400 }); + } + + const { username, password } = body as Record; + + if (!isNonEmptyString(username) || !isNonEmptyString(password)) { + return new Response("username and password are required", { status: 400 }); + } + + const user = await getUserByUsername(username); + if (!user) { + return new Response("invalid credentials", { status: 401 }); + } + + const ok = await verifyPassword(password, user.passwordHash); + if (!ok) { + return new Response("invalid credentials", { status: 401 }); + } + + const token = generateToken(user.id); + + return Response.json({ + user: { id: user.id, name: user.name, username: user.username }, + token, + }); +}