Answer
Who opens what: our own staff see every brand, a brand opens only to its own work email, an invite or a single jam guest link, and everyone else is sent to the demo stores; this page settles those rules so no new feature adds a way in that skips them.
Today
The roles
| Who | How they get in | What they may do | Source |
|---|---|---|---|
| Root admin | ADMIN_EMAILS; on production param@joinsquareup.com only |
Everything staff do, plus sign someone out everywhere and force a change to a protected connection | brew/auth.js:89-91 brew/tools/prod-env.sh:9 |
| Staff | Any address at joinsquareup.com or shopos.ai, subdomains too |
Every brand, every view, every make; never claim a brand that is not at their own domain | brew/auth.js:10-11, 33-36, 166 |
| Brand admin, the owner | First official email to tap Claim, connect an account or confirm the read | Invite, set views, connect and disconnect, hand over; a brand always keeps one admin | brew/auth.js:6-7, 159-171, 194-215 |
| Member, same company | Signs in with the claimed brand's own domain; joins on their own with every view; admins are emailed | Everything but team changes; may invite others to their own views only | brew/auth.js:104-109, 294-296 |
| Member, invited | An emailed invite, 14 days, valid only for that Google account | The views the invite named | brew/auth.js:78, 180-193, 264-276 |
| Jam guest | A shared jam link and a sign in | Chat, edit the brief and make drafts in that one jam; never share, send, approve, assign, discard or make paid media | brew/SCOPE.md:24 handbook content/engineering/changelog.md:58 |
| Outsider | Signed in with Gmail or another domain | One wall pointing to demo.joinsquareup.com; cannot start a new read or sign up for Plays emails | memory brew-access-rules |
| Visitor on the demo | Anyone | A throwaway sandbox of a prepared brand; makes are open there | brew/auth.js:37-38, 148 |
What counts as the brand's own email
- Same domain as the store, a subdomain of it, or the company domain the store sits under. brew/auth.js:27-31
- Free mail and shared platforms such as
myshopify.com,wixsite.comorvercel.appnever count. brew/auth.js:16, 22-24 - A brand our staff claimed for it, such as Arata, lets the store's own domain join. brew/auth.js:106-108
- Starting a read gives nothing on its own since 23 Oct. brew/auth.js:139-140 memory brew-access-rules
Special cases
- A closed Shopify store, read from its admin while behind the password page, is shown to its team and staff only; everyone else gets "This store isn't open yet". brew/closed-gate.js:1-12, 50-71 memory brew-closed-store-gate
- Inside the Shopify admin, Shopify's session token names the person; the installer becomes admin of an unclaimed brand, the store owner becomes admin, other staff members. memory brew-shopify-app-review
- Connecting accounts is for brand admins and staff only; teammates may sync. memory brew-real-connectors
- Protected connections,
PROTECTED_CONNECTIONS=arata-in, refuse disconnect, re-select or reconnect unless a root admin forces it. brew/connectors/protected.js:1-4 memory never-disconnect-arata-meta
Sessions and logging
- Google sign in uses PKCE and a nonce; only a Google verified email is trusted. brew/auth.js:3-4, 220-238
- The session is a signed cookie for 30 days with a session row; signing out ends it on every instance. brew/auth.js:243-244, 254-257 brew/sessions.js:1-4
- A sign out link from another site does nothing. brew/auth.js:254-256
STAFF_REQUIRE_HD=1would accept a staff address only from that domain's own Google Workspace; production does not set it. brew/auth.js:239-241 brew/tools/prod-env.sh:9- Every signed in email that opens a brand is logged with its access kind; only root admins read the list. brew/server.js:657-666, 817 handbook content/engineering/changelog.md:81
Decided exceptions
- A personal Gmail stays on staging's admin list, by Param's choice on 23 Oct; do not flag it again. memory brew-access-rules
Tomorrow
| Item | Version | Tag |
|---|---|---|
| Official email, invite or guest link as the only ways in | v1 | Stays |
STAFF_REQUIRE_HD=1 on production |
v1 | New, proposed |
| Staff by named address, not two whole domains | v1 | Changes, proposed |
| Guest links that expire and can be revoked | v1 | New, proposed |
| Proof of ownership by store admin or DNS, beside the email domain | v2 | New |
| Agency seats: one person on several brands, invited by each | v2 | New |
| Who may approve a post or a schedule, per brand | v2 | New |
| Who may set ad rules and undo a run | v4 | New |
How we do it
- Never add a route, page or file that opens brand data without going through
auth.roleOf,genOkand the closed store gate. - Every new brand route falls under the closed gate's
slugInpatterns. memory brew-closed-store-gate - When touching auth, test the outsider paths: Gmail, another domain, a guest, signed out.
cd /Users/param/Documents/ChatGPT/brew && AI_MOCK=1 node --test test/access.test.js test/qa-access.test.js test/qa-final-access.test.js test/qv1-roles-security.test.js
- To see who opened a brand, a root admin calls
GET /api/admin/visitors?slug=<slug>. - To end someone's sessions everywhere, a root admin uses
/api/admin/sessions.
Numbers
| Metric | Definition | Where measured | Status |
|---|---|---|---|
| Blocked opens | Signed in emails refused per brand per week | access_log, access blocked |
Proposed |
| Staff with full access | Distinct staff emails seen in access_log |
access_log, access brew |
Proposed |
| Unclaimed partner brands | Partners with no brand admin of their own | members.json per brand |
Proposed |
Open
- Should every
shopos.aiaddress see every brand? Proposal: no; a named list of staff addresses on production, and the two domains only on staging. - Turn on
STAFF_REQUIRE_HD=1on production? Proposal: yes, so a personal Google account made with a staff address never counts as staff. - Should guest links expire? Proposal: 30 days, with a Revoke button for brand admins.