diff --git a/docker/.env b/docker/.env deleted file mode 100644 index b68b901a188..00000000000 --- a/docker/.env +++ /dev/null @@ -1,29 +0,0 @@ -# fill with random passwords -OPERATOR_TOKEN=your-super-secret-operator-token - -JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long - -POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password - -# some SMTP server to send your auth-mails with -SMTP_HOST=smtp.mailgun.org -SMTP_PORT=25 -SMTP_USER= -SMTP_PASS= -SMTP_ADMIN_EMAIL= - -# storage config -STORAGE_BACKEND=file # file | s3 -STORAGE_S3_BUCKET=supa-storage-testing # name of s3 bucket where you want to store objects -STORAGE_REGION=us-east-1 # region where your bucket is located -# STORAGE_AWS_ACCESS_KEY_ID=XXXX # replace-with-your-aws-key and don't commit this to github -# STORAGE_AWS_SECRET_ACCESS_KEY=XXXX # replace-with-your-aws-key and don't commit this to github -FILE_SIZE_LIMIT=52428800 # max file size (in bytes) - -# predefined; don't change these unless you know what you're doing -POSTGRES_PORT=5432 -AUTH_PORT=9999 -REST_PORT=3000 -REALTIME_PORT=4000 -KONG_PORT=8000 -KONG_PORT_TLS=8443 diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 00000000000..bc259467a40 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,35 @@ +# Secrets + +POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password +JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long + +# Auth + +## General +SITE_URL=http://localhost:3000 +ADDITIONAL_REDIRECT_URLS= +JWT_EXPIRY=3600 +DISABLE_SIGNUP=false + +## Email auth +ENABLE_EMAIL_SIGNUP=true +ENABLE_EMAIL_AUTOCONFIRM=false +SMTP_ADMIN_EMAIL= +SMTP_HOST= +SMTP_PORT= +SMTP_USER= +SMTP_PASS= +SMTP_SENDER_NAME= + +## Phone auth +ENABLE_PHONE_SIGNUP=false +ENABLE_PHONE_AUTOCONFIRM=false + +# Ports + +## API endpoint ports +KONG_HTTP_PORT=8000 +KONG_HTTPS_PORT=8443 + +## DB port +POSTGRES_PORT=5432 diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 00000000000..e1343f9a2bc --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,3 @@ +volumes/db/data +volumes/storage +.env diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 85d03c579aa..42db78e9b96 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,118 +1,115 @@ -version: '3.6' services: kong: container_name: supabase-kong - build: - context: ./dockerfiles/kong - environment: - KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml - KONG_PLUGINS: request-transformer,cors,key-auth,http-log + image: kong:2.1 + restart: unless-stopped ports: - - ${KONG_PORT}:8000/tcp - - ${KONG_PORT_TLS}:8443/tcp + - ${KONG_HTTP_PORT}:8000/tcp + - ${KONG_HTTPS_PORT}:8443/tcp + volumes: + - ./volumes/kong.yml:/var/lib/kong/kong.yml + environment: + KONG_DATABASE: "off" + KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml + # https://github.com/supabase/cli/issues/14 + KONG_DNS_ORDER: LAST,A,CNAME + KONG_PLUGINS: request-transformer,cors,key-auth auth: container_name: supabase-auth - image: supabase/gotrue:latest - ports: - - ${AUTH_PORT} + image: supabase/gotrue:v2.1.8 depends_on: - db - restart: always + restart: unless-stopped environment: - GOTRUE_JWT_SECRET: ${JWT_SECRET} - GOTRUE_JWT_EXP: 3600 - GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated - GOTRUE_DB_DRIVER: postgres - DB_NAMESPACE: auth - API_EXTERNAL_URL: http://localhost:8000 GOTRUE_API_HOST: 0.0.0.0 - PORT: ${AUTH_PORT} + GOTRUE_API_PORT: 9999 + GOTRUE_DB_DRIVER: postgres + GOTRUE_DB_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres?sslmode=disable&search_path=auth + + GOTRUE_SITE_URL: ${SITE_URL} + GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS} + GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP} + + GOTRUE_JWT_SECRET: ${JWT_SECRET} + GOTRUE_JWT_EXP: ${JWT_EXPIRY} + GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + + GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP} + GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM} + GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL} GOTRUE_SMTP_HOST: ${SMTP_HOST} GOTRUE_SMTP_PORT: ${SMTP_PORT} GOTRUE_SMTP_USER: ${SMTP_USER} GOTRUE_SMTP_PASS: ${SMTP_PASS} - GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL} - - GOTRUE_DISABLE_SIGNUP: 'false' - GOTRUE_SITE_URL: http://localhost:8000 + GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME} GOTRUE_MAILER_URLPATHS_INVITE: /auth/v1/verify GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/v1/verify GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify - GOTRUE_MAILER_AUTOCONFIRM: 'true' - GOTRUE_LOG_LEVEL: DEBUG - GOTRUE_OPERATOR_TOKEN: ${OPERATOR_TOKEN} - DATABASE_URL: 'postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres?sslmode=disable&search_path=auth' + GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify + GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP} + GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM} rest: container_name: supabase-rest - image: postgrest/postgrest:latest - ports: - - ${REST_PORT}:3000 + image: postgrest/postgrest:v8.0.0 depends_on: - db - restart: always + restart: unless-stopped environment: - PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres + PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres PGRST_DB_SCHEMA: public, storage PGRST_DB_ANON_ROLE: anon PGRST_JWT_SECRET: ${JWT_SECRET} - realtime: container_name: supabase-realtime - image: supabase/realtime:latest - ports: - - ${REALTIME_PORT} + image: supabase/realtime:v0.15.0 depends_on: - db - restart: on-failure + restart: unless-stopped environment: DB_HOST: db + DB_PORT: 5432 DB_NAME: postgres DB_USER: postgres DB_PASSWORD: ${POSTGRES_PASSWORD} - DB_PORT: ${POSTGRES_PORT} - PORT: ${REALTIME_PORT} - HOSTNAME: localhost - # Disable JWT Auth locally. The JWT_SECRET will be ignored. - SECURE_CHANNELS: 'false' + SLOT_NAME: supabase_realtime + PORT: 4000 + SECURE_CHANNELS: "true" JWT_SECRET: ${JWT_SECRET} - storage: - image: supabase/storage-api:v0.9.1 - ports: - - '5000:5000' + container_name: supabase-storage + image: supabase/storage-api:v0.9.3 depends_on: - - db - - rest - restart: always + - db + - rest + restart: unless-stopped + volumes: + - ./volumes/storage:/var/lib/storage environment: - ANON_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYyNzIwODU0MCwiZXhwIjoxOTc0MzYzNzQwLCJhdWQiOiIiLCJzdWIiOiIiLCJyb2xlIjoiYW5vbiJ9.sUHErUOiKZ3nHQIxy-7jND6B80Uzf9G4NtMLmL6HXPQ - SERVICE_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYyNzIwNzUzMiwiZXhwIjoxNjkwMjc5NTMyLCJhdWQiOiIiLCJzdWIiOiIiLCJyb2xlIjoic2VydmljZV9yb2xlIn0.hfdXFZV5PdvUdo2xK0vStb1i97GJukSkRqfwd4YIh2M - PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string - POSTGREST_URL: http://rest:3000 - PGRST_JWT_SECRET: ${JWT_SECRET} - DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres - PGOPTIONS: "-c search_path=storage" - FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT} - REGION: ${STORAGE_REGION} # region where your bucket is located - GLOBAL_S3_BUCKET: ${STORAGE_S3_BUCKET} # name of s3 bucket where you want to store objects - # AWS_ACCESS_KEY_ID: replace-with-your-aws-key - # AWS_SECRET_ACCESS_KEY: replace-with-your-aws-secret - STORAGE_BACKEND: ${STORAGE_BACKEND} - FILE_STORAGE_BACKEND_PATH: /var/data/storage + ANON_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYyNzIwODU0MCwiZXhwIjoxOTc0MzYzNzQwfQ.zcaQfHd3VA7XgJmdGfmV86OLVJT9s2MTmSy-e69BpUY + SERVICE_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjI3MjA4NTQwLCJleHAiOjE5NzQzNjM3NDB9.pkT3PNpO4DtO45Ac5HK_TKCx8sGLgNtV__pr_ZrRSAU + POSTGREST_URL: http://rest:3000 + PGRST_JWT_SECRET: ${JWT_SECRET} + DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres + PGOPTIONS: -c search_path=storage + FILE_SIZE_LIMIT: 52428800 + STORAGE_BACKEND: file + FILE_STORAGE_BACKEND_PATH: /var/lib/storage + # TODO: https://github.com/supabase/storage-api/commit/a836fc9666c2434d89ca4b31402f74772d50fb6d + PROJECT_REF: stub + # TODO: https://github.com/supabase/storage-api/issues/55 + REGION: stub + GLOBAL_S3_BUCKET: stub db: container_name: supabase-db - build: - context: ./dockerfiles/postgres + image: supabase/postgres:13.3.0 + restart: unless-stopped ports: - - ${POSTGRES_PORT}:${POSTGRES_PORT} - command: - - postgres - - -c - - wal_level=logical + - ${POSTGRES_PORT}:5432 + volumes: + - ./volumes/db/data:/var/lib/postgresql/data + - ./volumes/db/init:/docker-entrypoint-initdb.d environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_PORT: ${POSTGRES_PORT} + command: postgres -c wal_level=logical diff --git a/docker/dockerfiles/kong/Dockerfile b/docker/dockerfiles/kong/Dockerfile deleted file mode 100644 index 8fa064d13de..00000000000 --- a/docker/dockerfiles/kong/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM kong:2.1 - -COPY --chown=kong:nogroup 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/docker/dockerfiles/kong/kong.yml b/docker/dockerfiles/kong/kong.yml deleted file mode 100644 index d645605f24b..00000000000 --- a/docker/dockerfiles/kong/kong.yml +++ /dev/null @@ -1,85 +0,0 @@ -_format_version: "1.1" -services: -- name: auth-v1-open - url: http://auth:9999/verify - routes: - - name: auth-v1-open - strip_path: true - paths: - - /auth/v1/verify - plugins: - - name: cors -- name: auth-v1-open-callback - url: http://auth:9999/callback - routes: - - name: auth-v1-open-callback - strip_path: true - paths: - - /auth/v1/callback - plugins: - - name: cors -- name: auth-v1-open-authorize - url: http://auth:9999/authorize - routes: - - name: auth-v1-open-authorize - strip_path: true - paths: - - /auth/v1/authorize - plugins: - - name: cors -- name: auth-v1 - _comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*' - url: http://auth:9999/ - routes: - - name: auth-v1-all - strip_path: true - paths: - - /auth/v1/ - plugins: - - name: cors - - name: key-auth - config: - hide_credentials: true -- name: rest-v1 - _comment: 'PostgREST: /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: realtime-v1 - _comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*' - url: http://realtime:4000/socket/ - routes: - - name: realtime-v1-all - strip_path: true - paths: - - /realtime/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: 'anon-key' - keyauth_credentials: - - key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYyNzIwODU0MCwiZXhwIjoxOTc0MzYzNzQwLCJhdWQiOiIiLCJzdWIiOiIiLCJyb2xlIjoiYW5vbiJ9.sUHErUOiKZ3nHQIxy-7jND6B80Uzf9G4NtMLmL6HXPQ -- username: 'service-key' - keyauth_credentials: - - key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYyNzIwNzUzMiwiZXhwIjoxNjkwMjc5NTMyLCJhdWQiOiIiLCJzdWIiOiIiLCJyb2xlIjoic2VydmljZV9yb2xlIn0.hfdXFZV5PdvUdo2xK0vStb1i97GJukSkRqfwd4YIh2M \ No newline at end of file diff --git a/docker/dockerfiles/postgres/00-initial-schema.sql b/docker/dockerfiles/postgres/00-initial-schema.sql deleted file mode 100644 index 98a5910f564..00000000000 --- a/docker/dockerfiles/postgres/00-initial-schema.sql +++ /dev/null @@ -1,30 +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; - -alter role anon set statement_timeout = '3s'; -alter role authenticated set statement_timeout = '8s'; - -ALTER ROLE postgres SET search_path = "$user", public, auth, extensions; diff --git a/docker/dockerfiles/postgres/Dockerfile b/docker/dockerfiles/postgres/Dockerfile deleted file mode 100644 index 3a073874fee..00000000000 --- a/docker/dockerfiles/postgres/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM supabase/postgres:13.3.0 - -COPY --chown=postgres 00-initial-schema.sql /docker-entrypoint-initdb.d/00-initial-schema.sql -COPY --chown=postgres auth-schema.sql /docker-entrypoint-initdb.d/auth-schema.sql -COPY --chown=postgres storage-schema.sql /docker-entrypoint-initdb.d/storage-schema.sql - -# Build time defaults -ARG build_POSTGRES_DB=postgres -ARG build_POSTGRES_USER=postgres -ARG build_POSTGRES_PASSWORD=postgres -ARG build_POSTGRES_PORT=5432 - -# Run time values -ENV POSTGRES_DB=$build_POSTGRES_DB -ENV POSTGRES_USER=$build_POSTGRES_USER -ENV POSTGRES_PASSWORD=$build_POSTGRES_PASSWORD -ENV POSTGRES_PORT=$build_POSTGRES_PORT - -EXPOSE 5432 diff --git a/docker/volumes/db/init/00-initial-schema.sql b/docker/volumes/db/init/00-initial-schema.sql new file mode 100644 index 00000000000..f3e63542a92 --- /dev/null +++ b/docker/volumes/db/init/00-initial-schema.sql @@ -0,0 +1,47 @@ +-- Set up reatime +-- create publication supabase_realtime; -- defaults to empty publication +create publication supabase_realtime; + +-- Supabase super admin +create user supabase_admin; +alter user supabase_admin with superuser createdb createrole replication bypassrls; + +-- Extension namespacing +create SCHEMA IF NOT exists 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; + +-- Set up auth roles for the developer +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 supabase_admin 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; + +-- Allow Extensions to be used in the API +grant usage on schema extensions to postgres, anon, authenticated, service_role; + +-- Set up namespacing +alter user supabase_admin SET search_path TO public, extensions; -- don't include the "auth" schema + +-- These are required so that the users receive grants whenever "supabase_admin" creates tables/function +alter default privileges for user supabase_admin in schema public grant all + on sequences to postgres, anon, authenticated, service_role; +alter default privileges for user supabase_admin in schema public grant all + on tables to postgres, anon, authenticated, service_role; +alter default privileges for user supabase_admin in schema public grant all + on functions to postgres, anon, authenticated, service_role; + +-- Set short statement/query timeouts for API roles +alter role anon set statement_timeout = '3s'; +alter role authenticated set statement_timeout = '8s'; diff --git a/docker/dockerfiles/postgres/auth-schema.sql b/docker/volumes/db/init/01-auth-schema.sql similarity index 68% rename from docker/dockerfiles/postgres/auth-schema.sql rename to docker/volumes/db/init/01-auth-schema.sql index b83e1ff4e35..9ad1054febf 100644 --- a/docker/dockerfiles/postgres/auth-schema.sql +++ b/docker/volumes/db/init/01-auth-schema.sql @@ -1,13 +1,14 @@ -CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION postgres; +CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_admin; -- auth.users definition + CREATE TABLE auth.users ( instance_id uuid NULL, - id uuid NOT NULL, + id uuid NOT NULL UNIQUE, aud varchar(255) NULL, "role" varchar(255) NULL, - email varchar(255) NULL, + email varchar(255) NULL UNIQUE, encrypted_password varchar(255) NULL, confirmed_at timestamptz NULL, invited_at timestamptz NULL, @@ -28,7 +29,10 @@ CREATE TABLE auth.users ( ); 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); +comment on table auth.users is 'Auth: Stores user login data within a secure schema.'; + -- auth.refresh_tokens definition + CREATE TABLE auth.refresh_tokens ( instance_id uuid NULL, id bigserial NOT NULL, @@ -42,7 +46,10 @@ CREATE TABLE auth.refresh_tokens ( 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); +comment on table auth.refresh_tokens is 'Auth: Store of tokens used to refresh JWT tokens once they expire.'; + -- auth.instances definition + CREATE TABLE auth.instances ( id uuid NOT NULL, uuid uuid NULL, @@ -51,7 +58,10 @@ CREATE TABLE auth.instances ( updated_at timestamptz NULL, CONSTRAINT instances_pkey PRIMARY KEY (id) ); +comment on table auth.instances is 'Auth: Manages users across multiple sites.'; + -- auth.audit_log_entries definition + CREATE TABLE auth.audit_log_entries ( instance_id uuid NULL, id uuid NOT NULL, @@ -60,11 +70,16 @@ CREATE TABLE auth.audit_log_entries ( CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id) ); CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id); +comment on table auth.audit_log_entries is 'Auth: Audit trail for user actions.'; + -- auth.schema_migrations definition + CREATE TABLE auth.schema_migrations ( "version" varchar(255) NOT NULL, CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version") ); +comment on table auth.schema_migrations is 'Auth: Manages updates to the auth system.'; + INSERT INTO auth.schema_migrations (version) VALUES ('20171026211738'), ('20171026211808'), @@ -73,20 +88,33 @@ VALUES ('20171026211738'), ('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 + +-- 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; --- Gets the User Email from the request cookie + +-- Gets the User email 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; +-- usage on auth functions to API roles GRANT USAGE ON SCHEMA auth TO anon, authenticated, service_role; + +-- Supabase super admin +CREATE USER supabase_auth_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION; +GRANT ALL PRIVILEGES ON SCHEMA auth TO supabase_auth_admin; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO supabase_auth_admin; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO supabase_auth_admin; +ALTER USER supabase_auth_admin SET search_path = "auth"; +ALTER table "auth".users OWNER TO supabase_auth_admin; +ALTER table "auth".refresh_tokens OWNER TO supabase_auth_admin; +ALTER table "auth".audit_log_entries OWNER TO supabase_auth_admin; +ALTER table "auth".instances OWNER TO supabase_auth_admin; +ALTER table "auth".schema_migrations OWNER TO supabase_auth_admin; diff --git a/docker/dockerfiles/postgres/storage-schema.sql b/docker/volumes/db/init/02-storage-schema.sql similarity index 56% rename from docker/dockerfiles/postgres/storage-schema.sql rename to docker/volumes/db/init/02-storage-schema.sql index c879c6b6234..ba891b018b7 100644 --- a/docker/dockerfiles/postgres/storage-schema.sql +++ b/docker/volumes/db/init/02-storage-schema.sql @@ -1,11 +1,10 @@ -CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION postgres; +CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION supabase_admin; 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; -DROP TABLE IF EXISTS "storage"."buckets"; CREATE TABLE "storage"."buckets" ( "id" text not NULL, "name" text NOT NULL, @@ -17,7 +16,6 @@ CREATE TABLE "storage"."buckets" ( ); CREATE UNIQUE INDEX "bname" ON "storage"."buckets" USING BTREE ("name"); -DROP TABLE IF EXISTS "storage"."objects"; CREATE TABLE "storage"."objects" ( "id" uuid NOT NULL DEFAULT extensions.uuid_generate_v4(), "bucket_id" text, @@ -36,7 +34,7 @@ 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) +CREATE FUNCTION storage.foldername(name text) RETURNS text[] LANGUAGE plpgsql AS $function$ @@ -48,7 +46,7 @@ BEGIN END $function$; -CREATE OR REPLACE FUNCTION storage.filename(name text) +CREATE FUNCTION storage.filename(name text) RETURNS text LANGUAGE plpgsql AS $function$ @@ -60,7 +58,7 @@ BEGIN END $function$; -CREATE OR REPLACE FUNCTION storage.extension(name text) +CREATE FUNCTION storage.extension(name text) RETURNS text LANGUAGE plpgsql AS $function$ @@ -70,11 +68,12 @@ _filename text; BEGIN select string_to_array(name, '/') into _parts; select _parts[array_length(_parts,1)] into _filename; + -- @todo return the last part instead of 2 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) +CREATE 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, @@ -88,24 +87,30 @@ AS $function$ DECLARE _bucketId text; BEGIN - select buckets."id" from buckets where buckets.name=bucketname limit 1 into _bucketId; - 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 = _bucketId - 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 - where objects.id is null or objects.bucket_id=_bucketId; + -- will be replaced by migrations when server starts + -- saving space for cloud-init 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 +-- create migrations table +-- https://github.com/ThomWright/postgres-migrations/blob/master/src/migrations/0_create-migrations-table.sql +-- we add this table here and not let it be auto-created so that the permissions are properly applied to it +CREATE TABLE IF NOT EXISTS storage.migrations ( + id integer PRIMARY KEY, + name varchar(100) UNIQUE NOT NULL, + hash varchar(40) NOT NULL, -- sha1 hex encoded hash of the file name and contents, to ensure it hasn't been altered since applying the migration + executed_at timestamp DEFAULT current_timestamp +); + +CREATE USER supabase_storage_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION; +GRANT ALL PRIVILEGES ON SCHEMA storage TO supabase_storage_admin; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO supabase_storage_admin; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO supabase_storage_admin; +ALTER USER supabase_storage_admin SET search_path = "storage"; +ALTER table "storage".objects owner to supabase_storage_admin; +ALTER table "storage".buckets owner to supabase_storage_admin; +ALTER table "storage".migrations OWNER TO supabase_storage_admin; +ALTER function "storage".foldername(text) owner to supabase_storage_admin; +ALTER function "storage".filename(text) owner to supabase_storage_admin; +ALTER function "storage".extension(text) owner to supabase_storage_admin; +ALTER function "storage".search(text,text,int,int,int) owner to supabase_storage_admin; diff --git a/docker/volumes/db/init/03-post-setup.sql b/docker/volumes/db/init/03-post-setup.sql new file mode 100644 index 00000000000..1304ae6ae09 --- /dev/null +++ b/docker/volumes/db/init/03-post-setup.sql @@ -0,0 +1,68 @@ +ALTER ROLE postgres SET search_path TO "\$user",public,extensions; +CREATE OR REPLACE FUNCTION extensions.notify_api_restart() +RETURNS event_trigger +LANGUAGE plpgsql +AS $$ +BEGIN + NOTIFY ddl_command_end; +END; +$$; +CREATE EVENT TRIGGER api_restart ON ddl_command_end +EXECUTE PROCEDURE extensions.notify_api_restart(); +COMMENT ON FUNCTION extensions.notify_api_restart IS 'Sends a notification to the API to restart. If your database schema has changed, this is required so that Supabase can rebuild the relationships.'; + +-- Trigger for pg_cron +CREATE OR REPLACE FUNCTION extensions.grant_pg_cron_access() +RETURNS event_trigger +LANGUAGE plpgsql +AS $$ +DECLARE + schema_is_cron bool; +BEGIN + schema_is_cron = ( + SELECT n.nspname = 'cron' + FROM pg_event_trigger_ddl_commands() AS ev + LEFT JOIN pg_catalog.pg_namespace AS n + ON ev.objid = n.oid + ); + + IF schema_is_cron + THEN + grant usage on schema cron to postgres with grant option; + + alter default privileges in schema cron grant all on tables to postgres with grant option; + alter default privileges in schema cron grant all on functions to postgres with grant option; + alter default privileges in schema cron grant all on sequences to postgres with grant option; + + alter default privileges for user supabase_admin in schema cron grant all + on sequences to postgres with grant option; + alter default privileges for user supabase_admin in schema cron grant all + on tables to postgres with grant option; + alter default privileges for user supabase_admin in schema cron grant all + on functions to postgres with grant option; + + grant all privileges on all tables in schema cron to postgres with grant option; + + END IF; + +END; +$$; +CREATE EVENT TRIGGER issue_pg_cron_access ON ddl_command_end WHEN TAG in ('CREATE SCHEMA') +EXECUTE PROCEDURE extensions.grant_pg_cron_access(); +COMMENT ON FUNCTION extensions.grant_pg_cron_access IS 'Grants access to pg_cron'; + +-- Supabase dashboard user +CREATE ROLE dashboard_user NOSUPERUSER CREATEDB CREATEROLE REPLICATION; +GRANT ALL ON DATABASE postgres TO dashboard_user; +GRANT ALL ON SCHEMA auth TO dashboard_user; +GRANT ALL ON SCHEMA extensions TO dashboard_user; +GRANT ALL ON SCHEMA storage TO dashboard_user; +GRANT ALL ON ALL TABLES IN SCHEMA auth TO dashboard_user; +GRANT ALL ON ALL TABLES IN SCHEMA extensions TO dashboard_user; +-- GRANT ALL ON ALL TABLES IN SCHEMA storage TO dashboard_user; +GRANT ALL ON ALL SEQUENCES IN SCHEMA auth TO dashboard_user; +GRANT ALL ON ALL SEQUENCES IN SCHEMA storage TO dashboard_user; +GRANT ALL ON ALL SEQUENCES IN SCHEMA extensions TO dashboard_user; +GRANT ALL ON ALL ROUTINES IN SCHEMA auth TO dashboard_user; +GRANT ALL ON ALL ROUTINES IN SCHEMA storage TO dashboard_user; +GRANT ALL ON ALL ROUTINES IN SCHEMA extensions TO dashboard_user; diff --git a/docker/volumes/kong.yml b/docker/volumes/kong.yml new file mode 100644 index 00000000000..09b156fd20b --- /dev/null +++ b/docker/volumes/kong.yml @@ -0,0 +1,85 @@ +_format_version: "1.1" +services: + - name: auth-v1-open + url: http://auth:9999/verify + routes: + - name: auth-v1-open + strip_path: true + paths: + - /auth/v1/verify + plugins: + - name: cors + - name: auth-v1-open-callback + url: http://auth:9999/callback + routes: + - name: auth-v1-open-callback + strip_path: true + paths: + - /auth/v1/callback + plugins: + - name: cors + - name: auth-v1-open-authorize + url: http://auth:9999/authorize + routes: + - name: auth-v1-open-authorize + strip_path: true + paths: + - /auth/v1/authorize + plugins: + - name: cors + - name: auth-v1 + _comment: "GoTrue: /auth/v1/* -> http://auth:9999/*" + url: http://auth:9999/ + routes: + - name: auth-v1-all + strip_path: true + paths: + - /auth/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: rest-v1 + _comment: "PostgREST: /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: realtime-v1 + _comment: "Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*" + url: http://realtime:4000/socket/ + routes: + - name: realtime-v1-all + strip_path: true + paths: + - /realtime/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - 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: anon + keyauth_credentials: + - key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYyNzIwODU0MCwiZXhwIjoxOTc0MzYzNzQwfQ.zcaQfHd3VA7XgJmdGfmV86OLVJT9s2MTmSy-e69BpUY + - username: service_role + keyauth_credentials: + - key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjI3MjA4NTQwLCJleHAiOjE5NzQzNjM3NDB9.pkT3PNpO4DtO45Ac5HK_TKCx8sGLgNtV__pr_ZrRSAU