SPB Git
2.1 KB · 48 lines typescript
1import "server-only";2import { headers } from "next/headers";3import { redirect } from "next/navigation";4import { getSession, type SessionUser } from "./session";5import { db, schema } from "@/db";6import { eq } from "drizzle-orm";7import { cache } from "react";89export class HttpError extends Error { constructor(public status: number, message: string) { super(message); } }1011/** For route handlers: 401 if no session; 403 if MFA pending. Also enforces same-origin on mutating requests. */12export async function requireUser(req?: Request): Promise<SessionUser> {13  const s = await getSession();14  if (!s) throw new HttpError(401, "Unauthenticated");15  if (s.totpEnabled && !s.mfaPassed) throw new HttpError(403, "MFA required");16  if (req && !["GET", "HEAD", "OPTIONS"].includes(req.method)) await assertSameOrigin();17  return s;18}19/** For server components/pages: redirect to login. */20export async function requireUserPage(): Promise<SessionUser> {21  const s = await getSession();22  if (!s) redirect("/login");23  if (s.totpEnabled && !s.mfaPassed) redirect("/login?step=totp");24  return s;25}26export async function assertSameOrigin() {27  const h = await headers();28  const sfs = h.get("sec-fetch-site");29  if (sfs && sfs !== "same-origin" && sfs !== "none") throw new HttpError(403, "Cross-site request blocked");30  const origin = h.get("origin");31  if (origin) {32    const host = h.get("x-forwarded-host") ?? h.get("host");33    const allowed = new Set([process.env.APP_URL?.replace(/\/$/, ""), `https://${host}`, `http://${host}`].filter(Boolean));34    if (!allowed.has(origin)) throw new HttpError(403, "Origin mismatch");35  }36}37/** The single owner workspace (created at bootstrap). */38export const getWorkspace = cache(async (userId: string) => {39  const [ws] = await db.select().from(schema.workspaces).where(eq(schema.workspaces.ownerId, userId)).limit(1);40  if (!ws) throw new HttpError(500, "Workspace missing — run pnpm bootstrap");41  return ws;42});43export function jsonError(e: unknown) {44  if (e instanceof HttpError) return Response.json({ error: e.message }, { status: e.status });45  console.error(e);46  return Response.json({ error: "Internal error" }, { status: 500 });47}48
Files · main
Menu

Theme