Skip to content

fix(db): enforce API key expiry in get_user_org_ids#1809

Merged
riderx merged 2 commits into
mainfrom
codex/fix-get-user-org-ids-apikey-expiry
Mar 18, 2026
Merged

fix(db): enforce API key expiry in get_user_org_ids#1809
riderx merged 2 commits into
mainfrom
codex/fix-get-user-org-ids-apikey-expiry

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 17, 2026

Summary (AI generated)

  • add a new forward Supabase migration that redefines public.get_user_org_ids()
  • enforce API key expiry in that RPC using find_apikey_by_value() and is_apikey_expired()
  • add pgTAP regression coverage for expired and valid API keys in get_user_org_ids()
  • avoid editing historical migrations so fresh resets and already-migrated databases both stay correct

Motivation (AI generated)

The original PR fixed the logic in the wrong place by editing an already-applied migration. This follow-up ships the same auth hardening through a new migration, which is the only way to patch existing databases without breaking fresh migration replay.

Business Impact (AI generated)

This closes an API-key-expiration bypass in a user/org lookup RPC while keeping deployment safe. It reduces stale-key exposure and avoids rollout regressions caused by historical-migration drift.

Test Plan (AI generated)

  • git diff --check
  • Added pgTAP regression cases for expired and valid API keys in get_user_org_ids()
  • Run bun run supabase:db:reset
  • Run bunx supabase test db

Generated with AI

Summary by CodeRabbit

  • New Features

    • Enforced API-key expiry checks and stricter access controls so integrations and users only access permitted organizations.
    • Consolidated organization membership resolution so visible org lists reflect all relevant memberships and scope restrictions.
  • Tests

    • Added tests for API-key expiry and org-membership retrieval to ensure correct behavior for expired vs valid keys.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82410c9a-9237-4a4b-bd0c-959bbb59d837

📥 Commits

Reviewing files that changed from the base of the PR and between b0a592f and 6ce63fb.

📒 Files selected for processing (1)
  • supabase/migrations/20260317021715_fix_get_user_org_ids_apikey_expiry.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • supabase/migrations/20260317021715_fix_get_user_org_ids_apikey_expiry.sql

📝 Walkthrough

Walkthrough

Adds a new SECURITY DEFINER PostgreSQL function public.get_user_org_ids() that returns organization UUIDs for the authenticated principal or API-key bearer, validates API key expiry, logs denial events, consolidates org IDs from RBAC, groups, apps, channels, and legacy org_users, and restricts results when an API key limits org access. Corresponding tests for expired and valid API keys were added.

Changes

Cohort / File(s) Summary
Migration: get_user_org_ids function
supabase/migrations/20260317021715_fix_get_user_org_ids_apikey_expiry.sql
Adds new SECURITY DEFINER function public.get_user_org_ids() returning TABLE("org_id" uuid). Authenticates via request header API key or current identity/session, checks API key expiry, logs denial, aggregates org IDs from role_bindings, group memberships, apps, channels, and legacy org_users, applies API-key org restrictions, and updates privileges/comments.
Tests: API key expiration validation
supabase/tests/42_test_apikey_expiration.sql
Increases test plan and adds tests verifying get_user_org_ids() behavior for expired and valid API keys via header setup and assertions (throws_ok / ok).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DB as Postgres (public.get_user_org_ids)
  participant ApikeyTbl as apikeys table
  participant RBAC as role_bindings / groups / apps / channels / org_users
  participant Audit as denial_logging

  Client->>DB: CALL public.get_user_org_ids() (with or without X-Client-Api-Key)
  DB->>ApikeyTbl: lookup API key (hashed/plain)
  alt API key found
    ApikeyTbl-->>DB: apikey record (includes expiry, limited_orgs)
    DB->>DB: validate expiry (is_apikey_expired)
    alt expired
      DB->>Audit: INSERT denial event
      DB-->>Client: return no orgs / error (per logic)
    else valid
      DB->>RBAC: collect org_ids from role_bindings, groups, apps, channels, org_users
      DB-->>DB: union all orgs, apply limited_orgs filter if present
      DB-->>Client: return setof org_id
    end
  else no API key
    DB->>RBAC: collect org_ids for current identity/session
    DB-->>Client: return setof org_id
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
I hop through rows of org UUIDs bright,
Checking keys by day and night,
Expiry noted, denials logged with care,
I gather memberships from everywhere,
A tiny rabbit guarding access fair.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(db): enforce API key expiry in get_user_org_ids' directly and specifically describes the main change—adding API key expiry enforcement to the get_user_org_ids function via a new migration.
Description check ✅ Passed The PR description includes a comprehensive Summary section with all key changes, clear Motivation explaining the rationale, Business Impact details, and a Test Plan with most items checked. However, the description does not fully match the required template structure with explicit Summary, Test plan, Screenshots, and Checklist sections as specified in the repository template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-get-user-org-ids-apikey-expiry
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 SQLFluff (4.0.4)
supabase/migrations/20260317021715_fix_get_user_org_ids_apikey_expiry.sql

