Why it is easy to run
If you run a show, you know the pile: a Drive folder, three forms, a slide deck, and a group text where the one important message is buried under forty others. This puts it in one private website the cast can check from their phone.
- Performers sign in with the Google account they already have (or a code sent to their email). No new password to forget.
- Adding or removing someone takes me a minute. When a performer leaves, I take them off the list. Nobody else has to change anything, because there is no shared password to reset.
- Someone not on the list can ask to get in with a one-line reason, and I approve or ignore it.
- There is nothing for me to maintain. No software for me to update, and nothing I have to keep running.
The rest of this page is for the technical folks.
The problem
The show I direct is invite-only, and performer identities are private. Everything lived across Drive folders, forms, a slide deck and group texts. The cast needed one place to go, and I needed to be able to add or remove one person without handing out a new shared password to everyone.
What I built
A static portal on Cloudflare Pages behind Cloudflare Access. Two policies, evaluated in order: people in the cohort group go straight in, and anyone else gets a request box with a one-line reason that I approve or ignore. Google sign-in is the primary login and an emailed one-time code is the fallback, which works because every performer already has a Google account.
The Access setup lives in a JSON config and a small Python CLI with plan, apply and status, so a change is reviewed before it is made and plan says “nothing to change” when the live config matches. The allowlist is generated from the roster, and the roster never leaves a gitignored directory. Two scoped API tokens, one read-only and one write, both with expiry.
The bug worth writing down
For two days nobody’s login code arrived. I had put 33 email addresses under a Require rule. In Access, Include is OR and Require is AND, so the rule asked each person to be all 33 people at once, nobody matched, and Access sends no code unless an Allow policy’s Include matches. The runbook now leads with that.
Rejected on purpose
Netlify’s site password (one shared password for everyone), Netlify Identity (deprecated), and self-set passwords (that needs a real auth product). All screenshots of the portal would show real people, so this page describes the architecture only.