Skip to content

enforce org authorization on metrics RPCs#1703

Closed
riderx wants to merge 79 commits intomainfrom
riderx/fix-rpc-metrics-auth
Closed

enforce org authorization on metrics RPCs#1703
riderx wants to merge 79 commits intomainfrom
riderx/fix-rpc-metrics-auth

Conversation

@riderx
Copy link
Member

@riderx riderx commented Feb 26, 2026

Summary (AI generated)

  • Added supabase/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql to enforce organization membership checks on metrics RPC endpoints and prevent anonymous cross-tenant usage telemetry disclosure.
  • Updated get_app_metrics, get_global_metrics, and get_total_metrics execution paths so requests now require valid read rights for the requested org and log denied attempts.

Test plan (AI generated)

  • bun lint:backend
  • Manual API validation: call each RPC with only anon key and verify unauthorized org IDs no longer return cross-tenant rows.

Screenshots (AI generated)

  • Not applicable (backend-only database migration).

Checklist (AI generated)

  • My code follows the code style of this project and passes bun run lint:backend && bun run lint.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • My change has adequate E2E test coverage.
  • I have tested my code manually, and I have provided steps how to reproduce my tests.

Summary by CodeRabbit

  • New Features
    • Added metrics endpoints to retrieve organization performance data by app and across the platform
    • Implemented secure access control with role-based authorization checks
    • Enabled performance optimization through intelligent data caching for faster queries

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

Warning

Rate limit exceeded

@riderx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 29 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between cc2a1dd and a948f99.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (66)
  • .gitsecret/paths/mapping.cfg
  • internal/AuthKey_8P7Y3V99PJ.p8.secret
  • internal/CICD.mobileprovision.secret
  • internal/Certificates.p12.secret
  • internal/Certificates_p12.p12.secret
  • internal/capgo-394818-68ad1517d330.json.secret
  • internal/cloudflare/.env.local.secret
  • internal/cloudflare/.env.preprod.secret
  • internal/cloudflare/.env.prod.secret
  • internal/forgr-key.jks.base64.secret
  • internal/forgr-key.jks.secret
  • internal/how-to-deploy.md.secret
  • internal/supabase/.env.local.secret
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/hi.json
  • messages/id.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/pl.json
  • messages/pt-br.json
  • messages/ru.json
  • messages/tr.json
  • messages/vi.json
  • messages/zh-cn.json
  • package.json
  • read_replicate/update_readreplica_passwords.sh
  • src/components/dashboard/DemoOnboardingGate.vue
  • src/components/dashboard/DemoOnboardingModal.vue
  • src/modules/auth.ts
  • src/modules/i18n.ts
  • src/pages/delete_account.vue
  • src/pages/resend_email.vue
  • src/pages/settings/organization/Security.vue
  • src/services/versions.ts
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/functions/_backend/public/apikey/post.ts
  • supabase/functions/_backend/public/build/request.ts
  • supabase/functions/_backend/public/build/upload.ts
  • supabase/functions/_backend/public/organization/members/delete.ts
  • supabase/functions/_backend/utils/cloudflare.ts
  • supabase/functions/_backend/utils/supabase.ts
  • supabase/functions/_backend/utils/version.ts
  • supabase/migrations/20250530233128_base.sql
  • supabase/migrations/20260225120000_restrict_webhooks_select_for_admin_only.sql
  • supabase/migrations/20260226090000_require_verified_email_for_delete_user.sql
  • supabase/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql
  • supabase/migrations/20260226153000_restrict_apikey_oracle_rpcs.sql
  • supabase/migrations/20260227000000_fix_rescind_invitation_rpc_access.sql
  • supabase/migrations/20260227000001_secure_record_build_time_rpc.sql
  • supabase/migrations/20260227010000_restrict_upsert_version_meta_exec.sql
  • supabase/migrations/20260227150000_fix_invite_user_to_org_security.sql
  • supabase/migrations/20260228000000_role_bindings_rls_assignable.sql
  • supabase/migrations/20260228000100_delete_member_cascade_bindings.sql
  • supabase/migrations/20260228000200_prevent_last_super_admin_delete.sql
  • supabase/migrations/20260228000300_fix_apikey_hashed_lookup.sql
  • supabase/migrations/20260228172308_fix_prevent_last_super_admin_cascade.sql
  • supabase/migrations/20260228172309_fix_rbac_test_compatibility.sql
  • supabase/tests/28_test_new_migration_functions.sql
  • supabase/tests/34_test_rbac_rls.sql
  • supabase/tests/35_test_is_admin_rbac.sql
  • tests/builder-payload.unit.test.ts
  • tests/organization-api.test.ts
