From c347f8159e890c8010de63a22b7fc5905f6d123c Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 11 Nov 2026 11:22:40 +0100 Subject: [PATCH 1/4] test: run the auth and postgrest tests on the supabase cli The auth tests ran auth v2.187.0 and the postgrest tests ran PostgREST v11.2.2 on Postgres 15 from docker compose files that had to be bumped by hand. Both now start through the Supabase CLI, like storage and realtime, so they follow the service versions the CLI ships. The auth tests used four auth servers with different configs. They now use a single one: the unconfirmed user comes from the admin API, the symmetric get_claims test signs its own HS256 token, and the server signs with the RSA test key so the JWKS tests keep working. Removes the broken sync-infra target and its gh-download script. SDK-2973 --- .github/workflows/ci.yml | 11 +- README.md | 3 +- infra.mk | 2 - src/auth/Makefile | 19 +-- src/auth/infra/db/00-schema.sql | 85 ---------- src/auth/infra/docker-compose.yml | 156 ------------------ src/auth/infra/supabase/.gitignore | 4 + src/auth/infra/supabase/config.toml | 91 ++++++++++ src/auth/infra/supabase/signing_keys.json | 19 +++ src/auth/pyproject.toml | 3 - src/auth/scripts/gh-download.py | 126 -------------- src/auth/tests/_async/conftest.py | 148 +---------------- src/auth/tests/_async/test_gotrue.py | 42 ++--- .../tests/_async/test_gotrue_admin_api.py | 24 +-- src/auth/tests/_sync/conftest.py | 148 +---------------- src/auth/tests/_sync/test_gotrue.py | 42 ++--- src/auth/tests/_sync/test_gotrue_admin_api.py | 24 +-- src/postgrest/Makefile | 16 +- src/postgrest/README.md | 6 +- src/postgrest/infra/docker-compose.yaml | 28 ---- src/postgrest/infra/supabase/.gitignore | 4 + src/postgrest/infra/supabase/config.toml | 37 +++++ .../migrations/20240101000000_init.sql} | 6 + src/postgrest/tests/_async/conftest.py | 2 +- src/postgrest/tests/_sync/conftest.py | 2 +- 25 files changed, 251 insertions(+), 797 deletions(-) delete mode 100644 src/auth/infra/db/00-schema.sql delete mode 100644 src/auth/infra/docker-compose.yml create mode 100644 src/auth/infra/supabase/.gitignore create mode 100644 src/auth/infra/supabase/config.toml create mode 100644 src/auth/infra/supabase/signing_keys.json delete mode 100644 src/auth/scripts/gh-download.py delete mode 100644 src/postgrest/infra/docker-compose.yaml create mode 100644 src/postgrest/infra/supabase/.gitignore create mode 100644 src/postgrest/infra/supabase/config.toml rename src/postgrest/infra/{init.sql => supabase/migrations/20240101000000_init.sql} (93%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 028519bf4..35536d25e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,8 +121,7 @@ jobs: timeout-minutes: 20 env: UV_RESOLUTION: ${{ matrix.resolution }} - USES_SUPABASE_CLI: ${{ contains(fromJSON('["realtime", "storage"]'), matrix.package) }} - USES_INFRA: ${{ contains(fromJSON('["realtime", "storage", "auth", "postgrest"]'), matrix.package) }} + USES_SUPABASE_CLI: ${{ contains(fromJSON('["realtime", "storage", "auth", "postgrest"]'), matrix.package) }} steps: - name: Clone Repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -150,17 +149,17 @@ jobs: # fails the container start with "address already in use". - name: Reserve infra ports if: ${{ env.USES_SUPABASE_CLI == 'true' }} - run: sudo sysctl -w net.ipv4.ip_local_reserved_ports=54320-54329,55320-55329 + run: sudo sysctl -w net.ipv4.ip_local_reserved_ports=54320-54329,55320-55329,56320-56329,57320-57329 # Pulling images from the registries on every job gets throttled # (429 / "Data limit exceeded"), so reuse the images between runs. - name: Cache docker images - if: ${{ env.USES_INFRA == 'true' }} + if: ${{ env.USES_SUPABASE_CLI == 'true' }} id: docker-images uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: /tmp/docker-images - key: docker-images-${{ matrix.package }}-${{ steps.supabase-cli.outputs.version || 'compose' }}-${{ hashFiles(format('src/{0}/infra/**', matrix.package)) }} + key: docker-images-${{ matrix.package }}-${{ steps.supabase-cli.outputs.version }}-${{ hashFiles(format('src/{0}/infra/**', matrix.package)) }} - name: Load cached docker images if: ${{ steps.docker-images.outputs.cache-hit == 'true' }} @@ -177,7 +176,7 @@ jobs: # On a cache miss cache-hit is an empty string, not 'false'. - name: Save docker images to cache - if: ${{ env.USES_INFRA == 'true' && steps.docker-images.outputs.cache-hit != 'true' }} + if: ${{ env.USES_SUPABASE_CLI == 'true' && steps.docker-images.outputs.cache-hit != 'true' }} run: | mkdir -p /tmp/docker-images docker ps --all --format '{{.Image}}' | sort -u \ diff --git a/README.md b/README.md index 0b6545a5f..53bcfa183 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,7 @@ cd supabase-py This repository relies on the following dependencies for development: - `uv` for python project management. - `make` for running project commands. -- `docker` for both `postgrest` and `auth` test containers. -- `supabase-cli` for both `storage` and `realtime` test containers. +- `supabase-cli` and `docker` for the `auth`, `postgrest`, `storage` and `realtime` test containers. All of these dependencies are included in the nix shell environment, through `flake.nix`. If you've got `nix` installed, you may prefer to use it through `nix develop`. diff --git a/infra.mk b/infra.mk index 895cfae1d..5975be70d 100644 --- a/infra.mk +++ b/infra.mk @@ -25,5 +25,3 @@ endef # $(call supabase_cleanup,) supabase_cleanup = supabase --workdir infra stop --no-backup >/dev/null 2>&1 || true; \ docker ps -aq --filter "label=com.supabase.cli.project=$(1)" | xargs docker rm -f >/dev/null 2>&1 || true - -compose_cleanup = (cd infra && docker compose down --remove-orphans >/dev/null 2>&1) || true diff --git a/src/auth/Makefile b/src/auth/Makefile index 183728cd8..bec01633f 100644 --- a/src/auth/Makefile +++ b/src/auth/Makefile @@ -16,29 +16,18 @@ help:: include ../../infra.mk +AUTH_EXCLUDED_SERVICES := studio,postgrest,realtime,storage-api,imgproxy,edge-runtime,logflare,vector,supavisor,postgres-meta + start-infra: - @$(call retry_start_infra,$(compose_cleanup),(cd infra && docker compose up -d)) + @$(call retry_start_infra,$(call supabase_cleanup,auth-py-tests),supabase --workdir infra start -x $(AUTH_EXCLUDED_SERVICES)) help:: @echo " start-infra -- start containers for tests" -clean-infra: - cd infra &&\ - docker compose down --remove-orphans &&\ - docker system prune -a --volumes -f -help:: - @echo " clean-infra -- delete all stored information about the containers" - stop-infra: - cd infra &&\ - docker compose down --remove-orphans + supabase --workdir infra stop help:: @echo " stop-infra -- stop containers for tests" -sync-infra: - uv run --package supabase_auth scripts/gh-download.py --repo=supabase/gotrue-js --branch=master --folder=infra -help:: - @echo " sync-infra -- update locked versions for test containers" - build-sync: uv run --package supabase_auth scripts/run-unasync.py help:: diff --git a/src/auth/infra/db/00-schema.sql b/src/auth/infra/db/00-schema.sql deleted file mode 100644 index 229af46f5..000000000 --- a/src/auth/infra/db/00-schema.sql +++ /dev/null @@ -1,85 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION postgres; --- auth.users definition -CREATE TABLE auth.users ( - instance_id uuid NULL, - id uuid NOT NULL, - aud varchar(255) NULL, - "role" varchar(255) NULL, - email varchar(255) NULL, - encrypted_password varchar(255) NULL, - confirmed_at timestamptz NULL, - invited_at timestamptz NULL, - confirmation_token varchar(255) NULL, - confirmation_sent_at timestamptz NULL, - recovery_token varchar(255) NULL, - recovery_sent_at timestamptz NULL, - email_change_token varchar(255) NULL, - email_change varchar(255) NULL, - email_change_sent_at timestamptz NULL, - last_sign_in_at timestamptz NULL, - raw_app_meta_data jsonb NULL, - raw_user_meta_data jsonb NULL, - is_super_admin bool NULL, - created_at timestamptz NULL, - updated_at timestamptz NULL, - CONSTRAINT users_pkey PRIMARY KEY (id) -); -CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, email); -CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id); --- auth.refresh_tokens definition -CREATE TABLE auth.refresh_tokens ( - instance_id uuid NULL, - id bigserial NOT NULL, - "token" varchar(255) NULL, - user_id varchar(255) NULL, - revoked bool NULL, - created_at timestamptz NULL, - updated_at timestamptz NULL, - CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id) -); -CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id); -CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id); -CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token); --- auth.instances definition -CREATE TABLE auth.instances ( - id uuid NOT NULL, - uuid uuid NULL, - raw_base_config text NULL, - created_at timestamptz NULL, - updated_at timestamptz NULL, - CONSTRAINT instances_pkey PRIMARY KEY (id) -); --- auth.audit_log_entries definition -CREATE TABLE auth.audit_log_entries ( - instance_id uuid NULL, - id uuid NOT NULL, - payload json NULL, - created_at timestamptz NULL, - CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id) -); -CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id); --- auth.schema_migrations definition -CREATE TABLE auth.schema_migrations ( - "version" varchar(255) NOT NULL, - CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version") -); -INSERT INTO auth.schema_migrations (version) -VALUES ('20171026211738'), - ('20171026211808'), - ('20171026211834'), - ('20180103212743'), - ('20180108183307'), - ('20180119214651'), - ('20180125194653'); --- Gets the User ID from the request cookie -create or replace function auth.uid() returns uuid as $$ - select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid; -$$ language sql stable; --- Gets the User ID from the request cookie -create or replace function auth.role() returns text as $$ - select nullif(current_setting('request.jwt.claim.role', true), '')::text; -$$ language sql stable; -GRANT ALL PRIVILEGES ON SCHEMA auth TO postgres; -GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO postgres; -GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO postgres; -ALTER USER postgres SET search_path = "auth"; diff --git a/src/auth/infra/docker-compose.yml b/src/auth/infra/docker-compose.yml deleted file mode 100644 index 6d3096dbc..000000000 --- a/src/auth/infra/docker-compose.yml +++ /dev/null @@ -1,156 +0,0 @@ -# docker-compose.yml -name: auth-tests -services: - gotrue: # Signup enabled, autoconfirm off - image: supabase/auth:v2.187.0 - ports: - - '9999:9999' - environment: - GOTRUE_MAILER_URLPATHS_CONFIRMATION: '/verify' - GOTRUE_JWT_SECRET: '37c304f8-51aa-419a-a1af-06154e63707a' - GOTRUE_JWT_EXP: 3600 - GOTRUE_DB_DRIVER: postgres - DB_NAMESPACE: auth - GOTRUE_API_HOST: 0.0.0.0 - PORT: 9999 - GOTRUE_DISABLE_SIGNUP: 'false' - API_EXTERNAL_URL: http://localhost:9999 - GOTRUE_SITE_URL: http://localhost:9999 - GOTRUE_URI_ALLOW_LIST: https://supabase.io/docs - GOTRUE_MAILER_AUTOCONFIRM: 'false' - GOTRUE_LOG_LEVEL: DEBUG - GOTRUE_OPERATOR_TOKEN: super-secret-operator-token - DATABASE_URL: 'postgres://postgres:postgres@db:5432/postgres?sslmode=disable' - GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: 'true' - GOTRUE_EXTERNAL_GOOGLE_ENABLED: 'true' - GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: 53566906701-bmhc1ndue7hild39575gkpimhs06b7ds.apps.googleusercontent.com - GOTRUE_EXTERNAL_GOOGLE_SECRET: Sm3s8RE85rDcS36iMy8YjrpC - GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: http://localhost:9999/callback - GOTRUE_SMTP_HOST: mail - GOTRUE_SMTP_PORT: 2500 - GOTRUE_SMTP_USER: GOTRUE_SMTP_USER - GOTRUE_SMTP_PASS: GOTRUE_SMTP_PASS - GOTRUE_SMTP_ADMIN_EMAIL: admin@email.com - GOTRUE_MAILER_SUBJECTS_CONFIRMATION: 'Please confirm' - GOTRUE_EXTERNAL_PHONE_ENABLED: 'true' - GOTRUE_SMS_PROVIDER: 'twilio' - GOTRUE_SMS_TWILIO_ACCOUNT_SID: '${GOTRUE_SMS_TWILIO_ACCOUNT_SID}' - GOTRUE_SMS_TWILIO_AUTH_TOKEN: '${GOTRUE_SMS_TWILIO_AUTH_TOKEN}' - GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID: '${GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID}' - GOTRUE_SMS_AUTOCONFIRM: 'false' - GOTRUE_COOKIE_KEY: 'sb' - depends_on: - - db - restart: on-failure - autoconfirm: # Signup enabled, autoconfirm on - image: supabase/auth:v2.187.0 - ports: - - '9998:9998' - environment: - GOTRUE_JWT_SECRET: '37c304f8-51aa-419a-a1af-06154e63707a' - GOTRUE_JWT_KEYS: '[{"kty":"oct","k":"Z7-AyPyChGNcQsX16cPBV-pPBo4q-zckDxkq1VZjATo","kid":"12580317-221c-49b6-894a-f4473b8afe39","key_ops":["sign", "verify"],"alg":"HS256"},{"kty":"RSA","n":"y3KQnIXK6wkPQ5m0XWp7z54BNZzXJk4IxXy81zFophdBBqz6u5OCMqWkC6i3WB7rlax4xjmxxyGyYRODooqCQTGahmpXryAAKc3g-gDIAq2MqVwlpmvXDavCVRK4hK7DZ6wK4MHrliSNHCuCkwIH3ofxTxgUwpSkOT58iU1ZOua5E1Y6R_Ozt3gLHha0Xa7a4V23pkP7n0xBvJPzIqiS3MZ4CQ_pz-buXYRgCPQkUJvXFFcuxmyqoYzorwQ1YVBOmH2XMx26RrCIxgj7geo9eVQ9u5qCPpQCGV5biqYMC4_m1kurOGf62URGRzXtmVzrW1PZJAeGoqMz5Fcfr8hiwQ","e":"AQAB","d":"C4XxquvpEmbw9mM-VAwz9w58Aw1fIkxJMuZdy9KAmue2RyqFCRrRxQycvgxQVi1qKpAaRx_9ccn20IjKa-psdkTY-8QKM2EcoUGH_KEOsxghX3ZYq5RwGdYgq7DjwqAjcTvNYe2Z6mcnlvDf9HOo_nG0uUYj5uGEa7meVCiNZUiSVdNGs-vOTUD8yB5pbZ4ute8ebuUzCWGQ3YwSNoWLa-dbECSO7jeobCapdB52MjEwE3_Ii8BWoySeDP-DEFX_5RTM2Zeh81zXAgmOxpZYTkjMsrznyxxBbXn7CdT8WMEXrreGZwIt3Mu6XpsLF5mwmTQ_ZyoM6tJpn5LeAhnCAQ","p":"6xy1skrnlrGUWtZFSHixn_eRA_O3GXKNBE4wziWodGZaFYsmFijZHbuQT0WFqc0epvLHNdNPvubFrVfV-U7ZIarfSSq6qBwBzDrDQS060MvjJIjrI16pKlx2X727FR1ZuwxT27dNg-wRTgKcZqXEalkvFOTEYBlCtw2-vzI0aRs","q":"3YWwOAs4GRZ9eq_fqNujACWJFyUO9QgEDPDOMg0EZhY7WkAlehTxxVXg65spWnfx_0GSc72I5N5qdbY-yDh2Dl7zIxvwnqZaKMJn4PEFkeAfyg62XlJlkHIwOVSj6vLNUDdDmG7bO2k6MyQ59jeuAemIljf9WhALNy8c9R0K3VM","dp":"KJ4LHcQnAjeng5Hk4kJHnXUtjls6VKEfj5DaiaKj2YgdI_-oEsf3ylUu9yLxloYjN4BVvgzFiBtiJzI3exyOEmzsqj1Bhe1guiGkvcvMj2nJ0fP9e1zNKM5UfPHQMjOh3tigXCLst0-_JZT55BnbNuw1YAytiFSU2_755xoLR-U","dq":"dCP7V-bJ6p1X_FLpOGau9wy262OKi_0_4mj-Mk-Q1tUhGRg4jeEdQRDdc6lN7Rilz-ZZGkVs2FGkD0MVd3PisXYmk2m6pfMhoe0K-WxkNy8Ce7Vq99jLVwgHMIenyS6zZjMTRYAZgPSShu2fVe-rU2VVLyz7r5RpzOzuibRIVfE","qi":"i7ND2teiVLkbaAs6rHfo5DiD1nlsORNYnn8Y_FjF6utb5OUljZ6-5WyEDJN9oIUX8o_Il9E6js-z7nhvPfFZHQN7ZWuYI0rO5qmsCDS9jWJ4GR61SgzZuLT7Jpp_KtwjW70x5wZ1Y-GugOP1Wct1YZWHn5YyLhvO6X_vttSmcS0","kid":"638c54b8-28c2-4b12-9598-ba12ef610a29","key_ops":["verify"],"alg":"RS256"}]' - GOTRUE_JWT_EXP: 3600 - GOTRUE_DB_DRIVER: postgres - DB_NAMESPACE: auth - GOTRUE_API_HOST: 0.0.0.0 - PORT: 9998 - GOTRUE_DISABLE_SIGNUP: 'false' - API_EXTERNAL_URL: http://localhost:9998 - GOTRUE_SITE_URL: http://localhost:9998 - GOTRUE_MAILER_AUTOCONFIRM: 'true' - GOTRUE_SMS_AUTOCONFIRM: 'true' - GOTRUE_LOG_LEVEL: DEBUG - GOTRUE_OPERATOR_TOKEN: super-secret-operator-token - DATABASE_URL: 'postgres://postgres:postgres@db:5432/postgres?sslmode=disable' - GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: 'true' - GOTRUE_EXTERNAL_PHONE_ENABLED: 'true' - GOTRUE_SMTP_HOST: mail - GOTRUE_SMTP_PORT: 2500 - GOTRUE_SMTP_USER: GOTRUE_SMTP_USER - GOTRUE_SMTP_PASS: GOTRUE_SMTP_PASS - GOTRUE_SMTP_ADMIN_EMAIL: admin@email.com - GOTRUE_COOKIE_KEY: 'sb' - GOTRUE_OAUTH_SERVER_ENABLED: 'true' - GOTRUE_OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION: 'true' - depends_on: - - db - restart: on-failure - autoconfirm_with_asymmetric_keys: # Signup enabled, autoconfirm on - image: supabase/auth:v2.187.0 - ports: - - '9996:9996' - environment: - GOTRUE_JWT_SECRET: 'Z7-AyPyChGNcQsX16cPBV-pPBo4q-zckDxkq1VZjATo' - GOTRUE_JWT_KEYS: '[{"kty":"oct","k":"Z7-AyPyChGNcQsX16cPBV-pPBo4q-zckDxkq1VZjATo","kid":"12580317-221c-49b6-894a-f4473b8afe39","key_ops":["verify"],"alg":"HS256"},{"kty":"RSA","n":"y3KQnIXK6wkPQ5m0XWp7z54BNZzXJk4IxXy81zFophdBBqz6u5OCMqWkC6i3WB7rlax4xjmxxyGyYRODooqCQTGahmpXryAAKc3g-gDIAq2MqVwlpmvXDavCVRK4hK7DZ6wK4MHrliSNHCuCkwIH3ofxTxgUwpSkOT58iU1ZOua5E1Y6R_Ozt3gLHha0Xa7a4V23pkP7n0xBvJPzIqiS3MZ4CQ_pz-buXYRgCPQkUJvXFFcuxmyqoYzorwQ1YVBOmH2XMx26RrCIxgj7geo9eVQ9u5qCPpQCGV5biqYMC4_m1kurOGf62URGRzXtmVzrW1PZJAeGoqMz5Fcfr8hiwQ","e":"AQAB","d":"C4XxquvpEmbw9mM-VAwz9w58Aw1fIkxJMuZdy9KAmue2RyqFCRrRxQycvgxQVi1qKpAaRx_9ccn20IjKa-psdkTY-8QKM2EcoUGH_KEOsxghX3ZYq5RwGdYgq7DjwqAjcTvNYe2Z6mcnlvDf9HOo_nG0uUYj5uGEa7meVCiNZUiSVdNGs-vOTUD8yB5pbZ4ute8ebuUzCWGQ3YwSNoWLa-dbECSO7jeobCapdB52MjEwE3_Ii8BWoySeDP-DEFX_5RTM2Zeh81zXAgmOxpZYTkjMsrznyxxBbXn7CdT8WMEXrreGZwIt3Mu6XpsLF5mwmTQ_ZyoM6tJpn5LeAhnCAQ","p":"6xy1skrnlrGUWtZFSHixn_eRA_O3GXKNBE4wziWodGZaFYsmFijZHbuQT0WFqc0epvLHNdNPvubFrVfV-U7ZIarfSSq6qBwBzDrDQS060MvjJIjrI16pKlx2X727FR1ZuwxT27dNg-wRTgKcZqXEalkvFOTEYBlCtw2-vzI0aRs","q":"3YWwOAs4GRZ9eq_fqNujACWJFyUO9QgEDPDOMg0EZhY7WkAlehTxxVXg65spWnfx_0GSc72I5N5qdbY-yDh2Dl7zIxvwnqZaKMJn4PEFkeAfyg62XlJlkHIwOVSj6vLNUDdDmG7bO2k6MyQ59jeuAemIljf9WhALNy8c9R0K3VM","dp":"KJ4LHcQnAjeng5Hk4kJHnXUtjls6VKEfj5DaiaKj2YgdI_-oEsf3ylUu9yLxloYjN4BVvgzFiBtiJzI3exyOEmzsqj1Bhe1guiGkvcvMj2nJ0fP9e1zNKM5UfPHQMjOh3tigXCLst0-_JZT55BnbNuw1YAytiFSU2_755xoLR-U","dq":"dCP7V-bJ6p1X_FLpOGau9wy262OKi_0_4mj-Mk-Q1tUhGRg4jeEdQRDdc6lN7Rilz-ZZGkVs2FGkD0MVd3PisXYmk2m6pfMhoe0K-WxkNy8Ce7Vq99jLVwgHMIenyS6zZjMTRYAZgPSShu2fVe-rU2VVLyz7r5RpzOzuibRIVfE","qi":"i7ND2teiVLkbaAs6rHfo5DiD1nlsORNYnn8Y_FjF6utb5OUljZ6-5WyEDJN9oIUX8o_Il9E6js-z7nhvPfFZHQN7ZWuYI0rO5qmsCDS9jWJ4GR61SgzZuLT7Jpp_KtwjW70x5wZ1Y-GugOP1Wct1YZWHn5YyLhvO6X_vttSmcS0","kid":"638c54b8-28c2-4b12-9598-ba12ef610a29","key_ops":["sign","verify"],"alg":"RS256"}]' - GOTRUE_JWT_EXP: 3600 - GOTRUE_DB_DRIVER: postgres - DB_NAMESPACE: auth - GOTRUE_API_HOST: 0.0.0.0 - PORT: 9996 - GOTRUE_DISABLE_SIGNUP: 'false' - API_EXTERNAL_URL: http://localhost:9996 - GOTRUE_SITE_URL: http://localhost:9996 - GOTRUE_MAILER_AUTOCONFIRM: 'true' - GOTRUE_SMS_AUTOCONFIRM: 'true' - GOTRUE_LOG_LEVEL: DEBUG - GOTRUE_OPERATOR_TOKEN: super-secret-operator-token - GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: 'true' - DATABASE_URL: 'postgres://postgres:postgres@db:5432/postgres?sslmode=disable' - GOTRUE_EXTERNAL_PHONE_ENABLED: 'true' - GOTRUE_SMTP_HOST: mail - GOTRUE_SMTP_PORT: 2500 - GOTRUE_SMTP_USER: GOTRUE_SMTP_USER - GOTRUE_SMTP_PASS: GOTRUE_SMTP_PASS - GOTRUE_SMTP_ADMIN_EMAIL: admin@email.com - GOTRUE_COOKIE_KEY: 'sb' - depends_on: - - db - restart: on-failure - disabled: # Signup disabled - image: supabase/auth:v2.187.0 - ports: - - '9997:9997' - environment: - GOTRUE_JWT_SECRET: '37c304f8-51aa-419a-a1af-06154e63707a' - GOTRUE_JWT_EXP: 3600 - GOTRUE_DB_DRIVER: postgres - DB_NAMESPACE: auth - GOTRUE_API_HOST: 0.0.0.0 - PORT: 9997 - GOTRUE_DISABLE_SIGNUP: 'true' - API_EXTERNAL_URL: http://localhost:9997 - GOTRUE_SITE_URL: http://localhost:9997 - GOTRUE_MAILER_AUTOCONFIRM: 'false' - GOTRUE_LOG_LEVEL: DEBUG - GOTRUE_OPERATOR_TOKEN: super-secret-operator-token - DATABASE_URL: 'postgres://postgres:postgres@db:5432/postgres?sslmode=disable' - GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: 'true' - GOTRUE_EXTERNAL_PHONE_ENABLED: 'false' - GOTRUE_EXTERNAL_EMAIL_ENABLED: 'false' - GOTRUE_SMTP_HOST: mail - GOTRUE_SMTP_PORT: 2500 - GOTRUE_SMTP_USER: GOTRUE_SMTP_USER - GOTRUE_SMTP_PASS: GOTRUE_SMTP_PASS - GOTRUE_SMTP_ADMIN_EMAIL: admin@email.com - GOTRUE_COOKIE_KEY: 'sb' - depends_on: - - db - restart: on-failure - mail: - image: phamhieu/inbucket:latest - ports: - - '2500:2500' # SMTP - - '9000:9000' # web interface - - '1100:1100' # POP3 - db: - image: supabase/postgres:15.1.1.46 - ports: - - '5432:5432' - command: postgres -c config_file=/etc/postgresql/postgresql.conf - volumes: - - ./db:/docker-entrypoint-initdb.d/ - environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_PORT: 5432 diff --git a/src/auth/infra/supabase/.gitignore b/src/auth/infra/supabase/.gitignore new file mode 100644 index 000000000..a3ad88055 --- /dev/null +++ b/src/auth/infra/supabase/.gitignore @@ -0,0 +1,4 @@ +# Supabase +.branches +.temp +.env diff --git a/src/auth/infra/supabase/config.toml b/src/auth/infra/supabase/config.toml new file mode 100644 index 000000000..291b4921b --- /dev/null +++ b/src/auth/infra/supabase/config.toml @@ -0,0 +1,91 @@ +project_id = "auth-py-tests" + +[api] +enabled = true +port = 57321 +schemas = ["public"] +extra_search_path = ["public", "extensions"] +max_rows = 1000 + +[db] +port = 57322 +shadow_port = 57320 +major_version = 17 + +[db.seed] +enabled = false + +[realtime] +enabled = false + +[studio] +enabled = false + +[local_smtp] +enabled = true +port = 57324 + +[storage] +enabled = false + +[auth] +enabled = true +site_url = "http://127.0.0.1:3000" +additional_redirect_urls = ["http://localhost:9999/welcome"] +jwt_expiry = 3600 +jwt_secret = "37c304f8-51aa-419a-a1af-06154e63707a" +signing_keys_path = "./signing_keys.json" +enable_refresh_token_rotation = true +refresh_token_reuse_interval = 10 +enable_signup = true +enable_anonymous_sign_ins = true +enable_manual_linking = false +minimum_password_length = 6 + +[auth.rate_limit] +email_sent = 10000 +sms_sent = 10000 +anonymous_users = 10000 +token_refresh = 10000 +sign_in_sign_ups = 10000 +token_verifications = 10000 +web3 = 10000 + +[auth.email] +enabled = true +double_confirm_changes = true +enable_confirmations = false +secure_password_change = false +max_frequency = "1s" + +[auth.sms] +enabled = true +enable_confirmations = false +template = "Your code is {{ .Code }}" +max_frequency = "1s" + +[auth.sms.twilio] +enabled = true +account_sid = "test" +message_service_sid = "test" +auth_token = "test" + +[auth.mfa.totp] +enroll_enabled = true +verify_enabled = true + +[auth.external.google] +enabled = true +client_id = "53566906701-bmhc1ndue7hild39575gkpimhs06b7ds.apps.googleusercontent.com" +secret = "Sm3s8RE85rDcS36iMy8YjrpC" + +[auth.oauth_server] +enabled = true +authorization_url_path = "/oauth/consent" +allow_dynamic_registration = true + +[edge_runtime] +enabled = false + +[analytics] +enabled = false diff --git a/src/auth/infra/supabase/signing_keys.json b/src/auth/infra/supabase/signing_keys.json new file mode 100644 index 000000000..9668a83ca --- /dev/null +++ b/src/auth/infra/supabase/signing_keys.json @@ -0,0 +1,19 @@ +[ + { + "kty": "RSA", + "n": "y3KQnIXK6wkPQ5m0XWp7z54BNZzXJk4IxXy81zFophdBBqz6u5OCMqWkC6i3WB7rlax4xjmxxyGyYRODooqCQTGahmpXryAAKc3g-gDIAq2MqVwlpmvXDavCVRK4hK7DZ6wK4MHrliSNHCuCkwIH3ofxTxgUwpSkOT58iU1ZOua5E1Y6R_Ozt3gLHha0Xa7a4V23pkP7n0xBvJPzIqiS3MZ4CQ_pz-buXYRgCPQkUJvXFFcuxmyqoYzorwQ1YVBOmH2XMx26RrCIxgj7geo9eVQ9u5qCPpQCGV5biqYMC4_m1kurOGf62URGRzXtmVzrW1PZJAeGoqMz5Fcfr8hiwQ", + "e": "AQAB", + "d": "C4XxquvpEmbw9mM-VAwz9w58Aw1fIkxJMuZdy9KAmue2RyqFCRrRxQycvgxQVi1qKpAaRx_9ccn20IjKa-psdkTY-8QKM2EcoUGH_KEOsxghX3ZYq5RwGdYgq7DjwqAjcTvNYe2Z6mcnlvDf9HOo_nG0uUYj5uGEa7meVCiNZUiSVdNGs-vOTUD8yB5pbZ4ute8ebuUzCWGQ3YwSNoWLa-dbECSO7jeobCapdB52MjEwE3_Ii8BWoySeDP-DEFX_5RTM2Zeh81zXAgmOxpZYTkjMsrznyxxBbXn7CdT8WMEXrreGZwIt3Mu6XpsLF5mwmTQ_ZyoM6tJpn5LeAhnCAQ", + "p": "6xy1skrnlrGUWtZFSHixn_eRA_O3GXKNBE4wziWodGZaFYsmFijZHbuQT0WFqc0epvLHNdNPvubFrVfV-U7ZIarfSSq6qBwBzDrDQS060MvjJIjrI16pKlx2X727FR1ZuwxT27dNg-wRTgKcZqXEalkvFOTEYBlCtw2-vzI0aRs", + "q": "3YWwOAs4GRZ9eq_fqNujACWJFyUO9QgEDPDOMg0EZhY7WkAlehTxxVXg65spWnfx_0GSc72I5N5qdbY-yDh2Dl7zIxvwnqZaKMJn4PEFkeAfyg62XlJlkHIwOVSj6vLNUDdDmG7bO2k6MyQ59jeuAemIljf9WhALNy8c9R0K3VM", + "dp": "KJ4LHcQnAjeng5Hk4kJHnXUtjls6VKEfj5DaiaKj2YgdI_-oEsf3ylUu9yLxloYjN4BVvgzFiBtiJzI3exyOEmzsqj1Bhe1guiGkvcvMj2nJ0fP9e1zNKM5UfPHQMjOh3tigXCLst0-_JZT55BnbNuw1YAytiFSU2_755xoLR-U", + "dq": "dCP7V-bJ6p1X_FLpOGau9wy262OKi_0_4mj-Mk-Q1tUhGRg4jeEdQRDdc6lN7Rilz-ZZGkVs2FGkD0MVd3PisXYmk2m6pfMhoe0K-WxkNy8Ce7Vq99jLVwgHMIenyS6zZjMTRYAZgPSShu2fVe-rU2VVLyz7r5RpzOzuibRIVfE", + "qi": "i7ND2teiVLkbaAs6rHfo5DiD1nlsORNYnn8Y_FjF6utb5OUljZ6-5WyEDJN9oIUX8o_Il9E6js-z7nhvPfFZHQN7ZWuYI0rO5qmsCDS9jWJ4GR61SgzZuLT7Jpp_KtwjW70x5wZ1Y-GugOP1Wct1YZWHn5YyLhvO6X_vttSmcS0", + "kid": "638c54b8-28c2-4b12-9598-ba12ef610a29", + "key_ops": [ + "sign", + "verify" + ], + "alg": "RS256" + } +] diff --git a/src/auth/pyproject.toml b/src/auth/pyproject.toml index a323a7795..897f684e6 100644 --- a/src/auth/pyproject.toml +++ b/src/auth/pyproject.toml @@ -31,9 +31,6 @@ repository = "https://github.com/supabase/supabase-py" documentation = "https://supabase.com/docs/reference/python/auth-api" changelog = "https://github.com/supabase/supabase-py/tree/main/CHANGELOG.md" -# [project.scripts] -# gh-download = "scripts.gh-download:main" - [dependency-groups] tests = [ "Faker >= 37.4.0", diff --git a/src/auth/scripts/gh-download.py b/src/auth/scripts/gh-download.py deleted file mode 100644 index b4ebbee79..000000000 --- a/src/auth/scripts/gh-download.py +++ /dev/null @@ -1,126 +0,0 @@ -# /// script -# dependencies = [ -# "pygithub >=1.57,<3.0", -# ] -# /// - -# This code was copied from -# https://gist.github.com/pdashford/2e4bcd4fc2343e2fd03efe4da17f577d -# and modified to work with Python 3, type hints, correct format and -# simplified the code to our needs. - -""" -Downloads folders from github repo -Requires PyGithub -pip install PyGithub -""" - -import base64 -import getopt -import os -import shutil -import sys - -from github import Github, GithubException -from github.ContentFile import ContentFile -from github.Repository import Repository - - -def get_sha_for_tag(repository: Repository, tag: str) -> str: - """ - Returns a commit PyGithub object for the specified repository and tag. - """ - branches = repository.get_branches() - matched_branches = [match for match in branches if match.name == tag] - if matched_branches: - return matched_branches[0].commit.sha - - tags = repository.get_tags() - matched_tags = [match for match in tags if match.name == tag] - if not matched_tags: - raise ValueError("No Tag or Branch exists with that name") - return matched_tags[0].commit.sha - - -def download_directory(repository: Repository, sha: str, server_path: str) -> None: - """ - Download all contents at server_path with commit tag sha in - the repository. - """ - if os.path.exists(server_path): - shutil.rmtree(server_path) - - os.makedirs(server_path) - contents = repository.get_dir_contents(server_path, ref=sha) - - for content in contents: - print(f"Processing {content.path}") - if content.type == "dir": - os.makedirs(content.path) - download_directory(repository, sha, content.path) - else: - try: - path = content.path - file_content = repository.get_contents(path, ref=sha) - if not isinstance(file_content, ContentFile): - raise ValueError("Expected ContentFile") - with open(content.path, "w+") as file_out: - if file_content.content: - file_data = base64.b64decode(file_content.content) - file_out.write(file_data.decode("utf-8")) - except (GithubException, OSError, ValueError) as exc: - print("Error processing %s: %s", content.path, exc) - - -def usage() -> None: - """ - Prints the usage command lines - """ - print("usage: gh-download --repo=repo --branch=branch --folder=folder") - - -def main(argv: list[str]) -> None: - """ - Main function block - """ - try: - opts, _ = getopt.getopt(argv, "r:b:f:", ["repo=", "branch=", "folder="]) - except getopt.GetoptError as err: - print(err) - usage() - sys.exit(2) - repo: str | None = None - branch: str | None = None - folder: str | None = None - for opt, arg in opts: - if opt in ("-r", "--repo"): - repo = arg - elif opt in ("-b", "--branch"): - branch = arg - elif opt in ("-f", "--folder"): - folder = arg - - if not repo: - print("Repo is required") - usage() - sys.exit(2) - if not branch: - print("Branch is required") - usage() - sys.exit(2) - if not folder: - print("Folder is required") - usage() - sys.exit(2) - - github = Github(None) - repository = github.get_repo(repo) - sha = get_sha_for_tag(repository, branch) - download_directory(repository, sha, folder) - - -if __name__ == "__main__": - """ - Entry point - """ - main(sys.argv[1:]) diff --git a/src/auth/tests/_async/conftest.py b/src/auth/tests/_async/conftest.py index dff80dd70..5982042a7 100644 --- a/src/auth/tests/_async/conftest.py +++ b/src/auth/tests/_async/conftest.py @@ -70,34 +70,10 @@ def mock_app_metadata() -> dict[str, list[str]]: } -SIGNUP_ENABLED_AUTO_CONFIRM_OFF_PORT = 9999 -SIGNUP_ENABLED_AUTO_CONFIRM_ON_PORT = 9998 -SIGNUP_DISABLED_AUTO_CONFIRM_OFF_PORT = 9997 -SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON_PORT = 9996 - -GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF = ( - f"http://localhost:{SIGNUP_ENABLED_AUTO_CONFIRM_OFF_PORT}" -) -GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON = ( - f"http://localhost:{SIGNUP_ENABLED_AUTO_CONFIRM_ON_PORT}" -) -GOTRUE_URL_SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON = ( - f"http://localhost:{SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON_PORT}" -) -GOTRUE_URL_SIGNUP_DISABLED_AUTO_CONFIRM_OFF = ( - f"http://localhost:{SIGNUP_DISABLED_AUTO_CONFIRM_OFF_PORT}" -) +AUTH_URL = "http://127.0.0.1:57321/auth/v1" GOTRUE_JWT_SECRET = "37c304f8-51aa-419a-a1af-06154e63707a" -AUTH_ADMIN_JWT = encode( - { - "sub": "1234567890", - "role": "supabase_admin", - }, - GOTRUE_JWT_SECRET, -) - @pytest.fixture async def admin_client_with_user_and_credentials( @@ -116,7 +92,7 @@ async def auth_client( request: pytest.FixtureRequest, ) -> AsyncGenerator[AsyncSupabaseAuthClient]: async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + url=AUTH_URL, auto_refresh_token=False, http_session=request.param(), ) as client: @@ -128,7 +104,7 @@ async def auth_client_with_session( request: pytest.FixtureRequest, ) -> AsyncGenerator[AsyncSupabaseAuthClient]: async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + url=AUTH_URL, auto_refresh_token=False, http_session=request.param(), ) as client: @@ -147,94 +123,6 @@ async def auth_client_with_session( await client.sign_out() -@pytest.fixture(params=http_sessions) -async def auth_client_with_asymmetric_session( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthClient]: - async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def auth_subscription_client( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthClient]: - async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def client_api_auto_confirm_enabled_client( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthClient]: - async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def client_api_auto_confirm_off_signups_enabled_client( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthClient]: - async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def client_api_auto_confirm_disabled_client( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthClient]: - async with AsyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_DISABLED_AUTO_CONFIRM_OFF, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def auth_admin_api_auto_confirm_enabled_client( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthAdmin]: - async with AsyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - default_headers={ - "Authorization": f"Bearer {AUTH_ADMIN_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def auth_admin_api_auto_confirm_disabled_client( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthAdmin]: - async with AsyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF, - default_headers={ - "Authorization": f"Bearer {AUTH_ADMIN_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - SECRET_KEY_JWT = encode( { "role": "service_role", @@ -248,35 +136,7 @@ async def secret_key_api_client( request: pytest.FixtureRequest, ) -> AsyncGenerator[AsyncSupabaseAuthAdmin]: async with AsyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - default_headers={ - "Authorization": f"Bearer {SECRET_KEY_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def secret_key_api_client_with_sms( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthAdmin]: - async with AsyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF, - default_headers={ - "Authorization": f"Bearer {SECRET_KEY_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -async def secret_key_api_client_no_sms( - request: pytest.FixtureRequest, -) -> AsyncGenerator[AsyncSupabaseAuthAdmin]: - async with AsyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_DISABLED_AUTO_CONFIRM_OFF, + url=AUTH_URL, default_headers={ "Authorization": f"Bearer {SECRET_KEY_JWT}", }, diff --git a/src/auth/tests/_async/test_gotrue.py b/src/auth/tests/_async/test_gotrue.py index 62f1a699c..93c2889ca 100644 --- a/src/auth/tests/_async/test_gotrue.py +++ b/src/auth/tests/_async/test_gotrue.py @@ -47,37 +47,41 @@ async def test_get_claims_returns_none_when_session_is_none( async def test_get_claims_calls_get_user_if_symmetric_jwt( mocker: MockerFixture, auth_client: AsyncSupabaseAuthClient ) -> None: - spy = mocker.spy(auth_client.session_manager, "_get_user") credentials = mock_user_credentials() options = SignUpWithPassword.email( email=credentials.email, password=credentials.password, ) - user = (await auth_client.sign_up(options)).user - - assert user is not None + response = await auth_client.sign_up(options) + assert response.user is not None + assert response.session is not None + payload = decode_jwt(response.session.access_token).payload + symmetric_jwt = encode( + payload.model_dump(exclude_none=True), GOTRUE_JWT_SECRET, algorithm="HS256" + ) + spy = mocker.spy(auth_client.session_manager, "_get_user") - response = await auth_client.get_claims() - assert response - claims = response.claims + claims_response = await auth_client.get_claims(symmetric_jwt) + assert claims_response + claims = claims_response.claims assert claims.model_extra - assert claims.model_extra["email"] == user.email + assert claims.model_extra["email"] == response.user.email spy.assert_called_once() async def test_get_claims_fetches_jwks_to_verify_asymmetric_jwt( - mocker: MockerFixture, auth_client_with_asymmetric_session: AsyncSupabaseAuthClient + mocker: MockerFixture, auth_client: AsyncSupabaseAuthClient ) -> None: credentials = mock_user_credentials() options = SignUpWithPassword.email( email=credentials.email, password=credentials.password, ) - user = (await auth_client_with_asymmetric_session.sign_up(options)).user + user = (await auth_client.sign_up(options)).user assert user is not None - response = await auth_client_with_asymmetric_session.get_claims() + response = await auth_client.get_claims() assert response claims = response.claims @@ -86,14 +90,14 @@ async def test_get_claims_fetches_jwks_to_verify_asymmetric_jwt( expected_keyid = "638c54b8-28c2-4b12-9598-ba12ef610a29" - assert len(auth_client_with_asymmetric_session._jwks.keys) == 1 - assert auth_client_with_asymmetric_session._jwks.keys[0].kid == expected_keyid + assert len(auth_client._jwks.keys) == 1 + assert auth_client._jwks.keys[0].kid == expected_keyid async def test_jwks_ttl_cache_behavior( - mocker: MockerFixture, auth_client_with_asymmetric_session: AsyncSupabaseAuthClient + mocker: MockerFixture, auth_client: AsyncSupabaseAuthClient ) -> None: - spy = mocker.spy(auth_client_with_asymmetric_session.executor.session, "send") + spy = mocker.spy(auth_client.executor.session, "send") # First call should fetch JWKS from endpoint credentials = mock_user_credentials() @@ -101,15 +105,15 @@ async def test_jwks_ttl_cache_behavior( email=credentials.email, password=credentials.password, ) - user = (await auth_client_with_asymmetric_session.sign_up(options)).user + user = (await auth_client.sign_up(options)).user assert user is not None - await auth_client_with_asymmetric_session.get_claims() + await auth_client.get_claims() first_call_count = spy.call_count # Second call within TTL should use cache - await auth_client_with_asymmetric_session.get_claims() + await auth_client.get_claims() assert spy.call_count == first_call_count # No additional JWKS request # Mock time to be after TTL expiry @@ -119,7 +123,7 @@ async def test_jwks_ttl_cache_behavior( mock_time.return_value = original_time() + 601 # TTL is 600 seconds # Call after TTL expiry should fetch fresh JWKS - await auth_client_with_asymmetric_session.get_claims() + await auth_client.get_claims() assert spy.call_count == first_call_count + 1 # One more JWKS request finally: # Restore original time function diff --git a/src/auth/tests/_async/test_gotrue_admin_api.py b/src/auth/tests/_async/test_gotrue_admin_api.py index 99acbdd42..d18dea06a 100644 --- a/src/auth/tests/_async/test_gotrue_admin_api.py +++ b/src/auth/tests/_async/test_gotrue_admin_api.py @@ -134,16 +134,14 @@ async def test_modify_app_metadata_using_update_user_by_id( async def test_modify_confirm_email_using_update_user_by_id( secret_key_api_client: AsyncSupabaseAuthAdmin, - client_api_auto_confirm_off_signups_enabled_client: AsyncSupabaseAuthClient, ) -> None: credentials = mock_user_credentials() - response = await client_api_auto_confirm_off_signups_enabled_client.sign_up( - SignUpWithPassword.email( + response = await secret_key_api_client.create_user( + AdminUserAttributes( email=credentials.email, password=credentials.password, ) ) - assert response.user assert not response.user.email_confirmed_at auth_response = await secret_key_api_client.update_user_by_id( response.user.id, @@ -156,11 +154,9 @@ async def test_modify_confirm_email_using_update_user_by_id( async def test_resend( - client_api_auto_confirm_off_signups_enabled_client: AsyncSupabaseAuthClient, + auth_client: AsyncSupabaseAuthClient, ) -> None: - await client_api_auto_confirm_off_signups_enabled_client.resend( - Resend.phone(phone="+112345678", type="sms") - ) + await auth_client.resend(Resend.phone(phone="+112345678", type="sms")) async def test_reauthenticate( @@ -183,12 +179,10 @@ async def test_reset_password_for_email( async def test_resend_missing_credentials( - client_api_auto_confirm_off_signups_enabled_client: AsyncSupabaseAuthClient, + auth_client: AsyncSupabaseAuthClient, ) -> None: credentials = mock_user_credentials() - await client_api_auto_confirm_off_signups_enabled_client.resend( - Resend.email(type="email_change", email=credentials.email) - ) + await auth_client.resend(Resend.email(type="email_change", email=credentials.email)) async def test_sign_in_anonymously( @@ -270,11 +264,9 @@ async def test_invite_user_by_email_creates_a_new_user_with_an_invited_at_timest async def test_sign_in_with_oauth( - client_api_auto_confirm_off_signups_enabled_client: AsyncSupabaseAuthClient, + auth_client: AsyncSupabaseAuthClient, ) -> None: - assert await client_api_auto_confirm_off_signups_enabled_client.sign_in_with_oauth( - provider="google" - ) + assert await auth_client.sign_in_with_oauth(provider="google") async def test_get_item_from_memory_storage( diff --git a/src/auth/tests/_sync/conftest.py b/src/auth/tests/_sync/conftest.py index 36d935f37..63a62b6db 100644 --- a/src/auth/tests/_sync/conftest.py +++ b/src/auth/tests/_sync/conftest.py @@ -70,34 +70,10 @@ def mock_app_metadata() -> dict[str, list[str]]: } -SIGNUP_ENABLED_AUTO_CONFIRM_OFF_PORT = 9999 -SIGNUP_ENABLED_AUTO_CONFIRM_ON_PORT = 9998 -SIGNUP_DISABLED_AUTO_CONFIRM_OFF_PORT = 9997 -SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON_PORT = 9996 - -GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF = ( - f"http://localhost:{SIGNUP_ENABLED_AUTO_CONFIRM_OFF_PORT}" -) -GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON = ( - f"http://localhost:{SIGNUP_ENABLED_AUTO_CONFIRM_ON_PORT}" -) -GOTRUE_URL_SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON = ( - f"http://localhost:{SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON_PORT}" -) -GOTRUE_URL_SIGNUP_DISABLED_AUTO_CONFIRM_OFF = ( - f"http://localhost:{SIGNUP_DISABLED_AUTO_CONFIRM_OFF_PORT}" -) +AUTH_URL = "http://127.0.0.1:57321/auth/v1" GOTRUE_JWT_SECRET = "37c304f8-51aa-419a-a1af-06154e63707a" -AUTH_ADMIN_JWT = encode( - { - "sub": "1234567890", - "role": "supabase_admin", - }, - GOTRUE_JWT_SECRET, -) - @pytest.fixture def admin_client_with_user_and_credentials( @@ -116,7 +92,7 @@ def auth_client( request: pytest.FixtureRequest, ) -> Generator[SyncSupabaseAuthClient]: with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + url=AUTH_URL, auto_refresh_token=False, http_session=request.param(), ) as client: @@ -128,7 +104,7 @@ def auth_client_with_session( request: pytest.FixtureRequest, ) -> Generator[SyncSupabaseAuthClient]: with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + url=AUTH_URL, auto_refresh_token=False, http_session=request.param(), ) as client: @@ -147,94 +123,6 @@ def auth_client_with_session( client.sign_out() -@pytest.fixture(params=http_sessions) -def auth_client_with_asymmetric_session( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthClient]: - with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def auth_subscription_client( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthClient]: - with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def client_api_auto_confirm_enabled_client( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthClient]: - with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def client_api_auto_confirm_off_signups_enabled_client( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthClient]: - with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def client_api_auto_confirm_disabled_client( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthClient]: - with SyncSupabaseAuthClient( - url=GOTRUE_URL_SIGNUP_DISABLED_AUTO_CONFIRM_OFF, - auto_refresh_token=False, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def auth_admin_api_auto_confirm_enabled_client( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthAdmin]: - with SyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - default_headers={ - "Authorization": f"Bearer {AUTH_ADMIN_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def auth_admin_api_auto_confirm_disabled_client( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthAdmin]: - with SyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF, - default_headers={ - "Authorization": f"Bearer {AUTH_ADMIN_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - SECRET_KEY_JWT = encode( { "role": "service_role", @@ -248,35 +136,7 @@ def secret_key_api_client( request: pytest.FixtureRequest, ) -> Generator[SyncSupabaseAuthAdmin]: with SyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, - default_headers={ - "Authorization": f"Bearer {SECRET_KEY_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def secret_key_api_client_with_sms( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthAdmin]: - with SyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_OFF, - default_headers={ - "Authorization": f"Bearer {SECRET_KEY_JWT}", - }, - http_session=request.param(), - ) as client: - yield client - - -@pytest.fixture(params=http_sessions) -def secret_key_api_client_no_sms( - request: pytest.FixtureRequest, -) -> Generator[SyncSupabaseAuthAdmin]: - with SyncSupabaseAuthAdmin( - url=GOTRUE_URL_SIGNUP_DISABLED_AUTO_CONFIRM_OFF, + url=AUTH_URL, default_headers={ "Authorization": f"Bearer {SECRET_KEY_JWT}", }, diff --git a/src/auth/tests/_sync/test_gotrue.py b/src/auth/tests/_sync/test_gotrue.py index c06e55a58..a494efc9c 100644 --- a/src/auth/tests/_sync/test_gotrue.py +++ b/src/auth/tests/_sync/test_gotrue.py @@ -47,37 +47,41 @@ def test_get_claims_returns_none_when_session_is_none( def test_get_claims_calls_get_user_if_symmetric_jwt( mocker: MockerFixture, auth_client: SyncSupabaseAuthClient ) -> None: - spy = mocker.spy(auth_client.session_manager, "_get_user") credentials = mock_user_credentials() options = SignUpWithPassword.email( email=credentials.email, password=credentials.password, ) - user = (auth_client.sign_up(options)).user - - assert user is not None + response = auth_client.sign_up(options) + assert response.user is not None + assert response.session is not None + payload = decode_jwt(response.session.access_token).payload + symmetric_jwt = encode( + payload.model_dump(exclude_none=True), GOTRUE_JWT_SECRET, algorithm="HS256" + ) + spy = mocker.spy(auth_client.session_manager, "_get_user") - response = auth_client.get_claims() - assert response - claims = response.claims + claims_response = auth_client.get_claims(symmetric_jwt) + assert claims_response + claims = claims_response.claims assert claims.model_extra - assert claims.model_extra["email"] == user.email + assert claims.model_extra["email"] == response.user.email spy.assert_called_once() def test_get_claims_fetches_jwks_to_verify_asymmetric_jwt( - mocker: MockerFixture, auth_client_with_asymmetric_session: SyncSupabaseAuthClient + mocker: MockerFixture, auth_client: SyncSupabaseAuthClient ) -> None: credentials = mock_user_credentials() options = SignUpWithPassword.email( email=credentials.email, password=credentials.password, ) - user = (auth_client_with_asymmetric_session.sign_up(options)).user + user = (auth_client.sign_up(options)).user assert user is not None - response = auth_client_with_asymmetric_session.get_claims() + response = auth_client.get_claims() assert response claims = response.claims @@ -86,14 +90,14 @@ def test_get_claims_fetches_jwks_to_verify_asymmetric_jwt( expected_keyid = "638c54b8-28c2-4b12-9598-ba12ef610a29" - assert len(auth_client_with_asymmetric_session._jwks.keys) == 1 - assert auth_client_with_asymmetric_session._jwks.keys[0].kid == expected_keyid + assert len(auth_client._jwks.keys) == 1 + assert auth_client._jwks.keys[0].kid == expected_keyid def test_jwks_ttl_cache_behavior( - mocker: MockerFixture, auth_client_with_asymmetric_session: SyncSupabaseAuthClient + mocker: MockerFixture, auth_client: SyncSupabaseAuthClient ) -> None: - spy = mocker.spy(auth_client_with_asymmetric_session.executor.session, "send") + spy = mocker.spy(auth_client.executor.session, "send") # First call should fetch JWKS from endpoint credentials = mock_user_credentials() @@ -101,15 +105,15 @@ def test_jwks_ttl_cache_behavior( email=credentials.email, password=credentials.password, ) - user = (auth_client_with_asymmetric_session.sign_up(options)).user + user = (auth_client.sign_up(options)).user assert user is not None - auth_client_with_asymmetric_session.get_claims() + auth_client.get_claims() first_call_count = spy.call_count # Second call within TTL should use cache - auth_client_with_asymmetric_session.get_claims() + auth_client.get_claims() assert spy.call_count == first_call_count # No additional JWKS request # Mock time to be after TTL expiry @@ -119,7 +123,7 @@ def test_jwks_ttl_cache_behavior( mock_time.return_value = original_time() + 601 # TTL is 600 seconds # Call after TTL expiry should fetch fresh JWKS - auth_client_with_asymmetric_session.get_claims() + auth_client.get_claims() assert spy.call_count == first_call_count + 1 # One more JWKS request finally: # Restore original time function diff --git a/src/auth/tests/_sync/test_gotrue_admin_api.py b/src/auth/tests/_sync/test_gotrue_admin_api.py index 91f27d45b..1d523caa6 100644 --- a/src/auth/tests/_sync/test_gotrue_admin_api.py +++ b/src/auth/tests/_sync/test_gotrue_admin_api.py @@ -134,16 +134,14 @@ def test_modify_app_metadata_using_update_user_by_id( def test_modify_confirm_email_using_update_user_by_id( secret_key_api_client: SyncSupabaseAuthAdmin, - client_api_auto_confirm_off_signups_enabled_client: SyncSupabaseAuthClient, ) -> None: credentials = mock_user_credentials() - response = client_api_auto_confirm_off_signups_enabled_client.sign_up( - SignUpWithPassword.email( + response = secret_key_api_client.create_user( + AdminUserAttributes( email=credentials.email, password=credentials.password, ) ) - assert response.user assert not response.user.email_confirmed_at auth_response = secret_key_api_client.update_user_by_id( response.user.id, @@ -156,11 +154,9 @@ def test_modify_confirm_email_using_update_user_by_id( def test_resend( - client_api_auto_confirm_off_signups_enabled_client: SyncSupabaseAuthClient, + auth_client: SyncSupabaseAuthClient, ) -> None: - client_api_auto_confirm_off_signups_enabled_client.resend( - Resend.phone(phone="+112345678", type="sms") - ) + auth_client.resend(Resend.phone(phone="+112345678", type="sms")) def test_reauthenticate( @@ -183,12 +179,10 @@ def test_reset_password_for_email( def test_resend_missing_credentials( - client_api_auto_confirm_off_signups_enabled_client: SyncSupabaseAuthClient, + auth_client: SyncSupabaseAuthClient, ) -> None: credentials = mock_user_credentials() - client_api_auto_confirm_off_signups_enabled_client.resend( - Resend.email(type="email_change", email=credentials.email) - ) + auth_client.resend(Resend.email(type="email_change", email=credentials.email)) def test_sign_in_anonymously( @@ -270,11 +264,9 @@ def test_invite_user_by_email_creates_a_new_user_with_an_invited_at_timestamp( def test_sign_in_with_oauth( - client_api_auto_confirm_off_signups_enabled_client: SyncSupabaseAuthClient, + auth_client: SyncSupabaseAuthClient, ) -> None: - assert client_api_auto_confirm_off_signups_enabled_client.sign_in_with_oauth( - provider="google" - ) + assert auth_client.sign_in_with_oauth(provider="google") def test_get_item_from_memory_storage( diff --git a/src/postgrest/Makefile b/src/postgrest/Makefile index 927b6f305..44369c736 100644 --- a/src/postgrest/Makefile +++ b/src/postgrest/Makefile @@ -18,24 +18,18 @@ help:: include ../../infra.mk +POSTGREST_EXCLUDED_SERVICES := studio,gotrue,mailpit,realtime,storage-api,imgproxy,edge-runtime,logflare,vector,supavisor,postgres-meta + start-infra: - @$(call retry_start_infra,$(compose_cleanup),(cd infra && docker compose up -d)) + @$(call retry_start_infra,$(call supabase_cleanup,postgrest-py-tests),supabase --workdir infra start -x $(POSTGREST_EXCLUDED_SERVICES)) help:: - @echo " stop-infra -- start containers for tests" + @echo " start-infra -- start containers for tests" stop-infra: - cd infra &&\ - docker compose down --remove-orphans + supabase --workdir infra stop help:: @echo " stop-infra -- stop containers for tests" -clean-infra: - cd infra &&\ - docker compose down --remove-orphans &&\ - docker system prune -a --volumes -f -help:: - @echo " clean-infra -- delete all stored information about the containers" - clean: rm -rf htmlcov .pytest_cache .ruff_cache rm -f .coverage coverage.xml diff --git a/src/postgrest/README.md b/src/postgrest/README.md index 8a7a8f63f..286e74247 100644 --- a/src/postgrest/README.md +++ b/src/postgrest/README.md @@ -12,13 +12,13 @@ ### Local PostgREST server -If you want to use a local PostgREST server for development, you can use our preconfigured instance via Docker Compose. +If you want to use a local PostgREST server for development, you can start our preconfigured instance with the [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started). ```sh -docker compose -f infra/docker-compose.yaml up +supabase --workdir infra start ``` -Once Docker Compose started, PostgREST is accessible at . +Once it has started, PostgREST is accessible at . ### Instructions diff --git a/src/postgrest/infra/docker-compose.yaml b/src/postgrest/infra/docker-compose.yaml deleted file mode 100644 index 30d847e14..000000000 --- a/src/postgrest/infra/docker-compose.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# docker-compose.yml -name: postgrest-tests -services: - rest: - image: postgrest/postgrest:v11.2.2 - ports: - - '3000:3000' - environment: - PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres - PGRST_DB_SCHEMAS: public,personal - PGRST_DB_EXTRA_SEARCH_PATH: extensions - PGRST_DB_ANON_ROLE: postgres - PGRST_DB_PLAN_ENABLED: 1 - PGRST_DB_TX_END: commit-allow-override - depends_on: - - db - db: - image: supabase/postgres:15.1.0.37 - ports: - - '5732:5732' - volumes: - - .:/docker-entrypoint-initdb.d/ - environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST: /var/run/postgresql - POSTGRES_PORT: 5732 diff --git a/src/postgrest/infra/supabase/.gitignore b/src/postgrest/infra/supabase/.gitignore new file mode 100644 index 000000000..a3ad88055 --- /dev/null +++ b/src/postgrest/infra/supabase/.gitignore @@ -0,0 +1,4 @@ +# Supabase +.branches +.temp +.env diff --git a/src/postgrest/infra/supabase/config.toml b/src/postgrest/infra/supabase/config.toml new file mode 100644 index 000000000..a0c9cc96d --- /dev/null +++ b/src/postgrest/infra/supabase/config.toml @@ -0,0 +1,37 @@ +project_id = "postgrest-py-tests" + +[api] +enabled = true +port = 56321 +schemas = ["public"] +extra_search_path = ["public", "extensions"] +max_rows = 1000 + +[db] +port = 56322 +shadow_port = 56320 +major_version = 17 + +[db.seed] +enabled = false + +[realtime] +enabled = false + +[studio] +enabled = false + +[local_smtp] +enabled = false + +[storage] +enabled = false + +[auth] +enabled = false + +[edge_runtime] +enabled = false + +[analytics] +enabled = false diff --git a/src/postgrest/infra/init.sql b/src/postgrest/infra/supabase/migrations/20240101000000_init.sql similarity index 93% rename from src/postgrest/infra/init.sql rename to src/postgrest/infra/supabase/migrations/20240101000000_init.sql index 20edeceec..a2c891ab2 100644 --- a/src/postgrest/infra/init.sql +++ b/src/postgrest/infra/supabase/migrations/20240101000000_init.sql @@ -103,3 +103,9 @@ insert into values (1, 1, 'harp'), (2, 1, 'violin'); + +grant select, insert, update, delete on all tables in schema public to anon; +grant execute on all functions in schema public to anon; + +alter role authenticator set pgrst.db_plan_enabled = 'true'; +notify pgrst, 'reload config'; diff --git a/src/postgrest/tests/_async/conftest.py b/src/postgrest/tests/_async/conftest.py index af2905e8a..59600d98b 100644 --- a/src/postgrest/tests/_async/conftest.py +++ b/src/postgrest/tests/_async/conftest.py @@ -8,7 +8,7 @@ from postgrest import AsyncPostgrestClient -REST_URL = "http://127.0.0.1:3000" +REST_URL = "http://127.0.0.1:56321/rest/v1" def httpx_client() -> AsyncClient: diff --git a/src/postgrest/tests/_sync/conftest.py b/src/postgrest/tests/_sync/conftest.py index 96083d5da..cf5f9da14 100644 --- a/src/postgrest/tests/_sync/conftest.py +++ b/src/postgrest/tests/_sync/conftest.py @@ -6,7 +6,7 @@ from postgrest import SyncPostgrestClient -REST_URL = "http://127.0.0.1:3000" +REST_URL = "http://127.0.0.1:56321/rest/v1" def httpx_client() -> Client: From 665acabc23cd5dec4d6bdce12f937988a32a423b Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 11 Nov 2026 11:29:02 +0100 Subject: [PATCH 2/4] test: keep the excluded services in one place and fix the postgrest example url --- src/auth/infra/supabase/config.toml | 15 ------------- src/postgrest/README.md | 2 +- src/postgrest/infra/supabase/config.toml | 21 ------------------- .../migrations/20240101000000_init.sql | 1 - 4 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/auth/infra/supabase/config.toml b/src/auth/infra/supabase/config.toml index 291b4921b..07148cfee 100644 --- a/src/auth/infra/supabase/config.toml +++ b/src/auth/infra/supabase/config.toml @@ -15,19 +15,10 @@ major_version = 17 [db.seed] enabled = false -[realtime] -enabled = false - -[studio] -enabled = false - [local_smtp] enabled = true port = 57324 -[storage] -enabled = false - [auth] enabled = true site_url = "http://127.0.0.1:3000" @@ -83,9 +74,3 @@ secret = "Sm3s8RE85rDcS36iMy8YjrpC" enabled = true authorization_url_path = "/oauth/consent" allow_dynamic_registration = true - -[edge_runtime] -enabled = false - -[analytics] -enabled = false diff --git a/src/postgrest/README.md b/src/postgrest/README.md index 286e74247..b433a4ed8 100644 --- a/src/postgrest/README.md +++ b/src/postgrest/README.md @@ -58,7 +58,7 @@ from supabase_utils.http.adapters.httpx import AsyncHttpxSession async def main() -> None: async with AsyncPostgrestClient( - "http://localhost:3000", AsyncHttpxSession(AsyncClient()) + "http://127.0.0.1:56321/rest/v1", AsyncHttpxSession(AsyncClient()) ) as client: r = await client.from_("countries").select("*").execute() countries = r.data diff --git a/src/postgrest/infra/supabase/config.toml b/src/postgrest/infra/supabase/config.toml index a0c9cc96d..bcc33497e 100644 --- a/src/postgrest/infra/supabase/config.toml +++ b/src/postgrest/infra/supabase/config.toml @@ -14,24 +14,3 @@ major_version = 17 [db.seed] enabled = false - -[realtime] -enabled = false - -[studio] -enabled = false - -[local_smtp] -enabled = false - -[storage] -enabled = false - -[auth] -enabled = false - -[edge_runtime] -enabled = false - -[analytics] -enabled = false diff --git a/src/postgrest/infra/supabase/migrations/20240101000000_init.sql b/src/postgrest/infra/supabase/migrations/20240101000000_init.sql index a2c891ab2..77bbbf009 100644 --- a/src/postgrest/infra/supabase/migrations/20240101000000_init.sql +++ b/src/postgrest/infra/supabase/migrations/20240101000000_init.sql @@ -108,4 +108,3 @@ grant select, insert, update, delete on all tables in schema public to anon; grant execute on all functions in schema public to anon; alter role authenticator set pgrst.db_plan_enabled = 'true'; -notify pgrst, 'reload config'; From a726edbf9ab6af5828d1411ef075a30e5fe03ba4 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 11 Nov 2026 11:43:50 +0100 Subject: [PATCH 3/4] test(auth): generate the jwt signing key instead of committing it --- src/auth/Makefile | 8 +++++++- src/auth/infra/supabase/.gitignore | 1 + src/auth/infra/supabase/signing_keys.json | 19 ------------------- src/auth/tests/_async/test_gotrue.py | 5 ++--- src/auth/tests/_sync/test_gotrue.py | 5 ++--- 5 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 src/auth/infra/supabase/signing_keys.json diff --git a/src/auth/Makefile b/src/auth/Makefile index bec01633f..09d632366 100644 --- a/src/auth/Makefile +++ b/src/auth/Makefile @@ -18,7 +18,13 @@ include ../../infra.mk AUTH_EXCLUDED_SERVICES := studio,postgrest,realtime,storage-api,imgproxy,edge-runtime,logflare,vector,supavisor,postgres-meta -start-infra: +SIGNING_KEYS := infra/supabase/signing_keys.json + +$(SIGNING_KEYS): + echo '[]' > $@ + supabase --workdir infra gen signing-key --algorithm ES256 --append + +start-infra: $(SIGNING_KEYS) @$(call retry_start_infra,$(call supabase_cleanup,auth-py-tests),supabase --workdir infra start -x $(AUTH_EXCLUDED_SERVICES)) help:: @echo " start-infra -- start containers for tests" diff --git a/src/auth/infra/supabase/.gitignore b/src/auth/infra/supabase/.gitignore index a3ad88055..382255f5b 100644 --- a/src/auth/infra/supabase/.gitignore +++ b/src/auth/infra/supabase/.gitignore @@ -2,3 +2,4 @@ .branches .temp .env +signing_keys.json diff --git a/src/auth/infra/supabase/signing_keys.json b/src/auth/infra/supabase/signing_keys.json deleted file mode 100644 index 9668a83ca..000000000 --- a/src/auth/infra/supabase/signing_keys.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "kty": "RSA", - "n": "y3KQnIXK6wkPQ5m0XWp7z54BNZzXJk4IxXy81zFophdBBqz6u5OCMqWkC6i3WB7rlax4xjmxxyGyYRODooqCQTGahmpXryAAKc3g-gDIAq2MqVwlpmvXDavCVRK4hK7DZ6wK4MHrliSNHCuCkwIH3ofxTxgUwpSkOT58iU1ZOua5E1Y6R_Ozt3gLHha0Xa7a4V23pkP7n0xBvJPzIqiS3MZ4CQ_pz-buXYRgCPQkUJvXFFcuxmyqoYzorwQ1YVBOmH2XMx26RrCIxgj7geo9eVQ9u5qCPpQCGV5biqYMC4_m1kurOGf62URGRzXtmVzrW1PZJAeGoqMz5Fcfr8hiwQ", - "e": "AQAB", - "d": "C4XxquvpEmbw9mM-VAwz9w58Aw1fIkxJMuZdy9KAmue2RyqFCRrRxQycvgxQVi1qKpAaRx_9ccn20IjKa-psdkTY-8QKM2EcoUGH_KEOsxghX3ZYq5RwGdYgq7DjwqAjcTvNYe2Z6mcnlvDf9HOo_nG0uUYj5uGEa7meVCiNZUiSVdNGs-vOTUD8yB5pbZ4ute8ebuUzCWGQ3YwSNoWLa-dbECSO7jeobCapdB52MjEwE3_Ii8BWoySeDP-DEFX_5RTM2Zeh81zXAgmOxpZYTkjMsrznyxxBbXn7CdT8WMEXrreGZwIt3Mu6XpsLF5mwmTQ_ZyoM6tJpn5LeAhnCAQ", - "p": "6xy1skrnlrGUWtZFSHixn_eRA_O3GXKNBE4wziWodGZaFYsmFijZHbuQT0WFqc0epvLHNdNPvubFrVfV-U7ZIarfSSq6qBwBzDrDQS060MvjJIjrI16pKlx2X727FR1ZuwxT27dNg-wRTgKcZqXEalkvFOTEYBlCtw2-vzI0aRs", - "q": "3YWwOAs4GRZ9eq_fqNujACWJFyUO9QgEDPDOMg0EZhY7WkAlehTxxVXg65spWnfx_0GSc72I5N5qdbY-yDh2Dl7zIxvwnqZaKMJn4PEFkeAfyg62XlJlkHIwOVSj6vLNUDdDmG7bO2k6MyQ59jeuAemIljf9WhALNy8c9R0K3VM", - "dp": "KJ4LHcQnAjeng5Hk4kJHnXUtjls6VKEfj5DaiaKj2YgdI_-oEsf3ylUu9yLxloYjN4BVvgzFiBtiJzI3exyOEmzsqj1Bhe1guiGkvcvMj2nJ0fP9e1zNKM5UfPHQMjOh3tigXCLst0-_JZT55BnbNuw1YAytiFSU2_755xoLR-U", - "dq": "dCP7V-bJ6p1X_FLpOGau9wy262OKi_0_4mj-Mk-Q1tUhGRg4jeEdQRDdc6lN7Rilz-ZZGkVs2FGkD0MVd3PisXYmk2m6pfMhoe0K-WxkNy8Ce7Vq99jLVwgHMIenyS6zZjMTRYAZgPSShu2fVe-rU2VVLyz7r5RpzOzuibRIVfE", - "qi": "i7ND2teiVLkbaAs6rHfo5DiD1nlsORNYnn8Y_FjF6utb5OUljZ6-5WyEDJN9oIUX8o_Il9E6js-z7nhvPfFZHQN7ZWuYI0rO5qmsCDS9jWJ4GR61SgzZuLT7Jpp_KtwjW70x5wZ1Y-GugOP1Wct1YZWHn5YyLhvO6X_vttSmcS0", - "kid": "638c54b8-28c2-4b12-9598-ba12ef610a29", - "key_ops": [ - "sign", - "verify" - ], - "alg": "RS256" - } -] diff --git a/src/auth/tests/_async/test_gotrue.py b/src/auth/tests/_async/test_gotrue.py index 93c2889ca..92cd923ef 100644 --- a/src/auth/tests/_async/test_gotrue.py +++ b/src/auth/tests/_async/test_gotrue.py @@ -88,10 +88,9 @@ async def test_get_claims_fetches_jwks_to_verify_asymmetric_jwt( assert claims.model_extra assert claims.model_extra["email"] == user.email - expected_keyid = "638c54b8-28c2-4b12-9598-ba12ef610a29" - + assert response.headers.alg == "ES256" assert len(auth_client._jwks.keys) == 1 - assert auth_client._jwks.keys[0].kid == expected_keyid + assert auth_client._jwks.keys[0].kid == response.headers.kid async def test_jwks_ttl_cache_behavior( diff --git a/src/auth/tests/_sync/test_gotrue.py b/src/auth/tests/_sync/test_gotrue.py index a494efc9c..675bed25a 100644 --- a/src/auth/tests/_sync/test_gotrue.py +++ b/src/auth/tests/_sync/test_gotrue.py @@ -88,10 +88,9 @@ def test_get_claims_fetches_jwks_to_verify_asymmetric_jwt( assert claims.model_extra assert claims.model_extra["email"] == user.email - expected_keyid = "638c54b8-28c2-4b12-9598-ba12ef610a29" - + assert response.headers.alg == "ES256" assert len(auth_client._jwks.keys) == 1 - assert auth_client._jwks.keys[0].kid == expected_keyid + assert auth_client._jwks.keys[0].kid == response.headers.kid def test_jwks_ttl_cache_behavior( From 7f64bad5d595605ed0bfd8ca2b8de87105b8b572 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 11 Nov 2026 11:51:55 +0100 Subject: [PATCH 4/4] test(auth): delete the signing key file when generating it fails --- src/auth/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/auth/Makefile b/src/auth/Makefile index 09d632366..f36e66bf0 100644 --- a/src/auth/Makefile +++ b/src/auth/Makefile @@ -18,6 +18,8 @@ include ../../infra.mk AUTH_EXCLUDED_SERVICES := studio,postgrest,realtime,storage-api,imgproxy,edge-runtime,logflare,vector,supavisor,postgres-meta +.DELETE_ON_ERROR: + SIGNING_KEYS := infra/supabase/signing_keys.json $(SIGNING_KEYS):