Skip to main content

Secrets & configuration

There are three secret stores in play:

  1. Cloudflare Workers secret store — runtime API secrets.
  2. GitHub Actions secrets — CI/CD.
  3. Local dev filesapps/api/.dev.vars, apps/mobile/.env.local.

Cross-reference: architecture/auth § Secrets summary is the canonical list of what each secret is for.

Cloudflare Workers (API runtime)

Set with wrangler secret put <NAME>:

NamePurpose
SESSION_SECRETHS256 key for signing our own session JWTs. 32+ bytes of randomness (openssl rand -hex 32).
PII_ENCRYPTION_KEYKey used with pgcrypto to encrypt sensitive columns at rest. 32+ bytes. See privacy.
INFINIA_API_USERNAMEHTTP Basic username for Infinia API
INFINIA_API_PASSWORDHTTP Basic password for Infinia API
INFINIA_WEBHOOK_SECRETPath secret matched against POST /webhooks/infinia/:secret. Also registered as the callback_url on every Infinia resource we create.
NEON_DB_URLPostgres URL. Production Neon branch (pinned to an EU region)

Public vars (wrangler.toml [vars] — not secrets)

NamePurpose
APPLE_BUNDLE_IDExpected aud claim on Apple id_tokens. Must match the iOS app's bundle id.
GOOGLE_OAUTH_CLIENT_IDExpected aud claim on Google id_tokens.
INFINIA_COMPANY_IDOur tenant id at Infinia, sent as x-company-id header on every request.
INFINIA_BASE_URLhttps://app2test.infiniaweb.com/infinia_api in sandbox; production URL when Infinia issues one.

Generating strong secrets

# 32-byte random hex (SESSION_SECRET, PII_ENCRYPTION_KEY, INFINIA_WEBHOOK_SECRET)
openssl rand -hex 32

GitHub Actions secrets

See deployment § Required GitHub secrets for the full list.

Local development

apps/api/.dev.vars (git-ignored — .env* in root .gitignore):

SESSION_SECRET=<openssl rand -hex 32>
PII_ENCRYPTION_KEY=<openssl rand -hex 32>
INFINIA_API_USERNAME=<sandbox username>
INFINIA_API_PASSWORD=<sandbox password>
INFINIA_WEBHOOK_SECRET=<openssl rand -hex 32>
INFINIA_COMPANY_ID=<sandbox company id>
INFINIA_BASE_URL=https://app2test.infiniaweb.com/infinia_api
APPLE_BUNDLE_ID=com.gringopay.dev
GOOGLE_OAUTH_CLIENT_ID=<dev OAuth client id>
NEON_DB_URL=postgresql://.../neondb?sslmode=require

Wrangler picks .dev.vars up automatically for wrangler dev. Tests use a local Postgres (cd apps/api && docker compose up -d --wait), not .dev.vars — Vitest defaults TEST_DATABASE_URL to the container; set it only to override.

apps/mobile/.env.local (git-ignored):

EXPO_PUBLIC_API_BASE_URL=http://localhost:8787
EXPO_PUBLIC_APPLE_BUNDLE_ID=com.gringopay.dev
EXPO_PUBLIC_GOOGLE_OAUTH_CLIENT_ID=<dev OAuth client id>

Only EXPO_PUBLIC_* vars are readable at runtime in the RN bundle.

Rotation

SESSION_SECRET — rotating logs everyone out (their existing JWTs stop verifying). Do this only when required (suspected compromise). Steps:

  1. openssl rand -hex 32 → new value
  2. wrangler secret put SESSION_SECRET
  3. Users re-authenticate on next request

PII_ENCRYPTION_KEY — rotating is a data-migration event because existing encrypted columns are keyed to the old value. Do not rotate casually. Proper rotation involves:

  1. Introduce PII_ENCRYPTION_KEY_NEXT as a second secret
  2. Ship code that writes with KEY_NEXT and reads with either key
  3. Backfill: re-encrypt every existing encrypted column with KEY_NEXT
  4. Once complete, promote KEY_NEXTSESSION_SECRET; drop the old key
  5. Ship code that only knows SESSION_SECRET

Only rotate on suspected compromise. Otherwise treat as static.

INFINIA_* secrets — rotate in the Infinia dashboard first, then wrangler secret put. For INFINIA_WEBHOOK_SECRET you must also update the callback_url on every active Infinia resource (owners, accounts, payouts, transfers) to include the new value — otherwise callbacks 401 at the path check.

NEON_DB_URL — rotate the DB user password in Neon, generate a new connection string, wrangler secret put.

Secret changes are picked up on the next request — no redeploy required.

What's not a secret (deliberately)

  • Apple / Google OAuth client IDs — public identifiers. Live in [vars].
  • Infinia company id — public identifier for our tenant. Live in [vars].
  • Infinia base URL — public. Live in [vars].

Public vars are safe in wrangler.toml and in the mobile bundle. Do not put anything with _SECRET / _PASSWORD / _KEY / _TOKEN in [vars] — those are secrets by definition.