📝 Walkthrough

Walkthrough

Introduces six new public PostgreSQL functions for retrieving organizational metrics with built-in authorization controls and caching. Functions compute per-app metrics, global aggregated metrics, and total metrics across specified date ranges, validating access permissions and leveraging cache mechanisms to optimize queries.

Changes

Cohort / File(s) Summary
Metrics RPC Functions
supabase/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql
Adds six overloaded public functions: get_app_metrics (2 versions), get_global_metrics (2 versions), and get_total_metrics (2 versions). Implements authorization checks (service_role bypass, permission validation), org existence verification, cache management (app_metrics_cache, org_metrics_cache with TTL logic), and conditional cache seeding. Includes request denial logging with JSONB context for audit purposes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Auth as Authorization
    participant DB as Database
    participant Cache as Metrics Cache
    participant Compute as Computation

    Client->>Auth: Request metrics (org_id, date range)
    alt Service Role
        Auth-->>DB: Allow (bypass)
    else Regular User
        Auth->>DB: Check permissions & org
        DB-->>Auth: Validate rights
        alt Unauthorized
            Auth-->>Client: Deny (log event)
        else Authorized
            Auth-->>DB: Proceed
        end
    end

    DB->>Cache: Check cache validity
    alt Cache Valid & Recent
        Cache-->>Client: Return cached metrics
    else Cache Stale or Missing
        DB->>Compute: Seed metrics
        Compute->>Cache: Store results
        Cache-->>Client: Return metrics
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

💰 Rewarded

Poem

🐰 A rabbit hops through metrics bright,
With authorization checks held tight,
Caching bounds the queries fast,
Access controlled, permissions asked,
Metrics bloom in organized light!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'enforce org authorization on metrics RPCs' accurately describes the main change: adding authorization checks to metrics RPC endpoints.
Description check ✅ Passed The description covers the summary, test plan, and checklist sections from the template but most checklist items remain unchecked, indicating incomplete verification.
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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/fix-rpc-metrics-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
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/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql (1)

1-3: Consider adding explicit GRANT statements to document access intent.

The migration creates public functions without explicit GRANT or REVOKE statements. While PostgreSQL defaults grant EXECUTE to PUBLIC for new functions and the authorization is enforced internally, explicit grants improve clarity and make the security model auditable.

