Skip to content

feat: add Supabase Edge Function secrets sync - #1014

Open
rohan-chaturvedi wants to merge 1 commit into
mainfrom
feat--supabase-secret-sync
Open

feat: add Supabase Edge Function secrets sync#1014
rohan-chaturvedi wants to merge 1 commit into
mainfrom
feat--supabase-secret-sync

Conversation

@rohan-chaturvedi

Copy link
Copy Markdown
Member

🔍 Overview

Phase currently supports syncing secrets to 11 third-party services (GitHub Actions, Railway, Vercel, Render, Cloudflare, etc.), but there is no integration for Supabase. Supabase Edge Functions consume secrets set on a project via the Supabase Management API, and teams using Phase as their source of truth currently have to copy secrets over manually or script the supabase CLI.

This PR adds Supabase Edge Function secrets as a first-class sync integration, following the same provider pattern as the existing token-credential integrations (Cloudflare Workers / Railway).

Fixes #1013

💡 Proposed Changes

Backend

  • New supabase provider (single access_token credential — a Supabase personal access token) and supabase_edge_functions service in the provider registry (api/services.py)
  • New sync module api/utils/syncing/supabase/main.py:
    • list_supabase_projects — lists projects via GET /v1/projects, sourcing the project ref from the documented ref field (with fallback to the deprecated id)
    • sync_supabase_secrets — authoritative sync: fetches remote secret names, deletes remote secrets no longer present in Phase, then bulk-upserts all Phase secrets via POST /v1/projects/{ref}/secrets
    • Writes are batched at 100 secrets per request (the Management API caps bulk creation at 100 items — same batching the official Supabase CLI does)
    • Reserved SUPABASE_-prefixed names are never deleted remotely, and SUPABASE_-prefixed Phase keys are skipped on push (the API rejects them); skipped keys are reported in the sync event message
  • RQ job perform_supabase_sync + SERVICE_DISPATCH entry (api/tasks/syncing.py)
  • GraphQL: supabaseProjects(credentialId) query (gated on IntegrationCredentials:read + provider check) and createSupabaseSync mutation with the same credential-org, sse_enabled, app-access, and duplicate-sync checks as sibling providers; sync options store project_ref + project_name
  • Migration 0140_supabase_sync_choices — validation-only AlterField pair for the new provider/service choices (no DB schema change)

Frontend

  • CreateSupabaseSync setup component (credential → Phase env/path → project combobox showing name, ref and region), wired into CreateSyncDialog
  • ProviderIcon (Supabase logo), ServiceInfo (project name + ref on sync cards)
  • New GraphQL documents under graphql/{queries,mutations}/syncing/supabase/, regenerated schema + codegen types
  • Credential creation uses the existing generic form (renders a masked ACCESS TOKEN field automatically)

🖼️ Screenshots or Demo

📝 Release Notes

  • New integration: sync secrets from any Phase environment to Supabase Edge Function secrets. Create a credential with a Supabase personal access token, pick a project, and Phase keeps the project's secrets in sync — including removing secrets deleted in Phase. Requires SSE (server-side encryption) on the app, like all sync integrations.

❓ Open Questions

  • The docs link for the Supabase provider currently falls back to the generic integrations page — a docs.phase.dev/integrations/platforms/supabase page needs to land in phasehq/docs alongside this.
  • Multi-batch writes (>100 secrets) are not atomic: a mid-sequence failure can leave the Supabase project partially updated until the next successful sync. This matches the official CLI's behavior; flagging in case we want retry semantics later.

🧪 Testing

  • 11 new unit tests in backend/tests/utils/syncing/test_supabase.py covering project listing (success / bad credentials / API error / ref fallback) and sync semantics (stale deletion, reserved-prefix skipping on both delete and push, 100-item batching, and error paths for fetch/delete/create)
  • Full backend suite passes (1574 passed; the 1 failure is the known root-user test_file_read_permission_error baseline)
  • Frontend: tsc --noEmit clean vs the main baseline, ESLint clean on all touched components, jest fully green
  • Sync semantics (endpoints, payload shapes, 100-item cap, reserved prefix, upsert-via-POST) verified against the live Management API OpenAPI spec and the official Supabase CLI implementation
  • Manually tested end-to-end against a real Supabase project

🎯 Reviewer Focus

  • backend/api/utils/syncing/supabase/main.py — the delete-then-upsert semantics and the reserved-prefix handling
  • CreateSupabaseSync mutation in backend/backend/graphene/mutations/syncing.py — authz checks mirror CreateCloudflareWorkersSync / CreateRailwaySync
  • frontend/components/syncing/Supabase/CreateSupabaseSync.tsx — mirrors CreateCloudflareWorkersSync

➕ Additional Context

  • The regenerated frontend/apollo/schema.graphql includes a few non-Supabase docstring hunks (email-change mutations): the checked-in schema was stale relative to main's backend, and this export refreshes it.
  • Secrets are synced with references resolved and personal overrides excluded, via the shared get_environment_secrets path used by all providers.

✨ How to Test the Changes Locally

  1. Check out the branch, rebuild/restart the dev stack, and run migrations (python manage.py migrate in the backend container)
  2. Create a Supabase personal access token at https://supabase.com/dashboard/account/tokens
  3. In the console: Integrations → Add credentials → Supabase, paste the token
  4. In an app (with SSE enabled or accept the prompt): Syncing → Supabase Edge Functions, pick the credential, a Phase environment/path, and a Supabase project
  5. Verify secrets land with supabase secrets list --project-ref <ref> (or Dashboard → Edge Functions → Secrets); delete a secret in Phase, redeploy, and confirm it's removed remotely
  6. Confirm SUPABASE_URL etc. are untouched, and a Phase key named SUPABASE_X is skipped with a note in the sync event message

💚 Did You...

  • Ensure linting passes (code style checks)?
  • Update dependencies and lockfiles (if required) — n/a, no new dependencies
  • Update migrations (if required)
  • Regenerate graphql schema and types (if required)
  • Verify the app builds locally?
  • Manually test the changes on different browsers/devices?

@nimish-ks nimish-ks self-assigned this Sep 9, 2026
@nimish-ks
nimish-ks self-requested a review September 9, 2026 07:20
@rohan-chaturvedi
rohan-chaturvedi marked this pull request as ready for review September 9, 2026 09: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.

Add support for syncing to Supabase projects

2 participants