User Error: No dialect was specified. You must configure a dialect or specify one on the command line using --dialect after the command. Available dialects:
ansi, athena, bigquery, clickhouse, databricks, db2, doris, duckdb, exasol, flink, greenplum, hive, impala, mariadb, materialize, mysql, oracle, postgres, redshift, snowflake, soql, sparksql, sqlite, starrocks, teradata, trino, tsql, vertica


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
supabase/tests/42_test_apikey_expiration.sql (1)

416-444: Strengthen the happy-path regression for get_user_org_ids().

count(*) > 0 only proves the RPC returned something. A regression that returns the wrong org set, or breaks the no-header authenticated fallback introduced by the new control flow, would still pass. Please assert the expected org IDs/count for the seeded user and add one session-authenticated case with no capgkey header.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/tests/42_test_apikey_expiration.sql` around lines 416 - 444, The
happy-path assertion for get_user_org_ids() is too weak—replace the loose check
(count(*) > 0) with assertions that verify the exact expected org IDs and/or
exact count for the seeded user when using the valid API key
'test-key-orgs-valid' (e.g., compare the result set or join to an expected
array), and add an additional test case that clears the capgkey header
(set_config('request.headers','{}', true) or omit capgkey) to exercise the
session-authenticated fallback path and assert the same expected org IDs/count;
target the test blocks that call get_user_org_ids() and the set_config calls for
'test-key-orgs-valid'/'test-key-orgs-expired' so you update those SELECT ...
ok(...) checks to perform precise equality checks against the expected org id
list or count.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260317021715_fix_get_user_org_ids_apikey_expiry.sql`:
- Around line 1-4: The migration creates/updates the SECURITY DEFINER function
get_user_org_ids but does not make its ACL explicit; modify the migration to
harden the function ACL by first revoking all privileges from PUBLIC for the
specific function signature (the get_user_org_ids() overload) and then granting
only the minimal required role(s) (e.g., the application role and/or specific
admin role) the EXECUTE privilege; ensure the REVOKE ALL and GRANT EXECUTE
statements reference the exact function name and signature (get_user_org_ids())
and are executed after the CREATE OR REPLACE FUNCTION to enforce consistent
permissions across all instances.

---

Nitpick comments:
In `@supabase/tests/42_test_apikey_expiration.sql`:
- Around line 416-444: The happy-path assertion for get_user_org_ids() is too
weak—replace the loose check (count(*) > 0) with assertions that verify the
exact expected org IDs and/or exact count for the seeded user when using the
valid API key 'test-key-orgs-valid' (e.g., compare the result set or join to an
expected array), and add an additional test case that clears the capgkey header
(set_config('request.headers','{}', true) or omit capgkey) to exercise the
session-authenticated fallback path and assert the same expected org IDs/count;
target the test blocks that call get_user_org_ids() and the set_config calls for
'test-key-orgs-valid'/'test-key-orgs-expired' so you update those SELECT ...
ok(...) checks to perform precise equality checks against the expected org id
list or count.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23e33c6d-6871-4b6e-a53f-b12a9c9bcfbb

📥 Commits

Reviewing files that changed from the base of the PR and between 877c35b and b0a592f.

📒 Files selected for processing (2)
  • supabase/migrations/20260317021715_fix_get_user_org_ids_apikey_expiry.sql
  • supabase/tests/42_test_apikey_expiration.sql

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit cb4eb23 into main Mar 18, 2026
15 checks passed
@riderx riderx deleted the codex/fix-get-user-org-ids-apikey-expiry branch March 18, 2026 19:03
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