Reduce Supabase disk IO
This commit is contained in:
@@ -107,6 +107,75 @@ export function runTests() {
|
||||
!middlewareSource.includes("return NextResponse.next();\n }\n }\n\n const response = NextResponse.next"),
|
||||
"middleware must not treat the mere presence of a bearer token as authenticated",
|
||||
);
|
||||
assert(
|
||||
middlewareSource.includes("function hasSupabaseSessionCookie") &&
|
||||
middlewareSource.includes("request.cookies.getAll()") &&
|
||||
middlewareSource.includes("hasSupabaseSessionCookieValues"),
|
||||
"middleware must detect non-empty Supabase session cookies locally via the shared helper before refreshing auth",
|
||||
);
|
||||
assert(
|
||||
middlewareSource.includes("redirectToLogin(request, pathname)") &&
|
||||
middlewareSource.indexOf("if (!hasSupabaseSessionCookie(request))") <
|
||||
middlewareSource.indexOf("await refreshMiddlewareSession(request)"),
|
||||
"middleware must redirect no-cookie page requests without calling Supabase auth",
|
||||
);
|
||||
assert(
|
||||
middlewareSource.includes("unauthorizedSupabaseSessionResponse()"),
|
||||
"middleware must reject no-cookie protected API requests without calling Supabase auth",
|
||||
);
|
||||
for (const route of [
|
||||
"app/api/ops/analytics/funnel/route.ts",
|
||||
"app/api/ops/config/route.ts",
|
||||
"app/api/ops/health-check/route.ts",
|
||||
"app/api/ops/leaderboard/weekly/route.ts",
|
||||
"app/api/ops/memberships/route.ts",
|
||||
"app/api/ops/memberships/growth/route.ts",
|
||||
"app/api/ops/memberships/overview/route.ts",
|
||||
"app/api/ops/online-users/route.ts",
|
||||
"app/api/ops/payments/incidents/route.ts",
|
||||
"app/api/ops/payments/incidents/[eventId]/resolve/route.ts",
|
||||
"app/api/ops/subscriptions/extend/route.ts",
|
||||
"app/api/ops/subscriptions/grant/route.ts",
|
||||
"app/api/ops/telegram/members-audit/route.ts",
|
||||
"app/api/ops/training/accuracy/route.ts",
|
||||
"app/api/ops/truth-history/route.ts",
|
||||
"app/api/ops/users/route.ts",
|
||||
"app/api/ops/users/grant-points/route.ts",
|
||||
"app/api/ops/view-logs/route.ts",
|
||||
]) {
|
||||
const routeSource = fs.readFileSync(path.join(projectRoot, route), "utf8");
|
||||
assert(
|
||||
routeSource.includes("requireOpsProxyAuth(req, auth)") &&
|
||||
routeSource.indexOf("requireOpsProxyAuth(req, auth)") >
|
||||
routeSource.indexOf("buildBackendRequestHeaders(req"),
|
||||
`${route} must reject requests without Supabase identity before forwarding the backend entitlement token`,
|
||||
);
|
||||
}
|
||||
assert(
|
||||
middlewareSource.includes('pathname === "/api/payments/config"'),
|
||||
"middleware must treat public payment config as public API so cached config requests do not refresh Supabase sessions",
|
||||
);
|
||||
const optionalRefreshFunction = middlewareSource.slice(
|
||||
middlewareSource.indexOf("function shouldRefreshOptionalSupabaseSession"),
|
||||
middlewareSource.indexOf("function hasSupabaseSessionCookie"),
|
||||
);
|
||||
assert(
|
||||
!optionalRefreshFunction.includes('pathname.startsWith("/api/ops/")') &&
|
||||
!optionalRefreshFunction.includes('pathname.startsWith("/api/payments/")'),
|
||||
"optional Supabase middleware refresh must not pre-read sessions for API routes that already build backend auth headers",
|
||||
);
|
||||
const optionalRefreshIndex = middlewareSource.indexOf(
|
||||
"function shouldRefreshOptionalSupabaseSession",
|
||||
);
|
||||
const systemStatusPublicIndex = middlewareSource.indexOf(
|
||||
'pathname === "/api/system/status"',
|
||||
);
|
||||
assert(
|
||||
systemStatusPublicIndex >= 0 &&
|
||||
optionalRefreshIndex >= 0 &&
|
||||
systemStatusPublicIndex < optionalRefreshIndex,
|
||||
"middleware must treat public system status as public API instead of optional Supabase session refresh",
|
||||
);
|
||||
|
||||
for (const route of paymentRoutes) {
|
||||
const routeSource = fs.readFileSync(path.join(projectRoot, route), "utf8");
|
||||
|
||||
@@ -92,6 +92,26 @@ export function runTests() {
|
||||
path.join(projectRoot, "app", "api", "auth", "me", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const paymentConfigRouteSource = fs.readFileSync(
|
||||
path.join(projectRoot, "app", "api", "payments", "config", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const paymentRuntimeRouteSource = fs.readFileSync(
|
||||
path.join(projectRoot, "app", "api", "payments", "runtime", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const backendAuthSource = fs.readFileSync(
|
||||
path.join(projectRoot, "lib", "backend-auth.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const opsAdminSource = fs.readFileSync(
|
||||
path.join(projectRoot, "lib", "ops-admin.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const supabaseServerSource = fs.readFileSync(
|
||||
path.join(projectRoot, "lib", "supabase", "server.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const subscriptionsPageSource = fs.readFileSync(
|
||||
path.join(
|
||||
projectRoot,
|
||||
@@ -102,6 +122,26 @@ export function runTests() {
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
const membershipsPageSource = fs.readFileSync(
|
||||
path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"ops",
|
||||
"memberships",
|
||||
"MembershipsPageClient.tsx",
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
const opsOverviewSource = fs.readFileSync(
|
||||
path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"ops",
|
||||
"overview",
|
||||
"OverviewPageClient.tsx",
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert(
|
||||
accountCenterSource.includes(
|
||||
@@ -157,6 +197,11 @@ export function runTests() {
|
||||
hookSource.includes("retriedBackendJson"),
|
||||
"account snapshot loader must retry with a refreshed Supabase token when local user exists but /api/auth/me reports unauthenticated",
|
||||
);
|
||||
assert(
|
||||
!hookSource.includes(".auth.getUser()") &&
|
||||
hookSource.includes(".auth.getSession()"),
|
||||
"account snapshot loader must use the local Supabase session instead of calling getUser before /api/auth/me validates the bearer",
|
||||
);
|
||||
assert(
|
||||
subscriptionsPageSource.includes("getSupabaseBrowserClient") &&
|
||||
subscriptionsPageSource.includes("refreshSession") &&
|
||||
@@ -165,11 +210,28 @@ export function runTests() {
|
||||
subscriptionsPageSource.includes("/api/ops/subscriptions/extend"),
|
||||
"ops manual subscription grant/extend must send the Supabase bearer token to avoid 401 when route cookies are stale",
|
||||
);
|
||||
assert(
|
||||
membershipsPageSource.includes("opsApi.membershipsOverview") &&
|
||||
!membershipsPageSource.includes('fetch("/api/ops/memberships/growth?days=90"') &&
|
||||
!membershipsPageSource.includes("Promise.all"),
|
||||
"ops memberships page must load memberships and growth through one overview proxy request to avoid duplicate Supabase session/subscription reads",
|
||||
);
|
||||
assert(
|
||||
opsOverviewSource.includes("opsApi.membershipsOverview(200, 30)") &&
|
||||
!opsOverviewSource.includes("opsApi.memberships()") &&
|
||||
!opsOverviewSource.includes("opsApi.membershipsGrowth(30)"),
|
||||
"ops overview page must reuse membershipsOverview for table and growth data instead of issuing separate membership/growth proxy requests",
|
||||
);
|
||||
assert(
|
||||
grantRouteSource.includes("grantSubscriptionDirectly") &&
|
||||
grantRouteSource.includes("res.status === 404") &&
|
||||
grantRouteSource.includes('"status": "active"'),
|
||||
"ops subscription grant route must fall back to direct Supabase grant when the VPS backend route is missing",
|
||||
grantRouteSource.includes('"status": "active"') &&
|
||||
grantRouteSource.includes("/rest/v1/profiles") &&
|
||||
grantRouteSource.includes("select=id&email=eq.") &&
|
||||
grantRouteSource.indexOf("/rest/v1/profiles") <
|
||||
grantRouteSource.indexOf("/auth/v1/admin/users") &&
|
||||
grantRouteSource.includes('Prefer: "return=minimal"'),
|
||||
"ops subscription grant route must fall back to direct Supabase grant and resolve users via indexed profiles before Auth Admin",
|
||||
);
|
||||
assert(
|
||||
authMeRouteSource.includes("if ((res.status === 401 || res.status === 403) && auth.authUserId)") &&
|
||||
@@ -177,4 +239,76 @@ export function runTests() {
|
||||
authMeRouteSource.includes("subscription_active: null"),
|
||||
"auth profile proxy must preserve authenticated identity with unknown subscription on backend 401/403 instead of forcing a false paywall",
|
||||
);
|
||||
assert(
|
||||
(authMeRouteSource.match(/buildBackendRequestHeaders\(req\)/g) || []).length === 1 &&
|
||||
authMeRouteSource.includes("let auth: Awaited<ReturnType<typeof buildBackendRequestHeaders>> | null = null"),
|
||||
"auth profile proxy must build backend auth headers once and reuse them on timeout/error fallback",
|
||||
);
|
||||
assert(
|
||||
authMeRouteSource.includes("hasSupabaseServerEnv()") &&
|
||||
authMeRouteSource.includes("!auth.authUserId") &&
|
||||
authMeRouteSource.includes('req.headers.get("authorization")') &&
|
||||
authMeRouteSource.indexOf("authenticated: false") <
|
||||
authMeRouteSource.indexOf("await fetch(`${API_BASE}/api/auth/me`"),
|
||||
"auth profile proxy must return unauthenticated locally for no-session Supabase requests instead of forwarding the backend entitlement token",
|
||||
);
|
||||
assert(
|
||||
backendAuthSource.includes("if (incomingAuth) {") &&
|
||||
backendAuthSource.indexOf("if (incomingAuth) {") <
|
||||
backendAuthSource.indexOf("const supabase = createSupabaseRouteClient"),
|
||||
"backend proxy must forward caller bearer tokens before creating a Supabase route client to avoid duplicate getUser calls",
|
||||
);
|
||||
assert(
|
||||
backendAuthSource.includes("headers.set(FORWARDED_SUPABASE_USER_ID_HEADER") &&
|
||||
backendAuthSource.includes("headers.set(FORWARDED_SUPABASE_EMAIL_HEADER") &&
|
||||
backendAuthSource.indexOf("headers.set(FORWARDED_SUPABASE_USER_ID_HEADER") >
|
||||
backendAuthSource.indexOf("const sessionUser = session?.user"),
|
||||
"backend proxy must forward Supabase session user id/email with the backend token so Python can skip duplicate /auth/v1/user validation",
|
||||
);
|
||||
assert(
|
||||
backendAuthSource.includes("function hasSupabaseSessionCookie") &&
|
||||
backendAuthSource.includes("String(cookie.value || \"\").trim()") &&
|
||||
backendAuthSource.includes("if (!hasSupabaseSessionCookie(request))") &&
|
||||
backendAuthSource.indexOf("if (!hasSupabaseSessionCookie(request))") <
|
||||
backendAuthSource.indexOf("const supabase = createSupabaseRouteClient"),
|
||||
"backend proxy must skip Supabase route client/session reads when no auth cookie is present",
|
||||
);
|
||||
assert(
|
||||
!backendAuthSource.includes(".auth.getUser()") &&
|
||||
backendAuthSource.includes(".auth.getSession()"),
|
||||
"backend proxy must not call Supabase getUser before forwarding a bearer token that the backend validates again",
|
||||
);
|
||||
assert(
|
||||
supabaseServerSource.includes("export function hasSupabaseSessionCookieValues") &&
|
||||
supabaseServerSource.includes('name === "supabase-auth-token"') &&
|
||||
supabaseServerSource.includes('name.startsWith("sb-")') &&
|
||||
supabaseServerSource.indexOf("if (!hasSupabaseSessionCookieValues") <
|
||||
supabaseServerSource.indexOf("const supabase = createSupabaseServerClient"),
|
||||
"Supabase server helpers must expose one cookie detector and skip refresh getUser calls when no session cookie exists",
|
||||
);
|
||||
assert(
|
||||
supabaseServerSource.includes(".auth.getClaims()") &&
|
||||
!supabaseServerSource.includes(".auth.getUser()"),
|
||||
"Supabase middleware refresh must validate JWTs with getClaims so asymmetric JWT projects avoid per-request /auth/v1/user reads",
|
||||
);
|
||||
assert(
|
||||
opsAdminSource.includes("hasSupabaseSessionCookieValues") &&
|
||||
opsAdminSource.indexOf("if (!hasSupabaseSessionCookieValues") <
|
||||
opsAdminSource.indexOf("const supabase = createSupabaseServerClient"),
|
||||
"ops admin page gate must redirect before creating a Supabase client/getUser call when no session cookie exists",
|
||||
);
|
||||
assert(
|
||||
opsAdminSource.includes(".auth.getClaims()") &&
|
||||
!opsAdminSource.includes(".auth.getUser()") &&
|
||||
opsAdminSource.includes("claims?.email"),
|
||||
"ops admin page gate must use verified JWT claims instead of a per-page getUser auth lookup",
|
||||
);
|
||||
assert(
|
||||
paymentConfigRouteSource.includes("includeSupabaseIdentity: false"),
|
||||
"payment config proxy must not read Supabase session cookies for public cached config",
|
||||
);
|
||||
assert(
|
||||
paymentRuntimeRouteSource.includes("includeSupabaseIdentity: false"),
|
||||
"payment runtime proxy must not read Supabase session cookies because backend entitlement token already protects the runtime status payload",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -151,14 +151,17 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
|
||||
setErrorText("");
|
||||
const attempt = async (retry: boolean): Promise<void> => {
|
||||
const userPromise = supabaseReady
|
||||
? getSupabaseBrowserClient().auth.getUser()
|
||||
: Promise.resolve({ data: { user: null as User | null } });
|
||||
? getSupabaseBrowserClient()
|
||||
.auth.getSession()
|
||||
.then(({ data: { session } }) => session?.user ?? null)
|
||||
.catch(() => null as User | null)
|
||||
: Promise.resolve(null as User | null);
|
||||
const authHeadersPromise = buildAuthedHeaders(false);
|
||||
const backendPromise = authHeadersPromise.then((headers) =>
|
||||
fetch("/api/auth/me", { cache: "no-store", headers }),
|
||||
);
|
||||
const [userResult, backendResult] = await Promise.all([userPromise, backendPromise]);
|
||||
setUser(userResult.data?.user ?? null);
|
||||
const [localUser, backendResult] = await Promise.all([userPromise, backendPromise]);
|
||||
setUser(localUser);
|
||||
if (!backendResult.ok) {
|
||||
if (retry && backendResult.status === 401) {
|
||||
await new Promise((r) => setTimeout(r, 1200));
|
||||
@@ -171,7 +174,7 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
|
||||
if (
|
||||
retry &&
|
||||
supabaseReady &&
|
||||
userResult.data?.user &&
|
||||
localUser &&
|
||||
backendJson.authenticated === false
|
||||
) {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
buildCityDetailProxyCachePolicy,
|
||||
buildForceRefreshProxyCachePolicy,
|
||||
@@ -22,4 +24,32 @@ export function runTests() {
|
||||
|
||||
const scanForced = buildForceRefreshProxyCachePolicy("true", 10);
|
||||
assert.equal(scanForced.fetchMode, "no-store");
|
||||
|
||||
const overviewProxySource = fs.readFileSync(
|
||||
path.join(
|
||||
process.cwd(),
|
||||
"app",
|
||||
"api",
|
||||
"scan",
|
||||
"terminal",
|
||||
"overview",
|
||||
"route.ts",
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(
|
||||
overviewProxySource,
|
||||
/buildBackendRequestHeaders\(req,\s*\{\s*includeSupabaseIdentity:\s*false,\s*\}\)/s,
|
||||
"scan terminal overview proxy must not read Supabase sessions for public overview payloads",
|
||||
);
|
||||
|
||||
const priorityWarmProxySource = fs.readFileSync(
|
||||
path.join(process.cwd(), "lib", "system-priority-proxy.ts"),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(
|
||||
priorityWarmProxySource,
|
||||
/buildBackendRequestHeaders\(req,\s*\{\s*includeSupabaseIdentity:\s*false,\s*\}\)/s,
|
||||
"priority warm proxy must not read Supabase sessions for backend-token warm hints",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,12 +41,9 @@ export function MembershipsPageClient() {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [mData, gData] = await Promise.all([
|
||||
opsApi.memberships(),
|
||||
fetch("/api/ops/memberships/growth?days=90", { cache: "no-store" }).then(r => r.ok ? r.json() : null),
|
||||
]);
|
||||
setMemberships((mData as unknown as { memberships?: MembershipEntry[] }).memberships ?? []);
|
||||
setGrowth((gData as { daily?: GrowthPoint[] })?.daily ?? []);
|
||||
const data = await opsApi.membershipsOverview(200, 90);
|
||||
setMemberships((data as unknown as { memberships?: MembershipEntry[] }).memberships ?? []);
|
||||
setGrowth((data as { daily?: GrowthPoint[] })?.daily ?? []);
|
||||
} catch { /* */ }
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -44,16 +44,15 @@ export function OverviewPageClient() {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [s, m, f, g] = await Promise.all([
|
||||
const [s, m, f] = await Promise.all([
|
||||
opsApi.systemStatus() as Promise<SystemStatusPayload>,
|
||||
opsApi.memberships() as Promise<MembershipsPayload>,
|
||||
opsApi.membershipsOverview(200, 30) as Promise<MembershipsPayload & { daily?: { date: string; trial: number; paid: number; total: number; cumulative: number }[] }>,
|
||||
opsApi.funnel(30),
|
||||
opsApi.membershipsGrowth(30),
|
||||
]);
|
||||
setStatus(s);
|
||||
setMemberships((m as MembershipsPayload).memberships ?? []);
|
||||
setFunnel(f);
|
||||
setGrowth(g?.daily ?? []);
|
||||
setGrowth(m?.daily ?? []);
|
||||
} catch { /* */ }
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user