From f5f047b886c336f1ccadc57dea31bb73b6d6433b Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Mon, 22 Dec 2025 18:06:13 +0200 Subject: [PATCH 1/3] test(storage): use supabase cli for tests --- .github/workflows/ci-core.yml | 6 + .../core/storage-js/infra/docker-compose.yml | 85 ----- .../core/storage-js/infra/kong/Dockerfile | 15 - packages/core/storage-js/infra/kong/kong.yml | 29 -- .../infra/postgres/00-initial-schema.sql | 23 -- .../core/storage-js/infra/postgres/Dockerfile | 10 - .../storage-js/infra/postgres/auth-schema.sql | 89 ----- .../storage-js/infra/postgres/dummy-data.sql | 60 --- .../infra/postgres/storage-schema.sql | 104 ------ .../core/storage-js/infra/storage/Dockerfile | 3 - packages/core/storage-js/package.json | 4 +- .../core/storage-js/test/storageApi.test.ts | 7 +- .../storage-js/test/storageBucketApi.test.ts | 11 +- .../storage-js/test/storageFileApi.test.ts | 7 +- .../test/storageFileApiNode.test.ts | 6 +- .../core/storage-js/test/supabase/.gitignore | 8 + .../core/storage-js/test/supabase/config.toml | 352 ++++++++++++++++++ .../core/storage-js/test/supabase/seed.sql | 136 +++++++ 18 files changed, 521 insertions(+), 434 deletions(-) delete mode 100644 packages/core/storage-js/infra/docker-compose.yml delete mode 100644 packages/core/storage-js/infra/kong/Dockerfile delete mode 100644 packages/core/storage-js/infra/kong/kong.yml delete mode 100644 packages/core/storage-js/infra/postgres/00-initial-schema.sql delete mode 100644 packages/core/storage-js/infra/postgres/Dockerfile delete mode 100644 packages/core/storage-js/infra/postgres/auth-schema.sql delete mode 100644 packages/core/storage-js/infra/postgres/dummy-data.sql delete mode 100644 packages/core/storage-js/infra/postgres/storage-schema.sql delete mode 100644 packages/core/storage-js/infra/storage/Dockerfile create mode 100644 packages/core/storage-js/test/supabase/.gitignore create mode 100644 packages/core/storage-js/test/supabase/config.toml create mode 100644 packages/core/storage-js/test/supabase/seed.sql diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 409fbcb8f..83258bfe4 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -88,6 +88,12 @@ jobs: flag-name: auth-js fail-on-error: false continue-on-error: true + + - name: Setup Supabase CLI (for storage-js tests) + uses: supabase/setup-cli@v1 + with: + version: latest + - name: Run storage-js tests (if affected) run: npx nx affected --target=test:storage diff --git a/packages/core/storage-js/infra/docker-compose.yml b/packages/core/storage-js/infra/docker-compose.yml deleted file mode 100644 index dc3801638..000000000 --- a/packages/core/storage-js/infra/docker-compose.yml +++ /dev/null @@ -1,85 +0,0 @@ -# docker-compose.yml - -services: - kong: - container_name: supabase-kong - build: - context: ./kong - environment: - KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml - KONG_PLUGINS: request-transformer,cors,key-auth,http-log - ports: - - 8000:8000/tcp - - 8443:8443/tcp - storage: - build: - context: ./storage - ports: - - '5050:5000' - depends_on: - db: - condition: service_healthy - restart: always - environment: - ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ - SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.FhK1kZdHmWdCIEZELt0QDCw6FIlCS8rVmp4RzaeI2LM - PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string - REGION: us-east-1 # region where your bucket is located - POSTGREST_URL: http://rest:3000 - GLOBAL_S3_BUCKET: supa-storage-testing # name of s3 bucket where you want to store objects - PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long - DATABASE_URL: postgres://postgres:postgres@db:5432/postgres - PGOPTIONS: '-c search_path=storage' - AWS_ACCESS_KEY_ID: replace-with-your-aws-key - AWS_SECRET_ACCESS_KEY: replace-with-your-aws-secret - FILE_SIZE_LIMIT: 52428800 - STORAGE_BACKEND: file - FILE_STORAGE_BACKEND_PATH: /tmp/storage - ENABLE_IMAGE_TRANSFORMATION: 'true' - IMGPROXY_URL: http://imgproxy:8080 - DEBUG: 'knex:*' - volumes: - - assets-volume:/tmp/storage - healthcheck: - test: ['CMD-SHELL', 'curl -f -LI http://localhost:5000/status'] - interval: 2s - db: - build: - context: ./postgres - ports: - - 5432:5432 - command: postgres -c config_file=/etc/postgresql/postgresql.conf - environment: - POSTGRES_PASSWORD: postgres - healthcheck: - test: ['CMD-SHELL', 'pg_isready'] - interval: 10s - timeout: 5s - retries: 5 - - dummy_data: - build: - context: ./postgres - depends_on: - storage: - condition: service_healthy - volumes: - - ./postgres:/sql - command: - - psql - - 'postgresql://postgres:postgres@db:5432/postgres' - - -f - - /sql/dummy-data.sql - - imgproxy: - image: darthsim/imgproxy - ports: - - 50020:8080 - volumes: - - assets-volume:/tmp/storage - environment: - - IMGPROXY_LOCAL_FILESYSTEM_ROOT=/ - - IMGPROXY_USE_ETAG=true - - IMGPROXY_ENABLE_WEBP_DETECTION=true -volumes: - assets-volume: diff --git a/packages/core/storage-js/infra/kong/Dockerfile b/packages/core/storage-js/infra/kong/Dockerfile deleted file mode 100644 index 4597f9035..000000000 --- a/packages/core/storage-js/infra/kong/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM kong:2.1 - -COPY kong.yml /var/lib/kong/kong.yml - -# Build time defaults -ARG build_KONG_DATABASE=off -ARG build_KONG_PLUGINS=request-transformer,cors,key-auth -ARG build_KONG_DECLARATIVE_CONFIG=/var/lib/kong/kong.yml - -# Run time values -ENV KONG_DATABASE=$build_KONG_DATABASE -ENV KONG_PLUGINS=$build_KONG_PLUGINS -ENV KONG_DECLARATIVE_CONFIG=$build_KONG_DECLARATIVE_CONFIG - -EXPOSE 8000 diff --git a/packages/core/storage-js/infra/kong/kong.yml b/packages/core/storage-js/infra/kong/kong.yml deleted file mode 100644 index 118003cec..000000000 --- a/packages/core/storage-js/infra/kong/kong.yml +++ /dev/null @@ -1,29 +0,0 @@ -_format_version: '1.1' -services: - - name: rest-v1 - _comment: 'PosgREST: /rest/v1/* -> http://rest:3000/*' - url: http://rest:3000/ - routes: - - name: rest-v1-all - strip_path: true - paths: - - /rest/v1/ - plugins: - - name: cors - - name: key-auth - config: - hide_credentials: true - - name: storage-v1 - _comment: 'Storage: /storage/v1/* -> http://storage-api:5000/*' - url: http://storage:5000/ - routes: - - name: storage-v1-all - strip_path: true - paths: - - /storage/v1/ - plugins: - - name: cors -consumers: - - username: 'private-key' - keyauth_credentials: - - key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoicG9zdGdyZXMifQ.magCcozTMKNrl76Tj2dsM7XTl_YH0v0ilajzAvIlw3U diff --git a/packages/core/storage-js/infra/postgres/00-initial-schema.sql b/packages/core/storage-js/infra/postgres/00-initial-schema.sql deleted file mode 100644 index 737d8bb65..000000000 --- a/packages/core/storage-js/infra/postgres/00-initial-schema.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Set up reatime -create publication supabase_realtime for all tables; - --- Extension namespacing -create schema extensions; -create extension if not exists "uuid-ossp" with schema extensions; -create extension if not exists pgcrypto with schema extensions; -create extension if not exists pgjwt with schema extensions; - --- Developer roles -create role anon nologin noinherit; -create role authenticated nologin noinherit; -- "logged in" user: web_user, app_user, etc -create role service_role nologin noinherit bypassrls; -- allow developers to create JWT's that bypass their policies - -create user authenticator noinherit; -grant anon to authenticator; -grant authenticated to authenticator; -grant service_role to authenticator; - -grant usage on schema public to postgres, anon, authenticated, service_role; -alter default privileges in schema public grant all on tables to postgres, anon, authenticated, service_role; -alter default privileges in schema public grant all on functions to postgres, anon, authenticated, service_role; -alter default privileges in schema public grant all on sequences to postgres, anon, authenticated, service_role; \ No newline at end of file diff --git a/packages/core/storage-js/infra/postgres/Dockerfile b/packages/core/storage-js/infra/postgres/Dockerfile deleted file mode 100644 index 4af4336fe..000000000 --- a/packages/core/storage-js/infra/postgres/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM supabase/postgres:15.8.1.044 -# -#COPY 00-initial-schema.sql /docker-entrypoint-initdb.d/00-initial-schema.sql -#COPY auth-schema.sql /docker-entrypoint-initdb.d/01-auth-schema.sql -#COPY storage-schema.sql /docker-entrypoint-initdb.d/02-storage-schema.sql - -# Build time defaults - - -EXPOSE 5432 \ No newline at end of file diff --git a/packages/core/storage-js/infra/postgres/auth-schema.sql b/packages/core/storage-js/infra/postgres/auth-schema.sql deleted file mode 100644 index 7a88c4b19..000000000 --- a/packages/core/storage-js/infra/postgres/auth-schema.sql +++ /dev/null @@ -1,89 +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 Role 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; --- Gets the User Email from the request cookie -create or replace function auth.email() returns text as $$ - select nullif(current_setting('request.jwt.claim.email', 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; \ No newline at end of file diff --git a/packages/core/storage-js/infra/postgres/dummy-data.sql b/packages/core/storage-js/infra/postgres/dummy-data.sql deleted file mode 100644 index af5067898..000000000 --- a/packages/core/storage-js/infra/postgres/dummy-data.sql +++ /dev/null @@ -1,60 +0,0 @@ --- insert users -INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at") VALUES -('00000000-0000-0000-0000-000000000000', '317eadce-631a-4429-a0bb-f19a7a517b4a', 'authenticated', 'authenticated', 'inian+user2@supabase.io', '', NULL, '2021-02-17 04:41:13.408828+00', '541rn7rTZPGeGCYsp0a38g', '2021-02-17 04:41:13.408828+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:41:13.406912+00', '2021-02-17 04:41:13.406919+00'), -('00000000-0000-0000-0000-000000000000', '4d56e902-f0a0-4662-8448-a4d9e643c142', 'authenticated', 'authenticated', 'inian+user1@supabase.io', '', NULL, '2021-02-17 04:40:58.570482+00', 'U1HvzExEO3l7JzP-4tTxJA', '2021-02-17 04:40:58.570482+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:40:58.568637+00', '2021-02-17 04:40:58.568642+00'), -('00000000-0000-0000-0000-000000000000', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', 'authenticated', 'authenticated', 'inian+admin@supabase.io', '', NULL, '2021-02-17 04:40:42.901743+00', '3EG99GjT_e3NC4eGEBXOjw', '2021-02-17 04:40:42.901743+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:40:42.890632+00', '2021-02-17 04:40:42.890637+00'); - --- insert buckets -INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_at") VALUES -('bucket2', 'bucket2', '4d56e902-f0a0-4662-8448-a4d9e643c142', '2021-02-17 04:43:32.770206+00', '2021-02-17 04:43:32.770206+00'), -('bucket3', 'bucket3', '4d56e902-f0a0-4662-8448-a4d9e643c142', '2021-02-17 04:43:32.770206+00', '2021-02-17 04:43:32.770206+00'), -('bucket4', 'bucket4', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-25 09:23:01.58385+00', '2021-02-25 09:23:01.58385+00'), -('bucket5', 'bucket5', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-27 03:04:25.6386+00', '2021-02-27 03:04:25.6386+00'), -('bucket-move', 'bucket-move', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-27 03:04:25.6386+00', '2021-02-27 03:04:25.6386+00'); - - --- insert objects -INSERT INTO "storage"."objects" ("id", "bucket_id", "name", "owner", "created_at", "updated_at", "last_accessed_at", "metadata") VALUES -('03e458f9-892f-4db2-8cb9-d3401a689e25', 'bucket2', 'public/sadcat-upload23.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-04 08:26:08.553748+00', '2021-03-04 08:26:08.553748+00', '2021-03-04 08:26:08.553748+00', '{"mimetype": "image/svg+xml", "size": 1234}'), -('070825af-a11d-44fe-9f1d-abdc76f686f2', 'bucket2', 'public/sadcat-upload.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-02 16:31:11.115996+00', '2021-03-02 16:31:11.115996+00', '2021-03-02 16:31:11.115996+00', '{"mimetype": "image/png", "size": 1234}'), -('0cac5609-11e1-4f21-b486-d0eeb60909f6', 'bucket2', 'curlimage.jpg', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-23 11:05:16.625075+00', '2021-02-23 11:05:16.625075+00', '2021-02-23 11:05:16.625075+00', '{"size": 1234}'), -('147c6795-94d5-4008-9d81-f7ba3b4f8a9f', 'bucket2', 'folder/only_uid.jpg', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-17 10:36:01.504227+00', '2021-02-17 11:03:03.049618+00', '2021-02-17 10:36:01.504227+00', '{"size": 1234}'), -('65a3aa9c-0ff2-4adc-85d0-eab673c27443', 'bucket2', 'authenticated/casestudy.png', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-17 10:42:19.366559+00', '2021-02-17 11:03:30.025116+00', '2021-02-17 10:42:19.366559+00', '{"size": 1234}'), -('10ABE273-D77A-4BDA-B410-6FC0CA3E6ADC', 'bucket2', 'authenticated/cat.jpg', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-17 10:42:19.366559+00', '2021-02-17 11:03:30.025116+00', '2021-02-17 10:42:19.366559+00', '{"size": 1234}'), -('1edccac7-0876-4e9f-89da-a08d2a5f654b', 'bucket2', 'authenticated/delete.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-02 16:31:11.115996+00', '2021-03-02 16:31:11.115996+00', '2021-03-02 16:31:11.115996+00', '{"mimetype": "image/png", "size": 1234}'), -('1a911f3c-8c1d-4661-93c1-8e065e4d757e', 'bucket2', 'authenticated/delete1.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('372d5d74-e24d-49dc-abe8-47d7eb226a2e', 'bucket2', 'authenticated/delete-multiple1.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('34811c1b-85e5-4eb6-a5e3-d607b2f6986e', 'bucket2', 'authenticated/delete-multiple2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('45950ff2-d3a8-4add-8e49-bafc01198340', 'bucket2', 'authenticated/delete-multiple3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('469b0216-5419-41f6-9a37-2abfd7fad29c', 'bucket2', 'authenticated/delete-multiple4.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('55930619-a668-4dbc-aea3-b93dfe101e7f', 'bucket2', 'authenticated/delete-multiple7.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('D1CE4E4F-03E2-473D-858B-301D7989B581', 'bucket2', 'authenticated/move-orig.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('222b3d1e-bc17-414c-b336-47894aa4d697', 'bucket2', 'authenticated/move-orig-2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('8f7d643d-1e82-4d39-ae39-d9bd6b0cfe9c', 'bucket2', 'authenticated/move-orig-3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}'), -('8377527d-3518-4dc8-8290-c6926470e795', 'bucket2', 'folder/subfolder/public-all-permissions.png', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-17 10:26:42.791214+00', '2021-02-17 11:03:30.025116+00', '2021-02-17 10:26:42.791214+00', '{"size": 1234}'), -('b39ae4ab-802b-4c42-9271-3f908c34363c', 'bucket2', 'private/sadcat-upload3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '{"mimetype": "image/svg+xml", "size": 1234}'), -('8098E1AC-C744-4368-86DF-71B60CCDE221', 'bucket3', 'sadcat-upload3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '{"mimetype": "image/svg+xml", "size": 1234}'), -('D3EB488E-94F4-46CD-86D3-242C13B95BAC', 'bucket3', 'sadcat-upload2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '{"mimetype": "image/svg+xml", "size": 1234}'); - --- add policies --- allows user to CRUD all buckets -CREATE POLICY crud_buckets ON storage.buckets for all USING (auth.uid() = '317eadce-631a-4429-a0bb-f19a7a517b4a'); -CREATE POLICY crud_objects ON storage.objects for all USING (auth.uid() = '317eadce-631a-4429-a0bb-f19a7a517b4a'); -CREATE POLICY crud_prefixes ON storage.prefixes for all USING (auth.uid() = '317eadce-631a-4429-a0bb-f19a7a517b4a'); - --- allow public CRUD acccess to the public folder in bucket2 -CREATE POLICY crud_public_folder ON storage.objects for all USING (bucket_id='bucket2' and (storage.foldername(name))[1] = 'public'); --- allow public CRUD acccess to a particular file in bucket2 -CREATE POLICY crud_public_file ON storage.objects for all USING (bucket_id='bucket2' and name = 'folder/subfolder/public-all-permissions.png'); --- allow public CRUD acccess to a folder in bucket2 to a user with a given id -CREATE POLICY crud_uid_folder ON storage.objects for all USING (bucket_id='bucket2' and (storage.foldername(name))[1] = 'only_uid' and auth.uid() = 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2'); --- allow public CRUD acccess to a file in bucket2 to a user with a given id -CREATE POLICY crud_uid_file ON storage.objects for all USING (bucket_id='bucket2' and name = 'folder/only_uid.jpg' and auth.uid() = 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2'); --- allow CRUD acccess to a folder in bucket2 to all authenticated users -CREATE POLICY authenticated_folder ON storage.objects for all USING (bucket_id='bucket2' and (storage.foldername(name))[1] = 'authenticated' and auth.role() = 'authenticated'); --- allow CRUD access to a folder in bucket2 to its owners -CREATE POLICY crud_owner_only ON storage.objects for all USING (bucket_id='bucket2' and (storage.foldername(name))[1] = 'only_owner' and owner = auth.uid()); --- allow CRUD access to bucket4 -CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4'); - -CREATE POLICY crud_my_bucket ON storage.objects for all USING (bucket_id='my-private-bucket' and auth.uid()::text = '317eadce-631a-4429-a0bb-f19a7a517b4a'); \ No newline at end of file diff --git a/packages/core/storage-js/infra/postgres/storage-schema.sql b/packages/core/storage-js/infra/postgres/storage-schema.sql deleted file mode 100644 index 8403fe179..000000000 --- a/packages/core/storage-js/infra/postgres/storage-schema.sql +++ /dev/null @@ -1,104 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION postgres; - -grant usage on schema storage to postgres, anon, authenticated, service_role; -alter default privileges in schema storage grant all on tables to postgres, anon, authenticated, service_role; -alter default privileges in schema storage grant all on functions to postgres, anon, authenticated, service_role; -alter default privileges in schema storage grant all on sequences to postgres, anon, authenticated, service_role; - -CREATE TABLE "storage"."buckets" ( - "id" text not NULL, - "name" text NOT NULL, - "owner" uuid, - "created_at" timestamptz DEFAULT now(), - "updated_at" timestamptz DEFAULT now(), - CONSTRAINT "buckets_owner_fkey" FOREIGN KEY ("owner") REFERENCES "auth"."users"("id"), - PRIMARY KEY ("id") -); -CREATE UNIQUE INDEX "bname" ON "storage"."buckets" USING BTREE ("name"); - -CREATE TABLE "storage"."objects" ( - "id" uuid NOT NULL DEFAULT extensions.uuid_generate_v4(), - "bucket_id" text, - "name" text, - "owner" uuid, - "created_at" timestamptz DEFAULT now(), - "updated_at" timestamptz DEFAULT now(), - "last_accessed_at" timestamptz DEFAULT now(), - "metadata" jsonb, - CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY ("bucket_id") REFERENCES "storage"."buckets"("id"), - PRIMARY KEY ("id") -); -CREATE UNIQUE INDEX "bucketid_objname" ON "storage"."objects" USING BTREE ("bucket_id","name"); -CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops); - -ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY; - -CREATE OR REPLACE FUNCTION storage.foldername(name text) - RETURNS text[] - LANGUAGE plpgsql -AS $function$ -DECLARE -_parts text[]; -BEGIN - select string_to_array(name, '/') into _parts; - return _parts[1:array_length(_parts,1)-1]; -END -$function$; - -CREATE OR REPLACE FUNCTION storage.filename(name text) - RETURNS text - LANGUAGE plpgsql -AS $function$ -DECLARE -_parts text[]; -BEGIN - select string_to_array(name, '/') into _parts; - return _parts[array_length(_parts,1)]; -END -$function$; - -CREATE OR REPLACE FUNCTION storage.extension(name text) - RETURNS text - LANGUAGE plpgsql -AS $function$ -DECLARE -_parts text[]; -_filename text; -BEGIN - select string_to_array(name, '/') into _parts; - select _parts[array_length(_parts,1)] into _filename; - return split_part(_filename, '.', 2); -END -$function$; - -CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0) - RETURNS TABLE ( - name text, - id uuid, - updated_at TIMESTAMPTZ, - created_at TIMESTAMPTZ, - last_accessed_at TIMESTAMPTZ, - metadata jsonb - ) - LANGUAGE plpgsql -AS $function$ -BEGIN - return query - with files_folders as ( - select ((string_to_array(objects.name, '/'))[levels]) as folder - from objects - where objects.name ilike prefix || '%' - and bucket_id = bucketname - GROUP by folder - limit limits - offset offsets - ) - select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders - left join objects - on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname; -END -$function$; - -GRANT ALL PRIVILEGES ON SCHEMA storage TO postgres; -GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO postgres; -GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO postgres; \ No newline at end of file diff --git a/packages/core/storage-js/infra/storage/Dockerfile b/packages/core/storage-js/infra/storage/Dockerfile deleted file mode 100644 index 5d8f27262..000000000 --- a/packages/core/storage-js/infra/storage/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM supabase/storage-api:v1.27.4 - -RUN apk add curl --no-cache \ No newline at end of file diff --git a/packages/core/storage-js/package.json b/packages/core/storage-js/package.json index 76dd269b0..c58afa394 100644 --- a/packages/core/storage-js/package.json +++ b/packages/core/storage-js/package.json @@ -43,8 +43,8 @@ "build:watch": "tsdown --watch", "test:storage": "npx nx test:clean storage-js && npx nx test:infra storage-js && npx nx test:suite storage-js && npx nx test:clean storage-js", "test:suite": "jest --runInBand --coverage", - "test:infra": "cd infra && docker compose down && docker compose up -d --build && sleep 10", - "test:clean": "cd infra && docker compose down --remove-orphans", + "test:infra": "cd test && supabase start && supabase db reset", + "test:clean": "cd test && supabase stop --no-backup || true", "docs": "typedoc --entryPoints src/index.ts --out docs/v2 --entryPoints src/packages/* --excludePrivate --excludeProtected", "docs:json": "typedoc --json docs/v2/spec.json --entryPoints src/index.ts --entryPoints src/packages/* --excludePrivate --excludeExternals --excludeProtected" }, diff --git a/packages/core/storage-js/test/storageApi.test.ts b/packages/core/storage-js/test/storageApi.test.ts index eb1c9bd55..a18b60988 100644 --- a/packages/core/storage-js/test/storageApi.test.ts +++ b/packages/core/storage-js/test/storageApi.test.ts @@ -1,9 +1,10 @@ import { StorageClient } from '../src/index' -// TODO: need to setup storage-api server for this test -const URL = 'http://localhost:8000/storage/v1' +// Supabase CLI local development defaults +const URL = 'http://127.0.0.1:54321/storage/v1' +// service_role key - bypasses RLS for testing const KEY = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aGVudGljYXRlZCIsInN1YiI6IjMxN2VhZGNlLTYzMWEtNDQyOS1hMGJiLWYxOWE3YTUxN2I0YSIsImlhdCI6MTcxMzQzMzgwMCwiZXhwIjoyMDI5MDA5ODAwfQ.jVFIR-MB7rNfUuJaUH-_CyDFZEHezzXiqcRcdrGd29o' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' const storage = new StorageClient(URL, { Authorization: `Bearer ${KEY}` }) const newBucketName = `my-new-bucket-${Date.now()}` diff --git a/packages/core/storage-js/test/storageBucketApi.test.ts b/packages/core/storage-js/test/storageBucketApi.test.ts index 1e565ebc5..a3d29041c 100644 --- a/packages/core/storage-js/test/storageBucketApi.test.ts +++ b/packages/core/storage-js/test/storageBucketApi.test.ts @@ -20,8 +20,8 @@ class MockResponse { } } -// Mock URL and credentials for testing -const URL = 'http://localhost:8000/storage/v1' +// Supabase CLI local development defaults +const URL = 'http://127.0.0.1:54321/storage/v1' const KEY = 'test-api-key' describe('Bucket API Error Handling', () => { @@ -85,12 +85,11 @@ describe('Bucket API Error Handling', () => { const { data, error } = await storage.listBuckets() expect(data).toBeNull() expect(error).not.toBeNull() - expect(error?.message).toBe(`headers must have required property 'authorization'`) + // Supabase CLI returns "Invalid Compact JWS" when no auth header is provided + expect(error?.message).toBe('Invalid Compact JWS') // throws when .throwOnError is enabled - await expect(storage.throwOnError().listBuckets()).rejects.toThrowError( - "headers must have required property 'authorization'" - ) + await expect(storage.throwOnError().listBuckets()).rejects.toThrowError('Invalid Compact JWS') }) it('handles network errors', async () => { diff --git a/packages/core/storage-js/test/storageFileApi.test.ts b/packages/core/storage-js/test/storageFileApi.test.ts index 645de3e00..be47496a4 100644 --- a/packages/core/storage-js/test/storageFileApi.test.ts +++ b/packages/core/storage-js/test/storageFileApi.test.ts @@ -8,10 +8,11 @@ import { StorageApiError, StorageError } from '../src/lib/errors' import BlobDownloadBuilder from '../src/packages/BlobDownloadBuilder' import StreamDownloadBuilder from '../src/packages/StreamDownloadBuilder' -// TODO: need to setup storage-api server for this test -const URL = 'http://localhost:8000/storage/v1' +// Supabase CLI local development defaults +const URL = 'http://127.0.0.1:54321/storage/v1' +// service_role key - bypasses RLS for testing const KEY = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aGVudGljYXRlZCIsInN1YiI6IjMxN2VhZGNlLTYzMWEtNDQyOS1hMGJiLWYxOWE3YTUxN2I0YSIsImlhdCI6MTcxMzQzMzgwMCwiZXhwIjoyMDI5MDA5ODAwfQ.jVFIR-MB7rNfUuJaUH-_CyDFZEHezzXiqcRcdrGd29o' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' const storage = new StorageClient(URL, { Authorization: `Bearer ${KEY}` }) diff --git a/packages/core/storage-js/test/storageFileApiNode.test.ts b/packages/core/storage-js/test/storageFileApiNode.test.ts index c47d46da1..e2da950f0 100644 --- a/packages/core/storage-js/test/storageFileApiNode.test.ts +++ b/packages/core/storage-js/test/storageFileApiNode.test.ts @@ -6,9 +6,11 @@ import { StorageClient } from '../src/index' import * as fs from 'fs' import * as path from 'path' -const URL = 'http://localhost:8000/storage/v1' +// Supabase CLI local development defaults +const URL = 'http://127.0.0.1:54321/storage/v1' +// service_role key - bypasses RLS for testing const KEY = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aGVudGljYXRlZCIsInN1YiI6IjMxN2VhZGNlLTYzMWEtNDQyOS1hMGJiLWYxOWE3YTUxN2I0YSIsImlhdCI6MTcxMzQzMzgwMCwiZXhwIjoyMDI5MDA5ODAwfQ.jVFIR-MB7rNfUuJaUH-_CyDFZEHezzXiqcRcdrGd29o' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' const storage = new StorageClient(URL, { Authorization: `Bearer ${KEY}` }) diff --git a/packages/core/storage-js/test/supabase/.gitignore b/packages/core/storage-js/test/supabase/.gitignore new file mode 100644 index 000000000..ad9264f0b --- /dev/null +++ b/packages/core/storage-js/test/supabase/.gitignore @@ -0,0 +1,8 @@ +# Supabase +.branches +.temp + +# dotenvx +.env.keys +.env.local +.env.*.local diff --git a/packages/core/storage-js/test/supabase/config.toml b/packages/core/storage-js/test/supabase/config.toml new file mode 100644 index 000000000..8ac628155 --- /dev/null +++ b/packages/core/storage-js/test/supabase/config.toml @@ -0,0 +1,352 @@ +# For detailed configuration reference documentation, visit: +# https://supabase.com/docs/guides/local-development/cli/config +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "test" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` and `graphql_public` schemas are included by default. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[api.tls] +# Enable HTTPS endpoints locally using a self-signed certificate. +enabled = false +# Paths to self-signed certificate pair. +# cert_path = "../certs/my-cert.pem" +# key_path = "../certs/my-key.pem" + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 17 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +# [db.vault] +# secret_key = "env(SECRET_VALUE)" + +[db.migrations] +# If disabled, migrations will be skipped during a db push or reset. +enabled = true +# Specifies an ordered list of schema files that describe your database. +# Supports glob patterns relative to supabase directory: "./schemas/*.sql" +schema_paths = [] + +[db.seed] +# If enabled, seeds the database after migrations during a db reset. +enabled = true +# Specifies an ordered list of seed files to load during db reset. +# Supports glob patterns relative to supabase directory: "./seeds/*.sql" +sql_paths = ["./seed.sql"] + +[db.network_restrictions] +# Enable management of network restrictions. +enabled = false +# List of IPv4 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv4 connections. Set empty array to block all IPs. +allowed_cidrs = ["0.0.0.0/0"] +# List of IPv6 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv6 connections. Set empty array to block all IPs. +allowed_cidrs_v6 = ["::/0"] + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = false + +[inbucket] +enabled = false + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +# Uncomment to configure local storage buckets +# [storage.buckets.images] +# public = false +# file_size_limit = "50MiB" +# allowed_mime_types = ["image/png", "image/jpeg"] +# objects_path = "./images" + +# Allow connections via S3 compatible clients +[storage.s3_protocol] +enabled = true + +[storage.image_transformation] +enabled = true + +# Store analytical data in S3 for running ETL jobs over Iceberg Catalog +# This feature is only available on the hosted platform. +[storage.analytics] +enabled = false +max_namespaces = 5 +max_tables = 10 +max_catalogs = 2 + +# Analytics Buckets is available to Supabase Pro plan. +# [storage.analytics.buckets.my-warehouse] + +# Store vector embeddings in S3 for large and durable datasets +# This feature is only available on the hosted platform. +[storage.vector] +enabled = false +max_buckets = 10 +max_indexes = 5 + +# Vector Buckets is available to Supabase Pro plan. +# [storage.vector.buckets.documents-openai] + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://127.0.0.1:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://127.0.0.1:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:/auth/v1). +# jwt_issuer = "" +# Path to JWT signing key. DO NOT commit your signing keys file to git. +# signing_keys_path = "./signing_keys.json" +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = true +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" + +[auth.rate_limit] +# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. +email_sent = 2 +# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. +sms_sent = 30 +# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. +anonymous_users = 30 +# Number of sessions that can be refreshed in a 5 minute interval per IP address. +token_refresh = 150 +# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). +sign_in_sign_ups = 30 +# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. +token_verifications = 30 +# Number of Web3 logins that can be made in a 5 minute interval per IP address. +web3 = 30 + +# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. +# [auth.captcha] +# enabled = true +# provider = "hcaptcha" +# secret = "" + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# If enabled, users will need to reauthenticate or have logged in recently to change their password. +secure_password_change = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires (defaults to 1 hour). +otp_expiry = 3600 + +# Use a production-ready SMTP server +# [auth.email.smtp] +# enabled = true +# host = "smtp.sendgrid.net" +# port = 587 +# user = "apikey" +# pass = "env(SENDGRID_API_KEY)" +# admin_email = "admin@email.com" +# sender_name = "Admin" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +# Uncomment to customize notification email template +# [auth.email.notification.password_changed] +# enabled = true +# subject = "Your password has been changed" +# content_path = "./templates/password_changed_notification.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = false +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }}" +# Controls the minimum amount of time that must pass before sending another sms otp. +max_frequency = "5s" + +# Use pre-defined map of phone number to OTP for testing. +# [auth.sms.test_otp] +# 4152127777 = "123456" + +# Configure logged in session timeouts. +# [auth.sessions] +# Force log out after the specified duration. +# timebox = "24h" +# Force log out if the user has been inactive longer than the specified duration. +# inactivity_timeout = "8h" + +# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. +# [auth.hook.before_user_created] +# enabled = true +# uri = "pg-functions://postgres/auth/before-user-created-hook" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +# [auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Multi-factor-authentication is available to Supabase Pro plan. +[auth.mfa] +# Control how many MFA factors can be enrolled at once per user. +max_enrolled_factors = 10 + +# Control MFA via App Authenticator (TOTP) +[auth.mfa.totp] +enroll_enabled = false +verify_enabled = false + +# Configure MFA via Phone Messaging +[auth.mfa.phone] +enroll_enabled = false +verify_enabled = false +otp_length = 6 +template = "Your code is {{ .Code }}" +max_frequency = "5s" + +# Configure MFA via WebAuthn +# [auth.mfa.web_authn] +# enroll_enabled = true +# verify_enabled = true + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false +# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address. +email_optional = false + +# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. +# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. +[auth.web3.solana] +enabled = false + +# Use Firebase Auth as a third-party provider alongside Supabase Auth. +[auth.third_party.firebase] +enabled = false +# project_id = "my-firebase-project" + +# Use Auth0 as a third-party provider alongside Supabase Auth. +[auth.third_party.auth0] +enabled = false +# tenant = "my-auth0-tenant" +# tenant_region = "us" + +# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. +[auth.third_party.aws_cognito] +enabled = false +# user_pool_id = "my-user-pool-id" +# user_pool_region = "us-east-1" + +# Use Clerk as a third-party provider alongside Supabase Auth. +[auth.third_party.clerk] +enabled = false +# Obtain from https://clerk.com/setup/supabase +# domain = "example.clerk.accounts.dev" + +# OAuth server configuration +[auth.oauth_server] +# Enable OAuth server functionality +enabled = false +# Path for OAuth consent flow UI +authorization_url_path = "/oauth/consent" +# Allow dynamic client registration +allow_dynamic_registration = false + +[edge_runtime] +enabled = false + +[analytics] +enabled = false + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/packages/core/storage-js/test/supabase/seed.sql b/packages/core/storage-js/test/supabase/seed.sql new file mode 100644 index 000000000..ceefe8723 --- /dev/null +++ b/packages/core/storage-js/test/supabase/seed.sql @@ -0,0 +1,136 @@ +-- Seed data for storage-js integration tests +-- This file is loaded after migrations during `supabase db reset` + +-- Insert test users into auth.users +-- Note: Supabase CLI's auth schema has more columns than the old Docker setup +-- We use the minimum required columns for testing +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + raw_app_meta_data, + raw_user_meta_data, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES + ( + '00000000-0000-0000-0000-000000000000', + '317eadce-631a-4429-a0bb-f19a7a517b4a', + 'authenticated', + 'authenticated', + 'test-user1@supabase.io', + crypt('password123', gen_salt('bf')), + NOW(), + '{"provider": "email", "providers": ["email"]}', + '{}', + NOW(), + NOW(), + '', + '', + '', + '' + ), + ( + '00000000-0000-0000-0000-000000000000', + '4d56e902-f0a0-4662-8448-a4d9e643c142', + 'authenticated', + 'authenticated', + 'test-user2@supabase.io', + crypt('password123', gen_salt('bf')), + NOW(), + '{"provider": "email", "providers": ["email"]}', + '{}', + NOW(), + NOW(), + '', + '', + '', + '' + ), + ( + '00000000-0000-0000-0000-000000000000', + 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', + 'authenticated', + 'authenticated', + 'test-admin@supabase.io', + crypt('password123', gen_salt('bf')), + NOW(), + '{"provider": "email", "providers": ["email"]}', + '{}', + NOW(), + NOW(), + '', + '', + '', + '' + ); + +-- Insert test buckets (using fixed timestamp for snapshot tests) +INSERT INTO storage.buckets (id, name, owner, created_at, updated_at, public) VALUES + ('bucket2', 'bucket2', '4d56e902-f0a0-4662-8448-a4d9e643c142', '2021-02-17T04:43:32.770Z', '2021-02-17T04:43:32.770Z', false), + ('bucket3', 'bucket3', '4d56e902-f0a0-4662-8448-a4d9e643c142', '2021-02-17T04:43:32.770Z', '2021-02-17T04:43:32.770Z', false), + ('bucket4', 'bucket4', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-17T04:43:32.770Z', '2021-02-17T04:43:32.770Z', false), + ('bucket5', 'bucket5', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-17T04:43:32.770Z', '2021-02-17T04:43:32.770Z', false), + ('bucket-move', 'bucket-move', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-17T04:43:32.770Z', '2021-02-17T04:43:32.770Z', false); + +-- Insert test objects +INSERT INTO storage.objects (id, bucket_id, name, owner, created_at, updated_at, last_accessed_at, metadata) VALUES + ('03e458f9-892f-4db2-8cb9-d3401a689e25', 'bucket2', 'public/sadcat-upload23.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/svg+xml", "size": 1234}'), + ('070825af-a11d-44fe-9f1d-abdc76f686f2', 'bucket2', 'public/sadcat-upload.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('0cac5609-11e1-4f21-b486-d0eeb60909f6', 'bucket2', 'curlimage.jpg', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', NOW(), NOW(), NOW(), '{"size": 1234}'), + ('147c6795-94d5-4008-9d81-f7ba3b4f8a9f', 'bucket2', 'folder/only_uid.jpg', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', NOW(), NOW(), NOW(), '{"size": 1234}'), + ('65a3aa9c-0ff2-4adc-85d0-eab673c27443', 'bucket2', 'authenticated/casestudy.png', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', NOW(), NOW(), NOW(), '{"size": 1234}'), + ('10abe273-d77a-4bda-b410-6fc0ca3e6adc', 'bucket2', 'authenticated/cat.jpg', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', NOW(), NOW(), NOW(), '{"size": 1234}'), + ('1edccac7-0876-4e9f-89da-a08d2a5f654b', 'bucket2', 'authenticated/delete.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('1a911f3c-8c1d-4661-93c1-8e065e4d757e', 'bucket2', 'authenticated/delete1.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('372d5d74-e24d-49dc-abe8-47d7eb226a2e', 'bucket2', 'authenticated/delete-multiple1.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('34811c1b-85e5-4eb6-a5e3-d607b2f6986e', 'bucket2', 'authenticated/delete-multiple2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('45950ff2-d3a8-4add-8e49-bafc01198340', 'bucket2', 'authenticated/delete-multiple3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('469b0216-5419-41f6-9a37-2abfd7fad29c', 'bucket2', 'authenticated/delete-multiple4.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('55930619-a668-4dbc-aea3-b93dfe101e7f', 'bucket2', 'authenticated/delete-multiple7.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('d1ce4e4f-03e2-473d-858b-301d7989b581', 'bucket2', 'authenticated/move-orig.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('222b3d1e-bc17-414c-b336-47894aa4d697', 'bucket2', 'authenticated/move-orig-2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('8f7d643d-1e82-4d39-ae39-d9bd6b0cfe9c', 'bucket2', 'authenticated/move-orig-3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/png", "size": 1234}'), + ('8377527d-3518-4dc8-8290-c6926470e795', 'bucket2', 'folder/subfolder/public-all-permissions.png', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', NOW(), NOW(), NOW(), '{"size": 1234}'), + ('b39ae4ab-802b-4c42-9271-3f908c34363c', 'bucket2', 'private/sadcat-upload3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/svg+xml", "size": 1234}'), + ('8098e1ac-c744-4368-86df-71b60ccde221', 'bucket3', 'sadcat-upload3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/svg+xml", "size": 1234}'), + ('d3eb488e-94f4-46cd-86d3-242c13b95bac', 'bucket3', 'sadcat-upload2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', NOW(), NOW(), NOW(), '{"mimetype": "image/svg+xml", "size": 1234}'); + +-- RLS Policies for storage.objects +-- Note: service_role key bypasses RLS, but we add these for tests that use anon/authenticated roles + +-- Allow user 317eadce-631a-4429-a0bb-f19a7a517b4a to CRUD all buckets and objects +CREATE POLICY crud_buckets ON storage.buckets FOR ALL USING (auth.uid() = '317eadce-631a-4429-a0bb-f19a7a517b4a'::uuid); +CREATE POLICY crud_objects ON storage.objects FOR ALL USING (auth.uid() = '317eadce-631a-4429-a0bb-f19a7a517b4a'::uuid); + +-- Allow public CRUD access to the public folder in bucket2 +CREATE POLICY crud_public_folder ON storage.objects FOR ALL USING (bucket_id = 'bucket2' AND (storage.foldername(name))[1] = 'public'); + +-- Allow public CRUD access to a particular file in bucket2 +CREATE POLICY crud_public_file ON storage.objects FOR ALL USING (bucket_id = 'bucket2' AND name = 'folder/subfolder/public-all-permissions.png'); + +-- Allow public CRUD access to a folder in bucket2 to a user with a given id +CREATE POLICY crud_uid_folder ON storage.objects FOR ALL USING (bucket_id = 'bucket2' AND (storage.foldername(name))[1] = 'only_uid' AND auth.uid() = 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2'::uuid); + +-- Allow public CRUD access to a file in bucket2 to a user with a given id +CREATE POLICY crud_uid_file ON storage.objects FOR ALL USING (bucket_id = 'bucket2' AND name = 'folder/only_uid.jpg' AND auth.uid() = 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2'::uuid); + +-- Allow CRUD access to a folder in bucket2 to all authenticated users +CREATE POLICY authenticated_folder ON storage.objects FOR ALL USING (bucket_id = 'bucket2' AND (storage.foldername(name))[1] = 'authenticated' AND auth.role() = 'authenticated'); + +-- Allow CRUD access to a folder in bucket2 to its owners +CREATE POLICY crud_owner_only ON storage.objects FOR ALL USING (bucket_id = 'bucket2' AND (storage.foldername(name))[1] = 'only_owner' AND owner_id = auth.uid()::text); + +-- Allow CRUD access to bucket4 +CREATE POLICY open_all_update ON storage.objects FOR ALL WITH CHECK (bucket_id = 'bucket4'); + +-- Allow CRUD access to my-private-bucket for specific user +CREATE POLICY crud_my_bucket ON storage.objects FOR ALL USING (bucket_id = 'my-private-bucket' AND auth.uid() = '317eadce-631a-4429-a0bb-f19a7a517b4a'::uuid); From f184454789f700f6ebb4e58baf596919265d9097 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Mon, 22 Dec 2025 18:19:43 +0200 Subject: [PATCH 2/3] test(storage): configure task dependency with nx --- nx.json | 17 +++++++++++ packages/core/storage-js/package.json | 4 --- packages/core/storage-js/project.json | 44 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 packages/core/storage-js/project.json diff --git a/nx.json b/nx.json index 24ff0dbd3..eee031740 100644 --- a/nx.json +++ b/nx.json @@ -122,6 +122,23 @@ "cache": true, "outputs": ["{projectRoot}/coverage"] }, + "test:storage": { + "cache": false + }, + "test:suite": { + "inputs": ["testing", "^production"], + "cache": false, + "outputs": ["{projectRoot}/coverage"] + }, + "test:infra": { + "cache": false + }, + "test:clean-pre": { + "cache": false + }, + "test:clean-post": { + "cache": false + }, "vite:test": { "inputs": ["testing", "^production"], "cache": true, diff --git a/packages/core/storage-js/package.json b/packages/core/storage-js/package.json index c58afa394..801979309 100644 --- a/packages/core/storage-js/package.json +++ b/packages/core/storage-js/package.json @@ -41,10 +41,6 @@ "scripts": { "build": "tsdown", "build:watch": "tsdown --watch", - "test:storage": "npx nx test:clean storage-js && npx nx test:infra storage-js && npx nx test:suite storage-js && npx nx test:clean storage-js", - "test:suite": "jest --runInBand --coverage", - "test:infra": "cd test && supabase start && supabase db reset", - "test:clean": "cd test && supabase stop --no-backup || true", "docs": "typedoc --entryPoints src/index.ts --out docs/v2 --entryPoints src/packages/* --excludePrivate --excludeProtected", "docs:json": "typedoc --json docs/v2/spec.json --entryPoints src/index.ts --entryPoints src/packages/* --excludePrivate --excludeExternals --excludeProtected" }, diff --git a/packages/core/storage-js/project.json b/packages/core/storage-js/project.json new file mode 100644 index 000000000..cb7674390 --- /dev/null +++ b/packages/core/storage-js/project.json @@ -0,0 +1,44 @@ +{ + "name": "@supabase/storage-js", + "targets": { + "test:clean-pre": { + "executor": "nx:run-commands", + "options": { + "command": "supabase stop --no-backup || true", + "cwd": "{projectRoot}/test" + } + }, + "test:infra": { + "executor": "nx:run-commands", + "options": { + "command": "supabase start && supabase db reset", + "cwd": "{projectRoot}/test" + }, + "dependsOn": ["test:clean-pre"] + }, + "test:suite": { + "executor": "nx:run-commands", + "options": { + "command": "jest --runInBand --coverage", + "cwd": "{projectRoot}" + }, + "dependsOn": ["test:infra"] + }, + "test:clean-post": { + "executor": "nx:run-commands", + "options": { + "command": "supabase stop --no-backup || true", + "cwd": "{projectRoot}/test" + }, + "dependsOn": ["test:suite"] + }, + "test:storage": { + "executor": "nx:run-commands", + "options": { + "command": "echo 'Storage tests complete'", + "cwd": "{projectRoot}" + }, + "dependsOn": ["test:clean-post"] + } + } +} From b75513f34b0b75a3ff1f620c3c150c279c863436 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Mon, 22 Dec 2025 18:23:09 +0200 Subject: [PATCH 3/3] test(storage): change coverage dir --- packages/core/storage-js/jest.config.js | 2 +- packages/core/storage-js/project.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/storage-js/jest.config.js b/packages/core/storage-js/jest.config.js index dc0de408e..0dea75ca6 100644 --- a/packages/core/storage-js/jest.config.js +++ b/packages/core/storage-js/jest.config.js @@ -2,7 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true, - coverageDirectory: './test/coverage', + coverageDirectory: './coverage', coverageReporters: ['lcov'], collectCoverageFrom: [ './src/**/*.{js,ts}', diff --git a/packages/core/storage-js/project.json b/packages/core/storage-js/project.json index cb7674390..96faab417 100644 --- a/packages/core/storage-js/project.json +++ b/packages/core/storage-js/project.json @@ -22,6 +22,7 @@ "command": "jest --runInBand --coverage", "cwd": "{projectRoot}" }, + "outputs": ["{projectRoot}/coverage"], "dependsOn": ["test:infra"] }, "test:clean-post": {