Skip to content

Commit a9a157f

Browse files
V48 Gate 2: Local Supabase QA tooling — seed + wallet-OAuth provider registration
supabase/seed.sql (referenced by config.toml [db.seed]) and scripts/register-local-wallet-oauth.sh, which registers the custom:bitcode-bitcoin OAuth2 provider with the local GoTrue stack (working around its admin-time HTTPS/SSRF guard) so wallet sign-in runs fully locally for interactive QA. No secrets are embedded; credentials are read from the running containers at runtime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 481c843 commit a9a157f

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Register the `custom:bitcode-bitcoin` custom OAuth2 provider with the LOCAL
4+
# Supabase GoTrue (v2.189.0) so wallet sign-in works fully locally.
5+
#
6+
# Why the two-step (register-then-patch):
7+
# GoTrue's admin API validates provider URLs with an SSRF guard
8+
# (utilities.ValidateOAuthURL) that REQUIRES https and BLOCKS localhost /
9+
# loopback / RFC-1918 — i.e. it rejects every local URL at registration time.
10+
# That guard is admin-time only; the *runtime* token/userinfo fetch uses the
11+
# default oauth2 client (no SSRF transport). So we:
12+
# 1. create the provider via the admin API using the real public https URLs
13+
# (passes validation; GoTrue encrypts client_secret correctly), then
14+
# 2. SQL-patch authorization_url/token_url/userinfo_url to the local
15+
# endpoints directly in auth.custom_oauth_providers.
16+
#
17+
# Browser-facing authorization_url -> http://localhost:3000 (the user's
18+
# browser hits it). token_url/userinfo_url -> http://host.docker.internal:3000
19+
# (the GoTrue *container* calls back into the app on the host).
20+
#
21+
# GoTrue stores custom providers in the `auth` schema, which `supabase db reset`
22+
# wipes — re-run this after every `supabase start` / `supabase db reset`.
23+
# No secrets are printed.
24+
25+
set -euo pipefail
26+
27+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
28+
ENVF="$ROOT/uapi/.env.local"
29+
30+
TMP="$(mktemp)"; chmod 600 "$TMP"
31+
trap 'rm -f "$TMP"' EXIT
32+
bash "$ROOT/scripts/supabase.sh" status -o env > "$TMP" 2>/dev/null
33+
API="$(grep '^API_URL=' "$TMP" | cut -d= -f2- | tr -d '"')"
34+
SVC="$(grep '^SERVICE_ROLE_KEY=' "$TMP" | cut -d= -f2- | tr -d '"')"
35+
[ -n "${API:-}" ] && [ -n "${SVC:-}" ] || { echo "ERROR: stack not up? run scripts/supabase.sh start" >&2; exit 1; }
36+
37+
DB_CONTAINER="$(docker ps --format '{{.Names}}' | grep -m1 '^supabase_db_')"
38+
[ -n "${DB_CONTAINER:-}" ] || { echo "ERROR: supabase_db container not found" >&2; exit 1; }
39+
40+
val() { grep -E "^$1=" "$ENVF" 2>/dev/null | head -1 | cut -d= -f2- | sed -E 's/^"(.*)"$/\1/'; }
41+
CID="$(val BITCODE_BITCOIN_OAUTH_CLIENT_ID)"; CID="${CID:-bitcode-bitcoin-wallet}"
42+
CSECRET="$(val BITCODE_BITCOIN_OAUTH_CLIENT_SECRET)"; CSECRET="${CSECRET:-bitcode-bitcoin-wallet-local-dev-secret}"
43+
44+
APP_BROWSER="${BITCODE_LOCAL_APP_ORIGIN:-http://localhost:3000}"
45+
APP_CONTAINER="${BITCODE_LOCAL_APP_ORIGIN_CONTAINER:-http://host.docker.internal:3000}"
46+
ID="custom:bitcode-bitcoin"
47+
ID_ENC="custom%3Abitcode-bitcoin"
48+
49+
# 1) idempotent re-create with PUBLIC https placeholders (pass SSRF validation)
50+
curl -s -o /dev/null -X DELETE "$API/auth/v1/admin/custom-providers/$ID_ENC" \
51+
-H "Authorization: Bearer $SVC" -H "apikey: $SVC" || true
52+
53+
resp="$(curl -s -w $'\n%{http_code}' -X POST "$API/auth/v1/admin/custom-providers" \
54+
-H "Authorization: Bearer $SVC" -H "apikey: $SVC" -H "Content-Type: application/json" \
55+
--data-binary @- <<JSON
56+
{
57+
"provider_type": "oauth2",
58+
"identifier": "$ID",
59+
"name": "Bitcode Bitcoin Wallet",
60+
"client_id": "$CID",
61+
"client_secret": "$CSECRET",
62+
"scopes": ["profile", "wallet:bitcoin"],
63+
"pkce_enabled": true,
64+
"email_optional": true,
65+
"enabled": true,
66+
"authorization_url": "https://bitcode.exchange/tps/wallet/authorize",
67+
"token_url": "https://bitcode.exchange/api/wallet/oauth/token",
68+
"userinfo_url": "https://bitcode.exchange/api/wallet/oauth/userinfo"
69+
}
70+
JSON
71+
)"
72+
code="$(printf '%s' "$resp" | tail -1)"
73+
echo "register custom:bitcode-bitcoin (placeholder URLs) -> HTTP $code"
74+
# 201 = created fresh; an existing provider comes back as 400 error_code=conflict
75+
# (re-run without db reset). Both are fine — the patch below updates the row.
76+
if [ "$code" != "201" ] && ! printf '%s' "$resp" | grep -q '"error_code":"conflict"'; then
77+
printf '%s\n' "$(printf '%s' "$resp" | sed '$d')"; exit 1
78+
fi
79+
80+
# 2) Drop the admin-time https CHECK constraints (they reject local http URLs),
81+
# then SQL-patch the URL columns to the local endpoints. The runtime
82+
# token/userinfo fetch uses the default oauth2 client (no SSRF guard), so
83+
# plain-http host.docker.internal works. This is on the ephemeral local
84+
# `auth` schema only; `supabase db reset` restores the constraints, which is
85+
# why this script must re-run after a reset. URLs are controlled (no quotes).
86+
# DDL on the GoTrue-owned auth table must run as its owner, supabase_auth_admin.
87+
# That role's password lives in the auth container's GOTRUE_DB_DATABASE_URL.
88+
AUTH_CONTAINER="$(docker ps --format '{{.Names}}' | grep -m1 '^supabase_auth_')"
89+
AUTH_DB_URL="$(docker exec "$AUTH_CONTAINER" printenv GOTRUE_DB_DATABASE_URL 2>/dev/null)"
90+
AUTH_PW="$(printf '%s' "$AUTH_DB_URL" | sed -E 's#^postgres(ql)?://supabase_auth_admin:([^@]*)@.*#\2#')"
91+
[ -n "${AUTH_PW:-}" ] || { echo "ERROR: could not read supabase_auth_admin password from auth container" >&2; exit 1; }
92+
docker exec -i -e PGPASSWORD="$AUTH_PW" "$DB_CONTAINER" \
93+
psql -U supabase_auth_admin -h 127.0.0.1 -d postgres -v ON_ERROR_STOP=1 >/dev/null <<SQL
94+
ALTER TABLE auth.custom_oauth_providers DROP CONSTRAINT IF EXISTS custom_oauth_providers_authorization_url_https;
95+
ALTER TABLE auth.custom_oauth_providers DROP CONSTRAINT IF EXISTS custom_oauth_providers_token_url_https;
96+
ALTER TABLE auth.custom_oauth_providers DROP CONSTRAINT IF EXISTS custom_oauth_providers_userinfo_url_https;
97+
UPDATE auth.custom_oauth_providers
98+
SET authorization_url = '$APP_BROWSER/tps/wallet/authorize',
99+
token_url = '$APP_CONTAINER/api/wallet/oauth/token',
100+
userinfo_url = '$APP_CONTAINER/api/wallet/oauth/userinfo',
101+
updated_at = now()
102+
WHERE identifier = 'custom:bitcode-bitcoin';
103+
SQL
104+
105+
echo "patched provider URLs -> browser=$APP_BROWSER container=$APP_CONTAINER"
106+
echo "--- verify (auth.custom_oauth_providers) ---"
107+
docker exec -i "$DB_CONTAINER" psql -U postgres -d postgres -A -F $'\t' \
108+
-c "SELECT identifier, provider_type, enabled, email_optional, pkce_enabled,
109+
authorization_url, token_url, userinfo_url
110+
FROM auth.custom_oauth_providers WHERE identifier='custom:bitcode-bitcoin';"

supabase/seed.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- Local Supabase seed (referenced by config.toml [db.seed]).
2+
--
3+
-- Intentionally minimal. The V48 local QA fixtures are established at runtime,
4+
-- not seeded as SQL, because they depend on services that own their own schema:
5+
--
6+
-- * The wallet identity + auth.users row is minted by GoTrue when the
7+
-- `custom:bitcode-bitcoin` provider completes a sign-in (see
8+
-- scripts/register-local-wallet-oauth.mjs, run after `supabase start`).
9+
-- * The GitHub App connection (user_connections) + repository inventory
10+
-- (vcs_repositories) are written by the live GitHub App install/callback
11+
-- flow against localhost, exactly as in staging.
12+
--
13+
-- Keeping this file present (even if empty of DML) satisfies the config.toml
14+
-- seed reference so `supabase db reset` does not error on a missing path.
15+
16+
select 'bitcode local seed: no-op (runtime fixtures)' as seed_note;

0 commit comments

Comments
 (0)