📝 Example: Explicit grants at end of migration
-- Explicitly grant to authenticated and anon for RPC access
GRANT EXECUTE ON FUNCTION public.get_app_metrics(uuid) TO authenticated, anon;
GRANT EXECUTE ON FUNCTION public.get_app_metrics(uuid, date, date) TO authenticated, anon;
GRANT EXECUTE ON FUNCTION public.get_global_metrics(uuid) TO authenticated, anon;
GRANT EXECUTE ON FUNCTION public.get_global_metrics(uuid, date, date) TO authenticated, anon;
GRANT EXECUTE ON FUNCTION public.get_total_metrics(uuid) TO authenticated, anon;
GRANT EXECUTE ON FUNCTION public.get_total_metrics(uuid, date, date) TO authenticated, anon;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql`
around lines 1 - 3, Add explicit GRANT/REVOKE statements for the created RPC
functions to document and lock down access intent: for each function created
(get_app_metrics(uuid), get_app_metrics(uuid, date, date),
get_global_metrics(uuid), get_global_metrics(uuid, date, date),
get_total_metrics(uuid), get_total_metrics(uuid, date, date)) add GRANT EXECUTE
ON FUNCTION ... TO authenticated, anon (or the appropriate roles) at the end of
the migration and consider REVOKE EXECUTE ON FUNCTION ... FROM PUBLIC if you
want to remove the default public execute privilege so the authorization model
is auditable and explicit.
🤖 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/20260226100000_fix_metrics_rpcs_org_authorization.sql`:
- Around line 173-208: Remove the redundant authorization block that duplicates
get_app_metrics' checks: delete the v_is_service_role/v_user_id check and the IF
NOT v_is_service_role ... END IF; block (including the calls to
public.get_identity, public.check_min_rights, public.pg_log and the early
RETURN) so this function simply calls public.get_app_metrics and returns its
aggregated results; keep the initial v_is_service_role assignment only if used
elsewhere, otherwise remove its declaration too.

---

Nitpick comments:
In `@supabase/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql`:
- Around line 1-3: Add explicit GRANT/REVOKE statements for the created RPC
functions to document and lock down access intent: for each function created
(get_app_metrics(uuid), get_app_metrics(uuid, date, date),
get_global_metrics(uuid), get_global_metrics(uuid, date, date),
get_total_metrics(uuid), get_total_metrics(uuid, date, date)) add GRANT EXECUTE
ON FUNCTION ... TO authenticated, anon (or the appropriate roles) at the end of
the migration and consider REVOKE EXECUTE ON FUNCTION ... FROM PUBLIC if you
want to remove the default public execute privilege so the authorization model
is auditable and explicit.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab763b and cc2a1dd.

📒 Files selected for processing (1)
  • supabase/migrations/20260226100000_fix_metrics_rpcs_org_authorization.sql

WcaleNieWolny and others added 27 commits February 26, 2026 12:01
Builder availability errors (not configured, call failed, error response,
missing upload URL) are transient server-side failures, not client errors.
Returning 503 allows the CLI retry logic to automatically retry these
requests instead of treating them as terminal 400 errors.
fix: return 503 for service_unavailable build errors
fix: correct vue/html-indent in DemoOnboardingModal
Extract buildBuilderPayload() from the inline fetch body so the
snake_case → camelCase mapping and exact key set can be tested.
6 vitest cases verify: camelCase output, no legacy credentials field,
correct metadata keys, and pass-through of contents.
Add unit tests for builder payload shape
Old CLI clients sending the flat `credentials` field would have it
silently dropped, causing confusing builder failures. Now the proxy
explicitly rejects non-empty `credentials` with a migration message
pointing to `build_credentials`.
…t SQL injection

The appId parameter was directly interpolated into the D1 SQL query string,
creating a SQL injection vulnerability. Switched to bound parameter via .bind().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dalanir and others added 28 commits March 2, 2026 14:11
fix(security): RBAC security audit fixes
* fix(auth): block account deletion for unverified users

* fix(auth): refresh session fields for email verification gate

* fix(auth): make delete_user insert idempotent

* fix(auth): explain blocked delete/settings when email unverified

* fix(auth): block delete action when email is unverified

* fix(auth): localize resend email block and make delete_user idempotent
* fix(db): restrict invite_user_to_org public rpc

* fix(db): use caller identity in invite 2FA check
* fix(db): secure record_build_time rpc writes

* fix(db): preserve service-role record_build_time path
* fix(api): preserve channel owner on channel upsert

