Operations & credentials runbook¶
Operational reference for running webanalytics in production: what secrets it holds, how to
rotate them safely, how to back up the data, and how to deploy. The product overview is in
Overview; how it's built is in Architecture.
/ production target: Cloudflare Worker webanalytics on the Swapps account
(account_id b7edd98532935f2d2477d46a266cd1bc), served at webanalytics.swapps.com via a
custom-domain route on the swapps.com zone (Wrangler manages the DNS record + SSL).
Credentials inventory¶
| Name | Where it lives | Type | What it protects |
|---|---|---|---|
GOOGLE_CLIENT_ID |
wrangler.jsonc → vars (non-secret) |
public OAuth client id | identifies the app to Google |
GOOGLE_CLIENT_SECRET |
wrangler secret |
secret | the OAuth code→token exchange |
TOKEN_ENCRYPTION_KEY |
wrangler secret |
AES-GCM key, base64 32-byte | encrypts every user refresh token at rest in D1/KV |
SESSION_SIGNING_KEY |
wrangler secret |
HMAC-SHA256 key, base64 32-byte | signs the wa_session cookie |
ADMIN_EMAILS |
wrangler.jsonc → vars (non-secret) |
allowlist | who can reach /admin |
Secrets are never committed. Locally they live in .dev.vars (git-ignored); in production
they are uploaded with wrangler secret put <NAME>. The OAuth client itself is created in the
Google Cloud Console project — full first-time setup is in the app repo's docs/setup.md.
Where user tokens are stored¶
Each signed-in user has an oauth_grant row holding their Google refresh token (encrypted
with TOKEN_ENCRYPTION_KEY) plus a short-lived encrypted access-token cache. We hold a per-user
Google grant — not a service account — so a leaked grant exposes one user, and rotating
TOKEN_ENCRYPTION_KEY invalidates every stored token at once (see below).
Key rotation¶
SESSION_SIGNING_KEY — zero data loss, logs everyone out¶
The signing key only protects session cookies; nothing persistent depends on it.
wrangler secret put SESSION_SIGNING_KEY # paste `openssl rand -base64 32`
Effect: every existing wa_session cookie fails its HMAC check on the next request, so all users
are logged out and sign in again (one click — the Google grant is untouched). Safe to do anytime;
do it immediately if a key is suspected leaked.
GOOGLE_CLIENT_SECRET — rotate in Google first¶
- Google Cloud Console → APIs & Services → Credentials → the OAuth client → Add secret (Google supports two live secrets during rotation).
wrangler secret put GOOGLE_CLIENT_SECRETwith the new value, thennpm run deploy.- Confirm a fresh login + a token refresh both succeed, then delete the old secret in Google.
Existing user refresh tokens keep working — they don't depend on the client secret — so there's no user-visible impact when done in this order.
TOKEN_ENCRYPTION_KEY — destructive, re-consent required¶
This key decrypts stored refresh tokens. There is no in-place re-encryption path, so rotating it invalidates every stored grant: users must re-authorize with Google on their next action.
wrangler secret put TOKEN_ENCRYPTION_KEYwith a newopenssl rand -base64 32.- Clear the now-undecryptable grants so the app cleanly routes users back through consent:
wrangler d1 execute webanalytics --remote \
--command "DELETE FROM oauth_grant;"
- Deploy. Users are prompted to reconnect Google; business data (sites, links, analyses) is untouched.
Only rotate this key on suspected compromise of the key itself — it is the most disruptive of the three. Announce it, because every user has to reconnect.
Data backup & restore¶
Business data lives in the D1 database webanalytics
(database_id 062e109f-3261-4b17-8c26-1e5697b7266c). KV holds only caches and ephemeral state
(sessions, encrypted access-token cache, PKCE state) — KV is disposable; do not back it up.
# Full logical snapshot before any risky migration or rotation.
wrangler d1 export webanalytics --remote --output webanalytics-$(date +%F).sql
# Restore into a fresh DB (e.g. for a dry run) and inspect.
wrangler d1 execute <restore-target> --remote --file webanalytics-YYYY-MM-DD.sql
Take an export before applying any table-recreating migration to production (see the migration
safety note in Architecture — 0004/0006 are the cautionary example).
Deploy & migrations¶
npm run db:migrate:remote # wrangler d1 migrations apply webanalytics --remote
npm run deploy # wrangler deploy
Routine order for a release that includes schema changes: export → migrate → deploy → smoke
test (/healthz, a login, one analysis run). Migrations are forward-only; there is no automatic
down-migration, so the D1 export is the rollback.
Access control¶
- App access is per-user Google OAuth — anyone with a Google account can sign in and only ever sees their own grant's data.
/adminis gated byADMIN_EMAILS(comma-separated, compared case-insensitively against the session email). Add/remove emails inwrangler.jsoncvarsand redeploy.
Phase 2 readiness (write scopes)¶
Phase 2 adds the analytics.edit and tagmanager.edit.containers scopes, which moves the OAuth
consent screen from Google's "non-sensitive" tier into sensitive/restricted scopes requiring
Google's verification review (homepage + published privacy policy + a demo video; typically
1–4 weeks). Do not add the scopes to the live client until that verification is scheduled —
otherwise the consent screen breaks for existing users. Track this gate on the Phase 2 task before
shipping any write action.