flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/apps/sudo

Chase PierceStripe webhooks, enterprise invoices, and sudo for both77f9c6ea1864aa138361b47a3dc2ba6746df885277f9c6e

README.md

sudo

g1t's staff console, at https://sudo.g1t.sh. It is organised the way customers know g1t: by workspace.

  • Workspaces (the home page): every workspace, newest first, 50 to a page, with its owners, members, who it is billed to, its terms, this month's usage against its limit, what it was charged and what it cost g1t. Search by workspace, owner, email or enterprise (across the whole list); filter to stopped or warning, comped or custom, or on an enterprise. Billing's figures are fetched for exactly the page shown, so the filters, and the totals over the list, cover that page; the page says so when there is more than one. A workspace's page shows its members, and under Billing its terms, who it is billed to (move it onto or off an enterprise), a credit form, a Stripe billing link, its ledger and its audit log.
  • Enterprises: customers that pay for several workspaces with one bill, one limit and one set of terms. Each has its workspaces (add or remove them), combined usage, terms, credits, ledger and audit log, and Invoices: where they go (the billing email, which also makes its Stripe customer), a "Send invoice now" button, and every invoice with its status (open, paid, overdue, void), a line per workspace, and a link to Stripe's hosted invoice page. An invoice also goes out on its own as each month closes: one Stripe invoice, a line per workspace for what it owes, net 30, emailed by Stripe.
  • Stripe: whether billing's key is in test or live mode (or off), the webhook Stripe calls (URL, endpoint id, events, who registered it and when), and the events Stripe sent lately with what billing did with each. "Register webhook" (or "Replace") has billing delete the endpoint it made before, create a new one and keep its signing secret, which no one sees. Do it once per mode, and again after switching to live keys.

Billing's internal account ids (ws_<slug> for a workspace's own, ent_… for an enterprise) are never shown as names; an enterprise's id appears only as small "Billing account id" text. Old /accounts/… links redirect to the workspace or enterprise they meant.

Cards stay on Stripe. sudo never shows a card field. To help a customer update their card or see invoices, staff make a Stripe billing link on the workspace's page (it is recorded) and send it to the owner.

It holds no data. Workspaces, owners and members come from identity's staff methods (admin_workspaces, admin_workspace; IdentityAdminApi in packages/contracts/src/identity.ts); everything about money goes to the billing service's (admin_*, BillingAdminApi in packages/contracts/src/billing.ts), where each change is recorded with the staff member's email. Both are reached over service bindings only, and nothing but sudo binds to them.

How it is locked

  1. Cloudflare Access sits in front of sudo.g1t.sh and signs people in.
  2. The worker checks Access's work on every request, the stylesheet included (run_worker_first): it verifies the Cf-Access-Jwt-Assertion JWT itself (RS256 against the team's published keys, audience, issuer, expiry), then requires its email to be in STAFF_EMAILS. That email is who every change is recorded as. See app/lib/access.ts.
  3. It fails closed. Until ACCESS_TEAM_DOMAIN, ACCESS_AUD and STAFF_EMAILS are all set, every request gets a 403 saying sudo is not configured.
  4. Changes are POSTs only, and only from sudo's own pages (Origin, or Referer, must be https://sudo.g1t.sh). Terms, enterprise moves, new enterprises, Stripe billing links, invoice emails, invoices and the webhook show a confirmation step first; a credit needs the workspace's slug typed out.
  5. The pages ship no JavaScript. The content security policy forbids every script and inline style; responses are no-store, noindex and cannot be framed. The worker has no workers.dev address or preview URLs.

Setting up Access (once, in the Cloudflare dashboard)

  1. Zero Trust → Access → Applications → Add an application → Self-hosted.

    • Application name: sudo.
    • Session duration: short, such as 8 hours.
    • Public hostname: sudo.g1t.sh (path empty, so it covers everything).
  2. Add a policy (g1t staff): action Allow, include Emails → the owner's address, and Emails ending in → g1t.sh for everyone with a g1t address (the same entries as STAFF_EMAILS, where a domain is written @g1t.sh). Add more staff here and in STAFF_EMAILS; either one alone is not enough.

  3. Save, then open the application's Overview (or Basic information) and copy the Application Audience (AUD) tag.

  4. Find the team domain under Zero Trust → Settings → Custom pages (or Team name and domain): it looks like <team>.cloudflareaccess.com.

  5. Put both into wrangler.jsonc:

    "vars": {
      "ACCESS_TEAM_DOMAIN": "<team>.cloudflareaccess.com",
      "ACCESS_AUD": "<the AUD tag>",
      "STAFF_EMAILS": "syntaqx@gmail.com, @g1t.sh"
    }
    jsonc
  6. Deploy: scripts/deploy.sh sudo (after billing and identity, whose admin_* methods it calls).

Visit https://sudo.g1t.sh: Access asks you to sign in, then the workspaces list opens. Anyone else gets Access's own refusal; anyone Access lets in who is not in STAFF_EMAILS gets a 403 from the worker.

Signing in through WARP

Staff signed in to the Zero Trust org in the Cloudflare One agent (WARP) reach sudo without the login page: the org allows WARP sessions as Access sign-ins (8 hours), the sudo app accepts them, and the g1t staff policy is also on the WARP enrollment app, so staff can enroll their devices. The same two checks still apply: the Access policy, and STAFF_EMAILS.

Working on it

npm run typecheck -w @g1t/sudo
npm test -w @g1t/sudo     # JWT verification, forms, money, the workspace join, paging
npm run build -w @g1t/sudo
sh

npm run dev serves the pages, but every request is refused without a real Access token, by design.