Skip to content

feat(relay): capture signup country at sign-in via cf.country - #563

Open
felipesalinasr wants to merge 1 commit into
mainfrom
feat/signup-country-capture
Open

feat(relay): capture signup country at sign-in via cf.country#563
felipesalinasr wants to merge 1 commit into
mainfrom
feat/signup-country-capture

Conversation

@felipesalinasr

Copy link
Copy Markdown
Collaborator

Why

We can't report users by country today. Google OAuth returns no location, and the handle_new_user Postgres trigger that creates the profiles row runs inside Postgres and never sees the request IP — so country can't be captured there. This adds tamper-proof, server-side country capture with zero user-facing friction.

How it works

  1. Migrationprofiles gets nullable signup_country (ISO 3166-1 alpha-2) and country_source. Additive and backward-compatible.
  2. Relay Worker — new POST /capture-country (houston-relay) reads request.cf.country (Cloudflare-resolved, server-side, can't be spoofed by the client), verifies the caller's Supabase access token via GoTrue, and PATCHes their profile with the service role. Idempotent — only writes when signup_country is null, so re-logins never overwrite the first value.
  3. App — after exchangeCodeForSession / setSession succeeds in app/src/lib/auth.ts, a non-blocking, fire-and-forget call hits the relay route. Failures only log; they never block or fail sign-in.

No IP is ever read or stored — request.cf.country is pre-resolved by Cloudflare. Country only, so this stays clear of GDPR PII handling.

Files

  • supabase/migrations/20260621000000_profiles_country.sql — new columns
  • houston-relay/src/capture-country.ts — new route handler + normalizeCountry
  • houston-relay/src/index.ts — route wiring
  • houston-relay/src/types.tsSUPABASE_URL var + SUPABASE_SERVICE_ROLE_KEY secret
  • houston-relay/wrangler.tomlSUPABASE_URL (prod + staging)
  • houston-relay/test/capture-country.test.tsnormalizeCountry unit tests
  • app/src/lib/auth.ts — non-blocking capture call (both auth branches)

Verification

  • pnpm --filter houston-relay typecheck — passes
  • normalizeCountry logic verified (XX / T1 / empty / malformed → null; valid → upper-cased)
  • Relay vitest pool couldn't run in the authoring sandbox (workspace path contains a space, which breaks the workers-pool module resolver) — CI runs it normally.

⚠️ Before merging / deploying

  • Set SUPABASE_SERVICE_ROLE_KEY as a relay Worker secret: cd houston-relay && wrangler secret put SUPABASE_SERVICE_ROLE_KEY (and again with --env staging). The route no-ops (503) until it's set.
  • Apply the migration to houston-prod.
  • Confirm staging SUPABASE_URL is correct if staging uses a separate Supabase project (currently points at the prod project).
  • Decide: signup-only (this PR) vs. also stamping a current_country on each login.

Notes

  • No backfill for existing users — auth.audit_log_entries is purged, so no historical IPs exist. Country is captured from launch forward only.
  • Not auto-merged: this touches the production auth path and needs the secret set first, so it's left for team review.

🤖 Generated with Claude Code

We can't report users by country: Google OAuth returns no location, and
the handle_new_user trigger that creates the profile row runs inside
Postgres and never sees the request IP. Capture it server-side instead.

- supabase: additive migration adds nullable profiles.signup_country
  (ISO alpha-2) + country_source. No backfill — no historical IPs exist.
- houston-relay: new POST /capture-country reads request.cf.country
  (server-side, tamper-proof), verifies the caller's Supabase access
  token, and PATCHes their profile with the service role. Idempotent
  (only when signup_country is null); no IP is read or stored.
- app: after sign-in, fire a non-blocking call to the relay route from
  auth.ts (both PKCE and implicit branches). Best-effort: failures only
  log and never affect sign-in.
- tests: unit-test normalizeCountry (XX/T1/empty/malformed -> null).

Deploy prerequisites (not in this diff): set SUPABASE_SERVICE_ROLE_KEY
as a relay Worker secret (prod + --env staging) before deploying.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@felipesalinasr felipesalinasr self-assigned this Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant