pr_01m47d15m3e54sn21z27rpy5n9/apps/sudo/README.md

119 lines6,110 bytesCodeBlame
1# sudo
2
3g1t's staff console, at <https://sudo.g1t.sh>. It is organised the way
4customers know g1t: by **workspace**.
5
6- **Workspaces** (the home page): every workspace, newest first, 50 to a
7 page, with its owners, members, who it is billed to, its terms, this
8 month's usage against its limit, what it was charged and what it cost
9 g1t. Search by workspace, owner, email or enterprise (across the whole
10 list); filter to stopped or warning, comped or custom, or on an
11 enterprise. Billing's figures are fetched for exactly the page shown, so
12 the filters, and the totals over the list, cover that page; the page
13 says so when there is more than one. A workspace's page shows its members, and under
14 **Billing** its terms, who it is billed to (move it onto or off an
15 enterprise), a credit form, a Stripe billing link, its ledger and its
16 audit log.
17- **Enterprises**: customers that pay for several workspaces with one
18 bill, one limit and one set of terms. Each has its workspaces (add or
19 remove them), combined usage, terms, credits, ledger and audit log, and
20 **Invoices**: where they go (the billing email, which also makes its
21 Stripe customer), a "Send invoice now" button, and every invoice with
22 its status (open, paid, overdue, void), a line per workspace, and a link
23 to Stripe's hosted invoice page. An invoice also goes out on its own as
24 each month closes: one Stripe invoice, a line per workspace for what it
25 owes, net 30, emailed by Stripe.
26- **Stripe**: whether billing's key is in test or live mode (or off), the
27 webhook Stripe calls (URL, endpoint id, events, who registered it and
28 when), and the events Stripe sent lately with what billing did with
29 each. "Register webhook" (or "Replace") has billing delete the endpoint
30 it made before, create a new one and keep its signing secret, which no
31 one sees. Do it once per mode, and again after switching to live keys.
32
33Billing's internal account ids (`ws_<slug>` for a workspace's own,
34`ent_…` for an enterprise) are never shown as names; an enterprise's id
35appears only as small "Billing account id" text. Old `/accounts/…` links
36redirect to the workspace or enterprise they meant.
37
38**Cards stay on Stripe.** sudo never shows a card field. To help a customer
39update their card or see invoices, staff make a Stripe billing link on the
40workspace's page (it is recorded) and send it to the owner.
41
42It holds no data. Workspaces, owners and members come from identity's
43staff methods (`admin_workspaces`, `admin_workspace`; `IdentityAdminApi` in
44`packages/contracts/src/identity.ts`); everything about money goes to the
45billing service's (`admin_*`, `BillingAdminApi` in
46`packages/contracts/src/billing.ts`), where each change is recorded with
47the staff member's email. Both are reached over service bindings only, and
48nothing but sudo binds to them.
49
50## How it is locked
51
521. **Cloudflare Access** sits in front of `sudo.g1t.sh` and signs people in.
532. **The worker checks Access's work** on every request, the stylesheet
54 included (`run_worker_first`): it verifies the `Cf-Access-Jwt-Assertion`
55 JWT itself (RS256 against the team's published keys, audience, issuer,
56 expiry), then requires its email to be in `STAFF_EMAILS`. That email is
57 who every change is recorded as. See `app/lib/access.ts`.
583. **It fails closed.** Until `ACCESS_TEAM_DOMAIN`, `ACCESS_AUD` and
59 `STAFF_EMAILS` are all set, every request gets a 403 saying sudo is not
60 configured.
614. **Changes** are POSTs only, and only from sudo's own pages (`Origin`, or
62 `Referer`, must be `https://sudo.g1t.sh`). Terms, enterprise moves, new
63 enterprises, Stripe billing links, invoice emails, invoices and the
64 webhook show a confirmation step first; a credit needs the workspace's
65 slug typed out.
665. **The pages ship no JavaScript.** The content security policy forbids
67 every script and inline style; responses are `no-store`, `noindex` and
68 cannot be framed. The worker has no `workers.dev` address or preview URLs.
69
70## Setting up Access (once, in the Cloudflare dashboard)
71
721. **Zero Trust → Access → Applications → Add an application → Self-hosted.**
73 - Application name: `sudo`.
74 - Session duration: short, such as 8 hours.
75 - Public hostname: `sudo.g1t.sh` (path empty, so it covers everything).
762. **Add a policy** (`g1t staff`): action *Allow*, include *Emails* → the
77 owner's address, and *Emails ending in* → `g1t.sh` for everyone with a
78 g1t address (the same entries as `STAFF_EMAILS`, where a domain is
79 written `@g1t.sh`). Add more staff here *and* in `STAFF_EMAILS`; either
80 one alone is not enough.
813. Save, then open the application's **Overview** (or *Basic information*)
82 and copy the **Application Audience (AUD) tag**.
834. Find the **team domain** under **Zero Trust → Settings → Custom pages**
84 (or *Team name and domain*): it looks like `<team>.cloudflareaccess.com`.
855. Put both into `wrangler.jsonc`:
86
87 ```jsonc
88 "vars": {
89 "ACCESS_TEAM_DOMAIN": "<team>.cloudflareaccess.com",
90 "ACCESS_AUD": "<the AUD tag>",
91 "STAFF_EMAILS": "syntaqx@gmail.com, @g1t.sh"
92 }
93 ```
94
956. Deploy: `scripts/deploy.sh sudo` (after `billing` and `identity`, whose
96 `admin_*` methods it calls).
97
98Visit <https://sudo.g1t.sh>: Access asks you to sign in, then the workspaces
99list opens. Anyone else gets Access's own refusal; anyone Access lets in who
100is not in `STAFF_EMAILS` gets a 403 from the worker.
101
102## Signing in through WARP
103
104Staff signed in to the Zero Trust org in the Cloudflare One agent (WARP)
105reach sudo without the login page: the org allows WARP sessions as Access
106sign-ins (8 hours), the sudo app accepts them, and the `g1t staff` policy
107is also on the WARP enrollment app, so staff can enroll their devices.
108The same two checks still apply: the Access policy, and `STAFF_EMAILS`.
109
110## Working on it
111
112```sh
113npm run typecheck -w @g1t/sudo
114npm test -w @g1t/sudo # JWT verification, forms, money, the workspace join, paging
115npm run build -w @g1t/sudo
116```
117
118`npm run dev` serves the pages, but every request is refused without a real
119Access token, by design.