* fix(auth): block sensitive account actions for unverified users (#1690)

* fix(auth): block account deletion for unverified users

* fix(auth): refresh session fields for email verification gate

* fix(auth): make delete_user insert idempotent

* fix(auth): explain blocked delete/settings when email unverified

* fix(auth): block delete action when email is unverified

* fix(auth): localize resend email block and make delete_user idempotent

* Restrict invite_user_to_org RPC to authenticated callers (#1710)

* fix(db): restrict invite_user_to_org public rpc

* fix(db): use caller identity in invite 2FA check

* fix(security): restrict webhook select to admin users (#1705)

* Secure record_build_time RPC for authorized callers (#1711)

* fix(db): secure record_build_time rpc writes

* fix(db): preserve service-role record_build_time path

* fix(api): preserve channel owner on channel upsert
* fix(security): restrict apikey oracle rpc access

* fix: webapp url

* fix: fix

* chore(release): 12.116.9

* fix: envs

* Revert "Merge pull request #1707 from Cap-go/fix_webapp_url"

This reverts commit ff20d1a.

* fix: typo

* chore(release): 12.116.10

* fix(security): restrict apikey oracle rpc access

* fix: return 503 instead of 400 for service_unavailable build errors

Builder availability errors (not configured, call failed, error response,
missing upload URL) are transient server-side failures, not client errors.
Returning 503 allows the CLI retry logic to automatically retry these
requests instead of treating them as terminal 400 errors.

* chore(release): 12.116.11

* fix: update PWD script

* fix: env vars

* fix: modal responsive

* feat: forward buildOptions + buildCredentials to builder (pass-through)

* fix: correct vue/html-indent in DemoOnboardingModal

* fix: use snake_case (build_options, build_credentials) in public API, map to camelCase for builder

* fix(security): sanitize SQL interpolation in Cloudflare Analytics Engine queries (#1702)

* chore(release): 12.116.12

* Add unit tests for builder payload shape contract

Extract buildBuilderPayload() from the inline fetch body so the
snake_case → camelCase mapping and exact key set can be tested.
6 vitest cases verify: camelCase output, no legacy credentials field,
correct metadata keys, and pass-through of contents.

* Reject deprecated `credentials` field with clear upgrade error

Old CLI clients sending the flat `credentials` field would have it
silently dropped, causing confusing builder failures. Now the proxy
explicitly rejects non-empty `credentials` with a migration message
pointing to `build_credentials`.

* fix(security): clean up role_bindings on member removal (#1722)

* chore(release): 12.116.13

* fix(security): use parameterized query in getStoreAppByIdCF to prevent SQL injection

The appId parameter was directly interpolated into the D1 SQL query string,
creating a SQL injection vulnerability. Switched to bound parameter via .bind().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): prevent privilege escalation in role_bindings endpoint

Add priority_rank check so callers cannot assign or update roles with
higher privileges than their own. Without this, any user with
org.update_user_roles could escalate to org_super_admin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): enforce is_assignable in role_bindings INSERT RLS policy

Direct PostgREST inserts could bypass the endpoint's is_assignable check
and assign non-assignable roles (e.g. platform_super_admin). The RLS
INSERT policy now requires the target role to have is_assignable = true.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): cascade all role bindings on member removal

delete_org_member_role previously only deleted the org-level binding,
leaving orphaned app/channel bindings. A removed member could retain
app-level access. Now deletes all bindings for the user in the org.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): add trigger to prevent deleting last super_admin binding

Direct PostgREST DELETEs on role_bindings could bypass the last
super_admin guards in delete_org_member_role. A BEFORE DELETE trigger
now rejects deletion of the last org_super_admin binding in any org.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): support hashed API keys in rbac_check_permission_direct

The RBAC path in rbac_check_permission_direct looked up API keys with
WHERE key = p_apikey, which silently failed for hashed keys. Switched
to find_apikey_by_value() which handles both plain-text and hashed keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: reword comment to pass typos CI check

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove unused desc import from role_bindings.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): add FOR UPDATE lock to prevent write-skew on last super_admin delete

Two concurrent DELETE transactions could both pass the count check and
both delete their rows, leaving zero super_admins. A SELECT ... FOR
UPDATE on the super_admin binding set now serializes concurrent deletes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent API key privilege escalation and fix organization member deletion test

