import { createServerFn } from "@tanstack/react-start"; import { enforceRateLimit, requestIp } from "~/server/limits"; import { z } from "zod"; import { viewerMiddleware } from "./middleware"; import { createSession, destroySession, loadUserByLogin } from "~/server/auth"; import { verifyPassword } from "~/server/crypto"; import { first } from "~/server/db"; import { createUser, githubOAuthEnabled, signupsEnabled } from "~/server/accounts"; import { badRequest } from "~/server/errors"; import { sendEmailVerification } from "~/server/account-security"; import { requiresSecondFactor, startPendingLogin } from "~/server/two-factor"; import { accountName, email, password } from "~/lib/validation"; export const getSession = createServerFn({ method: "GET" }) .middleware([viewerMiddleware]) .handler(async ({ context }) => { const unread = context.viewer ? (( await first<{ count: number }>( "SELECT COUNT(*) AS count FROM notifications WHERE user_id = ? AND read_at IS NULL", context.viewer.id, ) )?.count ?? 0) : 0; return { viewer: context.viewer, unreadNotifications: unread }; }); export const getAuthOptions = createServerFn({ method: "GET" }).handler(async () => { return { signupsEnabled: signupsEnabled(), githubEnabled: githubOAuthEnabled() }; }); export const signUp = createServerFn({ method: "POST" }) .validator(z.object({ username: accountName, email, password })) .handler(async ({ data }) => { await enforceRateLimit("AUTH_LIMITER", `signup:${requestIp()}`); const id = await createUser({ name: data.username, email: data.email, password: data.password, githubId: null, displayName: null, }); await createSession(id); await sendEmailVerification(id, data.email); return { ok: true }; }); export const signIn = createServerFn({ method: "POST" }) .validator(z.object({ login: z.string().trim().min(1), password: z.string().min(1) })) .handler(async ({ data }) => { await enforceRateLimit("AUTH_LIMITER", `login:${requestIp()}`); await enforceRateLimit("AUTH_LIMITER", `login:${data.login.toLowerCase()}:${requestIp()}`); const user = await loadUserByLogin(data.login); if (!user?.password_hash || !(await verifyPassword(data.password, user.password_hash))) { throw badRequest("Incorrect username or password"); } if (await requiresSecondFactor(user.id)) { await startPendingLogin(user.id); return { ok: true, twoFactor: true }; } await createSession(user.id); return { ok: true, twoFactor: false }; }); export const signOut = createServerFn({ method: "POST" }).handler(async () => { await destroySession(); return { ok: true }; });