import "server-only"; /** * Neon Auth (Better Auth) — server-side configuration. * * This file is Node-only. It is imported by: * - `src/app/api/auth/[...nextauth]/route.ts` (the API handler) * - Server actions that call signIn / signOut * - `src/lib/admin-permissions.ts` (reads getSession() for the current user) * * The middleware imports a separate, edge-safe instance built from * `src/auth.config.ts`. Both instances share the same session cookie, * so the middleware can read sessions minted here. * * Auth providers: configured in the Neon Auth dashboard (oauth-provider). * Neon Auth manages its own users in the `neon_auth.user` table. * Our `admin_users` table links to Neon Auth users by email. */ import { createNeonAuth } from "@neondatabase/auth/next/server"; import { getNeonAuthConfig } from "@/auth.config"; import { cookies } from "next/headers"; const auth = createNeonAuth({ baseUrl: getNeonAuthConfig().baseUrl, cookies: { secret: getNeonAuthConfig().cookieSecret }, }); const { getSession: _realGetSession, signIn, signOut, resetPassword, requestPasswordReset } = auth; const { listUsers, createUser, setRole, setUserPassword, updateUser } = auth.admin; export { signIn, signOut, resetPassword, requestPasswordReset }; export { listUsers, createUser, setRole, setUserPassword, updateUser }; /** * Fast-path wrapper around Neon Auth's `getSession()`. * * Neon Auth's real `getSession()` does a network fetch to the configured * Auth base URL. When the env var is unset (or set to the CI build-time * `placeholder.local`), every call hits a 30s DNS timeout before * returning `null`. Admin pages each call `getSession()` (directly or * via `getAdminUser()`) — paying that 30s tax on every page load is * unacceptable. * * Short-circuits when: * 1. `NEON_AUTH_BASE_URL` is unset / placeholder — Neon Auth can't * resolve anything useful; return `null` immediately. * 2. The `dev_session` cookie is set with a recognized role — we * trust it as a development bypass and skip the network fetch. * * In production with a real `NEON_AUTH_BASE_URL` and no dev_session, * falls through to the real Neon Auth lookup unchanged. */ function isAuthConfigured(): boolean { const url = process.env.NEON_AUTH_BASE_URL; if (!url || url === "placeholder" || url === "https://placeholder.local") return false; return true; } async function isDevSession(): Promise { if (process.env.NODE_ENV === "production" && process.env.PERF_TEST_AUTH !== "1") return false; try { const store = await cookies(); const v = store.get("dev_session")?.value; return v === "platform_admin" || v === "brand_admin" || v === "store_employee"; } catch { return false; } } export async function getSession(): Promise>> { if (!isAuthConfigured()) { if (process.env.PERF_TEST_AUTH === "1") console.log("[auth] getSession short-circuit: not configured"); return { data: null, error: null } as Awaited>; } if (await isDevSession()) { if (process.env.PERF_TEST_AUTH === "1") console.log("[auth] getSession short-circuit: dev_session"); return { data: null, error: null } as Awaited>; } if (process.env.PERF_TEST_AUTH === "1") console.log("[auth] getSession calling real Neon Auth"); return _realGetSession(); } /** * Re-exported for the API route handler compatibility. * `GET` and `POST` from `auth.handler()` handle all Neon Auth endpoints. */ const authInstance = auth.handler(); export const { GET: handlersGET, POST: handlersPOST } = authInstance; /** * Re-exported for middleware compatibility (edge runtime can't use Node-only auth). * The middleware uses `neonAuthMiddleware` from `@neondatabase/auth/next/server` directly. * This named export exists for call sites that import `auth` from this module. */ export { auth };