- Add validation to prevent limited API keys from creating unlimited keys
- Fix organization-api test to work with sync_org_user_to_role_binding trigger
- Change test user_right from 'invite_read' to 'read' (trigger-compatible)
- Verify trigger-created role_bindings instead of manually inserting them

* fix: allow CASCADE deletions in prevent_last_super_admin_binding_delete and fix RBAC test compatibility

- Add org existence check in trigger to allow CASCADE deletions when org is being deleted
- Add service_role bypass for administrative operations and tests
- Update tests to work with RBAC security constraints:
  - 34_test_rbac_rls.sql: Remove DELETE operation that violated super_admin protection
  - 35_test_is_admin_rbac.sql: Use service_role for test setup INSERT
- All SQL database tests now pass (860 tests)
- Backend tests remain passing (68 tests)

* fix(security): make getCallerMaxPriorityRank auth-type-aware and remove API key data leak

* chore(release): 12.116.14

* fix(security): correct API key RBAC principal mapping and remove service_role bypass

* fix(security): correct RBAC migration comments and add privilege check on delete

- Update migration comments to accurately reflect that service_role is NOT exempt

  from the last super_admin protection trigger

- Replace FOR UPDATE scan with pg_advisory_xact_lock to avoid cross-transaction deadlocks

- Add privilege-rank check in delete handler to prevent deleting higher-ranked role bindings

- Aligns with established advisory lock patterns in codebase

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: add self-2fa-required message for 2FA enforcement in multiple languages

* chore(release): 12.116.15

* fix(frontend): validate 2fa before enabling org enforcement (#1729)

* chore(release): 12.116.16

* fix(deps): update vue monorepo to v3.5.29 (#1731)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(release): 12.116.17

* chore: remove unused cloudflare function getStoreAppByIdCF

* chore(release): 12.116.18

* chore: stop editing immutable base migration

* fix(frontend): disable auto demo onboarding modal (#1733)

* chore(release): 12.116.19

* fix(auth): block sensitive account actions for unverified users (#1690)

* fix(auth): block account deletion for unverified users

* fix(auth): refresh session fields for email verification gate

* fix(auth): make delete_user insert idempotent

* fix(auth): explain blocked delete/settings when email unverified

* fix(auth): block delete action when email is unverified

* fix(auth): localize resend email block and make delete_user idempotent

* Restrict invite_user_to_org RPC to authenticated callers (#1710)

* fix(db): restrict invite_user_to_org public rpc

* fix(db): use caller identity in invite 2FA check

* fix(security): restrict webhook select to admin users (#1705)

* Secure record_build_time RPC for authorized callers (#1711)

* fix(db): secure record_build_time rpc writes

* fix(db): preserve service-role record_build_time path

* fix(security): restrict apikey oracle rpc access

* chore: stop editing immutable base migration

* fix(security): restrict apikey oracle rpc access

* chore(release): 12.116.20

* fix(security): restrict apikey oracle rpc access

* chore: stop editing immutable base migration

* fix(security): restrict apikey oracle rpc access

* chore: stop editing immutable base migration

* fix(security): restrict apikey oracle rpc access

---------

Co-authored-by: WcaleNieWolny <isupermichael007@gmail.com>
Co-authored-by: WcaleNieWolny <50914789+WcaleNieWolny@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: LOLO <131777939+artylobos@users.noreply.github.com>
Co-authored-by: Jordan Lorho <jordan.lorho@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* fix(db): restrict rescind_invitation rpc access

* test(db): cover rescind_invitation permission edges
* fix(db): restrict upsert_version_meta rpc writes

* fix(db): simplify upsert_version_meta service-role guard

* fix(db): remove runtime checks from upsert_version_meta

* fix(db): restrict upsert_version_meta execute privileges

* fix(supabase): keep upsert_version_meta privilege-only

* fix(db): rename upsert_version_meta migration to unique version
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 3, 2026

@riderx riderx closed this Mar 3, 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.

4 participants