Skip to content

Commit f4a19ed

Browse files
Patchalvclaude
andauthored
fix(rc-entitlement-drift-check): use REVENUECAT_SECRET_API_KEY_V2 (#66)
RC v1 secret keys are rejected by the v2 customer/entitlement endpoints with a 403. Splitting the env var so delete-account keeps using its v1 key on /v1/subscribers and rc-entitlement-drift-check uses a separately issued v2 key with customer_information:customers:read and customer_information:entitlements:read scopes. Surfaced during the first manual smoke test of the cron job (PR #60); returned 500 with rc_list_entitlements_403 in the function logs. Docs updated (payments.md, edge-functions.md, deployment.md) so operators issue a separate v2 key during first-time setup. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 872f729 commit f4a19ed

4 files changed

Lines changed: 20 additions & 7 deletions

File tree

docs/deployment.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ Server-side secrets (not in `.env`):
5151
| Secret | Purpose |
5252
|--------|---------|
5353
| `REVENUECAT_WEBHOOK_SECRET` | Authenticates RevenueCat webhook requests |
54-
| `REVENUECAT_SECRET_API_KEY` | Admin API key (used by `delete-account` and `rc-entitlement-drift-check`) |
54+
| `REVENUECAT_SECRET_API_KEY` | RC v1 secret key (used by `delete-account` only) |
55+
| `REVENUECAT_SECRET_API_KEY_V2` | RC v2-scoped secret key (used by `rc-entitlement-drift-check`). Issue separately in RC dashboard with `customer_information:customers:read` + `customer_information:entitlements:read` scopes. |
5556
| `REVENUECAT_PROJECT_ID` | RC project id (used by `rc-entitlement-drift-check`) |
5657
| `RC_DRIFT_CHECK_INVOKE_SECRET` | Bearer for pg_cron → `rc-entitlement-drift-check`; must mirror `vault.secrets.rc_drift_check_invoke_secret`. See `docs/payments.md` → "Drift Health Check" for the first-time setup and rotation runbook. |
5758

docs/edge-functions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ A table-row mutex on `public.drift_check_runs` (default-deny RLS) prevents overl
353353
### Secrets Required
354354

355355
- `RC_DRIFT_CHECK_INVOKE_SECRET` — bearer that pg_cron uses to invoke this function; must mirror `vault.secrets.rc_drift_check_invoke_secret`
356-
- `REVENUECAT_SECRET_API_KEY` — RC v2 admin key (shared with `delete-account`)
356+
- `REVENUECAT_SECRET_API_KEY_V2` — RC v2-scoped secret key with `customer_information:customers:read` + `customer_information:entitlements:read`. Distinct from `delete-account`'s v1 `REVENUECAT_SECRET_API_KEY`; v1 keys are rejected by RC v2 endpoints.
357357
- `REVENUECAT_PROJECT_ID` — RC project id (`proj18594bd9`)
358358

359359
### Tables Written

docs/payments.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,18 @@ After both have changed and the vault row is verified, manually fire one run to
140140
**First-time setup (only once per environment):**
141141

142142
```bash
143-
# 1. Generate and set the function env var
143+
# 1. Generate and set the function env vars
144144
INVOKE_SECRET=$(openssl rand -hex 32)
145145
supabase secrets set RC_DRIFT_CHECK_INVOKE_SECRET="$INVOKE_SECRET"
146146
supabase secrets set REVENUECAT_PROJECT_ID="proj18594bd9"
147-
# REVENUECAT_SECRET_API_KEY is already set (used by delete-account)
147+
148+
# REVENUECAT_SECRET_API_KEY_V2 is a SEPARATE key from REVENUECAT_SECRET_API_KEY.
149+
# delete-account uses the v1 key on /v1/subscribers; this function uses v2
150+
# endpoints which only accept a v2-scoped key. Issue a new v2 secret key in
151+
# the RC dashboard (Project Settings → API Keys) with permissions:
152+
# customer_information:customers:read
153+
# customer_information:entitlements:read
154+
supabase secrets set REVENUECAT_SECRET_API_KEY_V2="<v2-secret-key>"
148155

149156
# 2. Deploy the function
150157
supabase functions deploy rc-entitlement-drift-check --no-verify-jwt
@@ -205,7 +212,8 @@ The cron job will start firing at the next `:17 mod 6h` UTC mark.
205212

206213
- **Edge Function secrets** (set via dashboard or CLI):
207214
- `REVENUECAT_WEBHOOK_SECRET` — must match the Bearer token configured in RevenueCat webhook settings
208-
- `REVENUECAT_SECRET_API_KEY` — RC v2 REST API key, used by `delete-account` and `rc-entitlement-drift-check`
215+
- `REVENUECAT_SECRET_API_KEY` — RC **v1** secret key, used by `delete-account` against `/v1/subscribers`
216+
- `REVENUECAT_SECRET_API_KEY_V2` — RC **v2**-scoped secret key (separate from the v1 key), used by `rc-entitlement-drift-check` against `/v2/projects/...` endpoints. Required scopes: `customer_information:customers:read`, `customer_information:entitlements:read`.
209217
- `REVENUECAT_PROJECT_ID` — RC project id, used by `rc-entitlement-drift-check`
210218
- `RC_DRIFT_CHECK_INVOKE_SECRET` — Bearer for pg_cron → `rc-entitlement-drift-check`; must mirror `vault.secrets.rc_drift_check_invoke_secret`
211219
- `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` — auto-injected, no manual setup needed
@@ -225,7 +233,8 @@ The cron job will start firing at the next `:17 mod 6h` UTC mark.
225233
| `EXPO_PUBLIC_REVENUECAT_API_KEY` | `.env` + EAS secrets | RevenueCat Apple API key, read at build time |
226234
| `EXPO_PUBLIC_REVENUECAT_GOOGLE_API_KEY` | `.env` + EAS secrets | RevenueCat Google API key, read at build time |
227235
| `REVENUECAT_WEBHOOK_SECRET` | Supabase Edge Function secrets | Webhook auth, server-side only |
228-
| `REVENUECAT_SECRET_API_KEY` | Supabase Edge Function secrets | RC v2 REST API admin key (`delete-account`, `rc-entitlement-drift-check`) |
236+
| `REVENUECAT_SECRET_API_KEY` | Supabase Edge Function secrets | RC v1 secret key (`delete-account` only — used against `/v1/subscribers`) |
237+
| `REVENUECAT_SECRET_API_KEY_V2` | Supabase Edge Function secrets | RC v2-scoped secret key (`rc-entitlement-drift-check` — used against `/v2/projects/...` endpoints). Required scopes: `customer_information:customers:read`, `customer_information:entitlements:read`. |
229238
| `REVENUECAT_PROJECT_ID` | Supabase Edge Function secrets | RC project id, used by `rc-entitlement-drift-check` |
230239
| `RC_DRIFT_CHECK_INVOKE_SECRET` | Supabase Edge Function secrets **and** Supabase Vault | Bearer that pg_cron uses to invoke `rc-entitlement-drift-check`; rotate in both places together |
231240

supabase/functions/rc-entitlement-drift-check/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ serve(async (req) => {
8787

8888
try {
8989
const projectId = Deno.env.get("REVENUECAT_PROJECT_ID");
90-
const rcKey = Deno.env.get("REVENUECAT_SECRET_API_KEY");
90+
// RC v2 endpoints (list-customers, list-entitlements) require a
91+
// v2-scoped secret key. delete-account uses the v1 key on /v1/subscribers;
92+
// they're separate env vars so neither can break the other.
93+
const rcKey = Deno.env.get("REVENUECAT_SECRET_API_KEY_V2");
9194
if (!projectId || !rcKey) {
9295
throw new Error("missing_revenuecat_env");
9396
}

0 commit comments

Comments
 (0)