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.jsoncvars (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.jsoncvars (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

  1. Google Cloud Console → APIs & Services → Credentials → the OAuth client → Add secret (Google supports two live secrets during rotation).
  2. wrangler secret put GOOGLE_CLIENT_SECRET with the new value, then npm run deploy.
  3. 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.

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.

  1. wrangler secret put TOKEN_ENCRYPTION_KEY with a new openssl rand -base64 32.
  2. Clear the now-undecryptable grants so the app cleanly routes users back through consent:
wrangler d1 execute webanalytics --remote \
  --command "DELETE FROM oauth_grant;"
  1. 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 Architecture0004/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.
  • /admin is gated by ADMIN_EMAILS (comma-separated, compared case-insensitively against the session email). Add/remove emails in wrangler.jsonc vars and 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.