CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix(billing): quota exemption delegates to the canonical isSiteAdmin #5478

Merged⚡ AI-generatedXSccantynz wants to mergefix/quota-exemption-canonical-adminmainopened 22d ago
2 changed files+19−15
Modifiedsrc/__tests__/billing.test.ts+7−2View fileUnifiedSplit
157157 expect(exempt).toBeGreaterThan(-1);
158158 expect(plan).toBeGreaterThan(exempt);
159159 }
160 // Fail-closed the right way round: a DB error meters, never unmeters.
160 // The exemption must delegate to the CANONICAL admin check
161 // (lib/admin.ts isSiteAdmin — site_admins table + bootstrap), never a
162 // parallel read of users.is_admin: the first version did exactly
163 // that, and the owner hit the same 402 again with the "fix" live.
164 expect(src).toContain('import { isSiteAdmin } from "./admin"');
161165 const helper = src.slice(src.indexOf("async function isExemptSiteAdmin"));
162 expect(helper.slice(0, helper.indexOf("}\n}"))).toContain("return false;");
166 expect(helper.slice(0, 300)).toContain("return isSiteAdmin(userId)");
167 expect(helper.slice(0, 300)).not.toContain("users.isAdmin");
163168 });
164169});
Modifiedsrc/lib/billing.ts+12−13View fileUnifiedSplit
2424 billingPlans,
2525 userQuotas,
2626 repositories,
27 users,
2827 type BillingPlan,
2928 type UserQuota,
3029} from "../db/schema";
30import { isSiteAdmin } from "./admin";
3131
3232/**
3333 * Site admins are never metered by their own platform's billing.
3636 * 10 repos. Upgrade for more." while creating a repo on the platform they
3737 * operate. The quota gates only ever consulted the plan row, and the plan
3838 * system has no concept of "this account IS the platform" — checked here,
39 * at the chokepoint, so every quota gate below inherits it. Fail-closed
40 * to "not admin" so a DB hiccup meters rather than unmeters.
39 * at the chokepoint, so every quota gate below inherits it.
40 *
41 * Delegates to the CANONICAL isSiteAdmin (lib/admin.ts: site_admins table
42 * + oldest-user bootstrap). The first version of this exemption read
43 * users.is_admin instead — a second representation of adminness that the
44 * owner's account does not carry — and the owner hit the same 402 again
45 * with the "fix" deployed. Two sources of truth, the platform's oldest
46 * bug class; never a parallel copy of an authorization decision.
47 * isSiteAdmin fails closed (not-admin) on DB errors: meters, never
48 * unmeters.
4149 */
4250async function isExemptSiteAdmin(userId: string): Promise<boolean> {
4351 if (!userId) return false;
44 try {
45 const [u] = await db
46 .select({ isAdmin: users.isAdmin })
47 .from(users)
48 .where(eq(users.id, userId))
49 .limit(1);
50 return u?.isAdmin === true;
51 } catch {
52 return false;
53 }
52 return isSiteAdmin(userId);
5453}
5554
5655export const DEFAULT_PLAN_SLUG = "free";
5756
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts