826f554ae1
Three coordinated changes that improve CI/build ergonomics and dev-mode performance. None change database schema; all are reversible by env var. src/lib/auth.ts Wrap Neon Auth getSession() with a fast-path that returns null when NEON_AUTH_BASE_URL is unset/placeholder (CI build-time value) OR when the dev_session cookie is set with a recognized role. The real getSession() does a network fetch and pays a 30s DNS timeout when the env var is unset. Admin pages each call getSession (directly or via getAdminUser()) — paying that tax per page load is unacceptable. src/lib/db.ts + db/client.ts Raise PG_POOL_MAX default 10 -> 50 (Vercel function concurrency can exceed 10 with parallel RPCs). Lower PG_POOL_CONN_TIMEOUT_MS default 30s -> 5s in src/lib/db.ts and 10s -> 5s in db/client.ts so a bad Neon branch fails fast instead of hanging requests. src/lib/admin-permissions.ts + src/proxy.ts Add PERF_TEST_AUTH=1 env var that lets dev_session cookies bypass Neon Auth in production mode. Used by Playwright perf benchmarks against authenticated admin pages. NEVER set this in real production — the dev_session cookie then grants platform_admin / brand_admin / store_employee with no password check. The middleware guard in proxy.ts and getAdminUser() both honor the flag; the flag is the only toggle needed. package.json Add react-doctor ^0.5.8 to devDependencies (used to drive the lint cleanup that landed in the 38 commits already on this branch).
97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
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<boolean> {
|
|
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<Awaited<ReturnType<typeof _realGetSession>>> {
|
|
if (!isAuthConfigured()) {
|
|
if (process.env.PERF_TEST_AUTH === "1") console.log("[auth] getSession short-circuit: not configured");
|
|
return { data: null, error: null } as Awaited<ReturnType<typeof _realGetSession>>;
|
|
}
|
|
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<ReturnType<typeof _realGetSession>>;
|
|
}
|
|
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 };
|