Reduce Supabase disk IO

This commit is contained in:
2569718930@qq.com
2026-05-29 17:22:33 +08:00
parent 9e3a4e2f45
commit 2269aaefd7
63 changed files with 4637 additions and 410 deletions
@@ -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);
};