chore(infra): auth fast-path, pg pool tuning, react-doctor devdep
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).
This commit is contained in:
@@ -29,8 +29,10 @@ import type { AdminRole, AdminUser, TenantContext } from "@/lib/admin-permission
|
||||
* shows "Access Denied" — correct behavior.
|
||||
*/
|
||||
export async function getAdminUser(): Promise<AdminUser | null> {
|
||||
// Check for dev_session cookie in development mode
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// Check for dev_session cookie in development mode, or when
|
||||
// PERF_TEST_AUTH=1 (for production-mode perf benchmarks against
|
||||
// authenticated admin pages — never enable in real production).
|
||||
if (process.env.NODE_ENV !== "production" || process.env.PERF_TEST_AUTH === "1") {
|
||||
const cookieStore = await cookies();
|
||||
const devSession = cookieStore.get("dev_session")?.value;
|
||||
if (devSession === "platform_admin" || devSession === "brand_admin" || devSession === "store_employee") {
|
||||
|
||||
+55
-2
@@ -19,14 +19,67 @@ import "server-only";
|
||||
|
||||
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 },
|
||||
});
|
||||
|
||||
export const { getSession, signIn, signOut, resetPassword, requestPasswordReset } = auth;
|
||||
export const { listUsers, createUser, setRole, setUserPassword, updateUser } = auth.admin;
|
||||
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.
|
||||
|
||||
+2
-2
@@ -48,10 +48,10 @@ function buildPool(): Pool {
|
||||
// PG_POOL_MAX (default 10)
|
||||
// PG_POOL_IDLE_MS (default 30s)
|
||||
// PG_POOL_CONN_TIMEOUT_MS (default 10s)
|
||||
max: parseInt(process.env.PG_POOL_MAX ?? "10", 10),
|
||||
max: parseInt(process.env.PG_POOL_MAX ?? "50", 10),
|
||||
idleTimeoutMillis: parseInt(process.env.PG_POOL_IDLE_MS ?? "30000", 10),
|
||||
connectionTimeoutMillis: parseInt(
|
||||
process.env.PG_POOL_CONN_TIMEOUT_MS ?? "30000",
|
||||
process.env.PG_POOL_CONN_TIMEOUT_MS ?? "5000",
|
||||
10,
|
||||
),
|
||||
// Vercel/serverless recycling: keep the pool hot for warm invocations.
|
||||
|
||||
Reference in New Issue
Block a user