diff --git a/api/beta.yaml b/api/beta.yaml index 257a46e84..cd6c20a15 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -118,7 +118,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/V1ProjectResponse' + $ref: '#/components/schemas/V1ProjectWithDatabaseResponse' tags: - Projects security: @@ -132,7 +132,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V1CreateProjectBody' + $ref: '#/components/schemas/V1CreateProjectBodyDto' responses: '201': description: '' @@ -174,7 +174,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateOrganizationBodyV1' + $ref: '#/components/schemas/CreateOrganizationV1Dto' responses: '201': description: '' @@ -331,6 +331,11 @@ paths: minLength: 20 maxLength: 20 type: string + - name: reveal + required: true + in: query + schema: + type: boolean responses: '200': description: '' @@ -356,6 +361,11 @@ paths: minLength: 20 maxLength: 20 type: string + - name: reveal + required: true + in: query + schema: + type: boolean requestBody: required: true content: @@ -391,6 +401,11 @@ paths: in: path schema: type: string + - name: reveal + required: true + in: query + schema: + type: boolean requestBody: required: true content: @@ -408,6 +423,39 @@ paths: - Secrets security: - bearer: [] + get: + operationId: getApiKey + summary: '[Alpha] Get API key' + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + - name: id + required: true + in: path + schema: + type: string + - name: reveal + required: true + in: query + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyResponse' + tags: + - Secrets + security: + - bearer: [] delete: operationId: deleteApiKey summary: '[Alpha] Deletes an API key for the project' @@ -425,6 +473,11 @@ paths: in: path schema: type: string + - name: reveal + required: true + in: query + schema: + type: boolean responses: '200': description: '' @@ -927,7 +980,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V1ProjectResponse' + $ref: '#/components/schemas/V1ProjectWithDatabaseResponse' '500': description: Failed to retrieve project tags: @@ -2012,6 +2065,13 @@ paths: in: query schema: type: string + - name: compute_multiplier + required: false + in: query + schema: + minimum: 1 + maximum: 4 + type: number requestBody: required: true content: @@ -2153,6 +2213,13 @@ paths: in: query schema: type: string + - name: compute_multiplier + required: false + in: query + schema: + minimum: 1 + maximum: 4 + type: number requestBody: required: true content: @@ -2728,7 +2795,7 @@ components: - version - postgres_engine - release_channel - V1ProjectResponse: + V1ProjectWithDatabaseResponse: type: object properties: id: @@ -2774,36 +2841,9 @@ components: - name - region - created_at + - database - status - DesiredInstanceSize: - type: string - enum: - - micro - - small - - medium - - large - - xlarge - - 2xlarge - - 4xlarge - - 8xlarge - - 12xlarge - - 16xlarge - ReleaseChannel: - type: string - enum: - - internal - - alpha - - beta - - ga - - withdrawn - PostgresEngine: - type: string - description: >- - Postgres engine version. If not provided, the latest version will be - used. - enum: - - '15' - V1CreateProjectBody: + V1CreateProjectBodyDto: type: object properties: db_pass: @@ -2820,13 +2860,13 @@ components: enum: - free - pro + deprecated: true description: >- Subscription Plan is now set on organization level and is ignored in this request - example: free - deprecated: true region: type: string + description: Region you want your server to reside in enum: - us-east-1 - us-east-2 @@ -2846,28 +2886,96 @@ components: - ca-central-1 - ap-south-1 - sa-east-1 - description: Region you want your server to reside in - example: us-east-1 kps_enabled: type: boolean deprecated: true description: This field is deprecated and is ignored in this request desired_instance_size: - $ref: '#/components/schemas/DesiredInstanceSize' + type: string + enum: + - micro + - small + - medium + - large + - xlarge + - 2xlarge + - 4xlarge + - 8xlarge + - 12xlarge + - 16xlarge template_url: type: string + format: uri description: Template URL used to create the project from the CLI. example: >- https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone release_channel: - $ref: '#/components/schemas/ReleaseChannel' + type: string + enum: + - internal + - alpha + - beta + - ga + - withdrawn + description: Release channel. If not provided, GA will be used. postgres_engine: - $ref: '#/components/schemas/PostgresEngine' + type: string + enum: + - '15' + description: >- + Postgres engine version. If not provided, the latest version will be + used. required: - db_pass - name - organization_id - region + additionalProperties: false + V1ProjectResponse: + type: object + properties: + id: + type: string + description: Id of your project + organization_id: + type: string + description: Slug of your organization + name: + type: string + description: Name of your project + region: + type: string + description: Region of your project + example: us-east-1 + created_at: + type: string + description: Creation timestamp + example: '2023-03-29T16:32:59Z' + status: + enum: + - ACTIVE_HEALTHY + - ACTIVE_UNHEALTHY + - COMING_UP + - GOING_DOWN + - INACTIVE + - INIT_FAILED + - REMOVED + - RESTARTING + - UNKNOWN + - UPGRADING + - PAUSING + - RESTORING + - RESTORE_FAILED + - PAUSE_FAILED + - RESIZING + type: string + required: + - id + - organization_id + - name + - region + - created_at + - status OrganizationResponseV1: type: object properties: @@ -2878,13 +2986,14 @@ components: required: - id - name - CreateOrganizationBodyV1: + CreateOrganizationV1Dto: type: object properties: name: type: string required: - name + additionalProperties: false OAuthTokenBody: type: object properties: @@ -3128,6 +3237,34 @@ components: nullable: true allOf: - $ref: '#/components/schemas/ApiKeySecretJWTTemplate' + DesiredInstanceSize: + type: string + enum: + - micro + - small + - medium + - large + - xlarge + - 2xlarge + - 4xlarge + - 8xlarge + - 12xlarge + - 16xlarge + ReleaseChannel: + type: string + enum: + - internal + - alpha + - beta + - ga + - withdrawn + PostgresEngine: + type: string + description: >- + Postgres engine version. If not provided, the latest version will be + used. + enum: + - '15' CreateBranchBody: type: object properties: @@ -5090,6 +5227,10 @@ components: type: string verify_jwt: type: boolean + compute_multiplier: + type: number + minimum: 1 + maximum: 4 required: - slug - name @@ -5125,6 +5266,8 @@ components: type: string import_map_path: type: string + compute_multiplier: + type: number required: - version - created_at @@ -5164,6 +5307,8 @@ components: type: string import_map_path: type: string + compute_multiplier: + type: number required: - version - created_at @@ -5181,6 +5326,10 @@ components: type: string verify_jwt: type: boolean + compute_multiplier: + type: number + minimum: 1 + maximum: 4 V1StorageBucketResponse: type: object properties: diff --git a/cmd/projects.go b/cmd/projects.go index 08ec10dd8..c119e7171 100644 --- a/cmd/projects.go +++ b/cmd/projects.go @@ -33,21 +33,21 @@ var ( Allowed: awsRegions(), } plan = utils.EnumFlag{ - Allowed: []string{string(api.V1CreateProjectBodyPlanFree), string(api.V1CreateProjectBodyPlanPro)}, - Value: string(api.V1CreateProjectBodyPlanFree), + Allowed: []string{string(api.V1CreateProjectBodyDtoPlanFree), string(api.V1CreateProjectBodyDtoPlanPro)}, + Value: string(api.V1CreateProjectBodyDtoPlanFree), } size = utils.EnumFlag{ Allowed: []string{ - string(api.Micro), - string(api.Small), - string(api.Medium), - string(api.Large), - string(api.Xlarge), - string(api.N2xlarge), - string(api.N4xlarge), - string(api.N8xlarge), - string(api.N12xlarge), - string(api.N16xlarge), + string(api.DesiredInstanceSizeMicro), + string(api.DesiredInstanceSizeSmall), + string(api.DesiredInstanceSizeMedium), + string(api.DesiredInstanceSizeLarge), + string(api.DesiredInstanceSizeXlarge), + string(api.DesiredInstanceSizeN2xlarge), + string(api.DesiredInstanceSizeN4xlarge), + string(api.DesiredInstanceSizeN8xlarge), + string(api.DesiredInstanceSizeN12xlarge), + string(api.DesiredInstanceSizeN16xlarge), }, } @@ -69,14 +69,14 @@ var ( if len(args) > 0 { projectName = args[0] } - body := api.V1CreateProjectBody{ + body := api.V1CreateProjectBodyDto{ Name: projectName, OrganizationId: orgId, DbPass: dbPassword, - Region: api.V1CreateProjectBodyRegion(region.Value), + Region: api.V1CreateProjectBodyDtoRegion(region.Value), } if cmd.Flags().Changed("size") { - body.DesiredInstanceSize = (*api.DesiredInstanceSize)(&size.Value) + body.DesiredInstanceSize = (*api.V1CreateProjectBodyDtoDesiredInstanceSize)(&size.Value) } return create.Run(cmd.Context(), body, afero.NewOsFs()) }, diff --git a/docs/supabase/db/reset.md b/docs/supabase/db/reset.md index 98a8003ca..acb9b9832 100644 --- a/docs/supabase/db/reset.md +++ b/docs/supabase/db/reset.md @@ -6,4 +6,4 @@ Requires the local development stack to be started by running `supabase start`. Recreates the local Postgres container and applies all local migrations found in `supabase/migrations` directory. If test data is defined in `supabase/seed.sql`, it will be seeded after the migrations are run. Any other data or schema changes made during local development will be discarded. -Note that since Postgres roles are cluster level entities, those changes will persist between resets. In order to reset custom roles, you need to restart the local development stack. +When running db reset with `--linked` or `--db-url` flag, a SQL script is executed to identify and drop all user created entities in the remote database. Since Postgres roles are cluster level entities, any custom roles created through the dashboard or `supabase/roles.sql` will not be deleted by remote reset. diff --git a/go.mod b/go.mod index 4e58e4562..d8bfd6274 100644 --- a/go.mod +++ b/go.mod @@ -21,9 +21,9 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/go-errors/errors v1.5.1 github.com/go-git/go-git/v5 v5.12.0 - github.com/go-xmlfmt/xmlfmt v1.1.2 + github.com/go-xmlfmt/xmlfmt v1.1.3 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/golangci/golangci-lint v1.62.0 + github.com/golangci/golangci-lint v1.62.2 github.com/google/go-github/v62 v62.0.0 github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 @@ -44,8 +44,8 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 - github.com/stripe/pg-schema-diff v0.7.0 + github.com/stretchr/testify v1.10.0 + github.com/stripe/pg-schema-diff v0.8.0 github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 github.com/zalando/go-keyring v0.2.6 go.opentelemetry.io/otel v1.32.0 @@ -66,9 +66,9 @@ require ( github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect github.com/Antonboom/nilnil v1.0.0 // indirect - github.com/Antonboom/testifylint v1.5.0 // indirect + github.com/Antonboom/testifylint v1.5.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Crocmagnon/fatcontext v0.5.2 // indirect + github.com/Crocmagnon/fatcontext v0.5.3 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect @@ -226,7 +226,7 @@ require ( github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.5.0 // indirect + github.com/mgechev/revive v1.5.1 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -245,7 +245,7 @@ require ( github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.18.0 // indirect + github.com/nunnatsa/ginkgolinter v0.18.3 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect @@ -254,7 +254,7 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/polyfloyd/go-errorlint v1.6.0 // indirect + github.com/polyfloyd/go-errorlint v1.7.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -303,7 +303,7 @@ require ( github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect - github.com/uudashr/iface v1.2.0 // indirect + github.com/uudashr/iface v1.2.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -334,7 +334,7 @@ require ( golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect - golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect diff --git a/go.sum b/go.sum index e224d065e..28de4b7ef 100644 --- a/go.sum +++ b/go.sum @@ -47,16 +47,16 @@ github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoT github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= -github.com/Antonboom/testifylint v1.5.0 h1:dlUIsDMtCrZWUnvkaCz3quJCoIjaGi41GzjPBGkkJ8A= -github.com/Antonboom/testifylint v1.5.0/go.mod h1:wqaJbu0Blb5Wag2wv7Z5xt+CIV+eVLxtGZrlK13z3AE= +github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= +github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= -github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= +github.com/Crocmagnon/fatcontext v0.5.3 h1:zCh/wjc9oyeF+Gmp+V60wetm8ph2tlsxocgg/J0hOps= +github.com/Crocmagnon/fatcontext v0.5.3/go.mod h1:XoCQYY1J+XTfyv74qLXvNw4xFunr3L1wkopIIKG7wGM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= @@ -371,8 +371,8 @@ github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUN github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -427,8 +427,8 @@ github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUP github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.62.0 h1:/G0g+bi1BhmGJqLdNQkKBWjcim8HjOPc4tsKuHDOhcI= -github.com/golangci/golangci-lint v1.62.0/go.mod h1:jtoOhQcKTz8B6dGNFyfQV3WZkQk+YvBDewDtNpiAJts= +github.com/golangci/golangci-lint v1.62.2 h1:b8K5K9PN+rZN1+mKLtsZHz2XXS9aYKzQ9i25x3Qnxxw= +github.com/golangci/golangci-lint v1.62.2/go.mod h1:ILWWyeFUrctpHVGMa1dg2xZPKoMUTc5OIMgW7HZr34g= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -703,8 +703,8 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.5.0 h1:oaSmjA7rP8+HyoRuCgC531VHwnLH1AlJdjj+1AnQceQ= -github.com/mgechev/revive v1.5.0/go.mod h1:L6T3H8EoerRO86c7WuGpvohIUmiploGiyoYbtIWFmV8= +github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= +github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -755,8 +755,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.18.0 h1:ZXO1wKhPg3A6LpbN5dMuqwhfOjN5c3ous8YdKOuqk9k= -github.com/nunnatsa/ginkgolinter v0.18.0/go.mod h1:vPrWafSULmjMGCMsfGA908if95VnHQNAahvSBOjTuWs= +github.com/nunnatsa/ginkgolinter v0.18.3 h1:WgS7X3zzmni3vwHSBhvSgqrRgUecN6PQUcfB0j1noDw= +github.com/nunnatsa/ginkgolinter v0.18.3/go.mod h1:BE1xyB/PNtXXG1azrvrqJW5eFH0hSRylNzFy8QHPwzs= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -801,8 +801,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= -github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= +github.com/polyfloyd/go-errorlint v1.7.0 h1:Zp6lzCK4hpBDj8y8a237YK4EPrMXQWvOe3nGoH4pFrU= +github.com/polyfloyd/go-errorlint v1.7.0/go.mod h1:dGWKu85mGHnegQ2SWpEybFityCg3j7ZbwsVUxAOk9gY= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -947,10 +947,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stripe/pg-schema-diff v0.7.0 h1:00Z+LGGe9GhMsN5gLtx/ZwF/+xPOMgod/g8x8H1JmV4= -github.com/stripe/pg-schema-diff v0.7.0/go.mod h1:HuTBuWLuvnY9g9nptbSD58xugN19zSJNkF4w/sYRtdU= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stripe/pg-schema-diff v0.8.0 h1:Ggm4yDbPtaflYQLV3auEMTLxQPaentV/wmDEoCF5jxQ= +github.com/stripe/pg-schema-diff v0.8.0/go.mod h1:HuTBuWLuvnY9g9nptbSD58xugN19zSJNkF4w/sYRtdU= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= @@ -981,8 +981,8 @@ github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/ github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= -github.com/uudashr/iface v1.2.0 h1:ECJjh5q/1Zmnv/2yFpWV6H3oMg5+Mo+vL0aqw9Gjazo= -github.com/uudashr/iface v1.2.0/go.mod h1:Ux/7d/rAF3owK4m53cTVXL4YoVHKNqnoOeQHn2xrlp0= +github.com/uudashr/iface v1.2.1 h1:vHHyzAUmWZ64Olq6NZT3vg/z1Ws56kyPdBOd5kTXDF8= +github.com/uudashr/iface v1.2.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 h1:MzD3XeOOSO3mAjOPpF07jFteSKZxsRHvlIcAR9RQzKM= github.com/withfig/autocomplete-tools/packages/cobra v1.2.0/go.mod h1:RoXh7+7qknOXL65uTzdzE1mPxqcPwS7FLCE9K5GfmKo= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -1117,8 +1117,8 @@ golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBn golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 h1:bVwtbF629Xlyxk6xLQq2TDYmqP0uiWaet5LwRebuY0k= -golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= +golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 4769a81d1..677ef1530 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -77,7 +77,7 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options .. return err } // 2. Create project - params := api.V1CreateProjectBody{ + params := api.V1CreateProjectBodyDto{ Name: filepath.Base(workdir), TemplateUrl: &starter.Url, } diff --git a/internal/db/branch/switch_/switch__test.go b/internal/db/branch/switch_/switch__test.go index 429606627..7c70959ce 100644 --- a/internal/db/branch/switch_/switch__test.go +++ b/internal/db/branch/switch_/switch__test.go @@ -2,7 +2,6 @@ package switch_ import ( "context" - "fmt" "net/http" "os" "path/filepath" @@ -14,6 +13,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/supabase/cli/internal/db/reset" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgtest" @@ -42,10 +42,14 @@ func TestSwitchCommand(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). Reply("ALTER DATABASE"). - Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). - Reply("DO"). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). + Reply("ALTER DATABASE"). + Query(reset.TERMINATE_BACKENDS). + Reply("SELECT 1"). + Query(reset.COUNT_REPLICATION_SLOTS). + Reply("SELECT 1", []interface{}{0}). Query("ALTER DATABASE postgres RENAME TO main;"). Reply("ALTER DATABASE"). Query("ALTER DATABASE " + branch + " RENAME TO postgres;"). @@ -218,8 +222,10 @@ func TestSwitchDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). - ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`) + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). + ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). + Query(reset.TERMINATE_BACKENDS) // Run test err := switchDatabase(context.Background(), "main", "target", conn.Intercept) // Check error @@ -234,10 +240,14 @@ func TestSwitchDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). + Reply("ALTER DATABASE"). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). Reply("ALTER DATABASE"). - Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). - Reply("DO"). + Query(reset.TERMINATE_BACKENDS). + Reply("SELECT 1"). + Query(reset.COUNT_REPLICATION_SLOTS). + Reply("SELECT 1", []interface{}{0}). Query("ALTER DATABASE postgres RENAME TO main;"). ReplyError(pgerrcode.DuplicateDatabase, `database "main" already exists`) // Setup mock docker @@ -260,10 +270,14 @@ func TestSwitchDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). + Reply("ALTER DATABASE"). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). Reply("ALTER DATABASE"). - Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). - Reply("DO"). + Query(reset.TERMINATE_BACKENDS). + Reply("SELECT 1"). + Query(reset.COUNT_REPLICATION_SLOTS). + Reply("SELECT 1", []interface{}{0}). Query("ALTER DATABASE postgres RENAME TO main;"). Reply("ALTER DATABASE"). Query("ALTER DATABASE target RENAME TO postgres;"). diff --git a/internal/db/diff/diff.go b/internal/db/diff/diff.go index 38a3bed58..6c5faa892 100644 --- a/internal/db/diff/diff.go +++ b/internal/db/diff/diff.go @@ -146,12 +146,12 @@ func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) { func ConnectShadowDatabase(ctx context.Context, timeout time.Duration, options ...func(*pgx.ConnConfig)) (conn *pgx.Conn, err error) { // Retry until connected, cancelled, or timeout - policy := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), uint64(timeout.Seconds())) + policy := start.NewBackoffPolicy(ctx, timeout) config := pgconn.Config{Port: utils.Config.Db.ShadowPort} connect := func() (*pgx.Conn, error) { return utils.ConnectLocalPostgres(ctx, config, options...) } - return backoff.RetryWithData(connect, backoff.WithContext(policy, ctx)) + return backoff.RetryWithData(connect, policy) } func MigrateShadowDatabase(ctx context.Context, container string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { diff --git a/internal/db/reset/reset.go b/internal/db/reset/reset.go index a9d2f4f4f..a475ca80d 100644 --- a/internal/db/reset/reset.go +++ b/internal/db/reset/reset.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/cenkalti/backoff/v4" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" @@ -164,20 +165,40 @@ func recreateDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) err return sql.ExecBatch(ctx, conn) } +const ( + TERMINATE_BACKENDS = "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IN ('postgres', '_supabase')" + COUNT_REPLICATION_SLOTS = "SELECT COUNT(*) FROM pg_replication_slots WHERE database IN ('postgres', '_supabase')" +) + func DisconnectClients(ctx context.Context, conn *pgx.Conn) error { - // Must be executed separately because running in transaction is unsupported - disconn := "ALTER DATABASE postgres ALLOW_CONNECTIONS false;" - if _, err := conn.Exec(ctx, disconn); err != nil { + // Must be executed separately because looping in transaction is unsupported + // https://dba.stackexchange.com/a/11895 + disconn := migration.MigrationFile{ + Statements: []string{ + "ALTER DATABASE postgres ALLOW_CONNECTIONS false", + "ALTER DATABASE _supabase ALLOW_CONNECTIONS false", + TERMINATE_BACKENDS, + }, + } + if err := disconn.ExecBatch(ctx, conn); err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code != pgerrcode.InvalidCatalogName { return errors.Errorf("failed to disconnect clients: %w", err) } } - term := fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres") - if _, err := conn.Exec(ctx, term); err != nil { - return errors.Errorf("failed to terminate backend: %w", err) + // Wait for WAL senders to drop their replication slots + policy := start.NewBackoffPolicy(ctx, 10*time.Second) + waitForDrop := func() error { + var count int + if err := conn.QueryRow(ctx, COUNT_REPLICATION_SLOTS).Scan(&count); err != nil { + err = errors.Errorf("failed to count replication slots: %w", err) + return &backoff.PermanentError{Err: err} + } else if count > 0 { + return errors.Errorf("replication slots still active: %d", count) + } + return nil } - return nil + return backoff.Retry(waitForDrop, policy) } func RestartDatabase(ctx context.Context, w io.Writer) error { diff --git a/internal/db/reset/reset_test.go b/internal/db/reset/reset_test.go index c65aa0ef9..4e3558be3 100644 --- a/internal/db/reset/reset_test.go +++ b/internal/db/reset/reset_test.go @@ -3,7 +3,6 @@ package reset import ( "context" "errors" - "fmt" "io" "net/http" "path/filepath" @@ -202,10 +201,14 @@ func TestRecreateDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). Reply("ALTER DATABASE"). - Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). - Reply("DO"). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). + Reply("ALTER DATABASE"). + Query(TERMINATE_BACKENDS). + Reply("SELECT 1"). + Query(COUNT_REPLICATION_SLOTS). + Reply("SELECT 1", []interface{}{0}). Query("DROP DATABASE IF EXISTS postgres WITH (FORCE)"). Reply("DROP DATABASE"). Query("CREATE DATABASE postgres WITH OWNER postgres"). @@ -228,14 +231,17 @@ func TestRecreateDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). - ReplyError(pgerrcode.InvalidCatalogName, `database "postgres" does not exist`). - Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). - ReplyError(pgerrcode.UndefinedTable, `relation "pg_stat_activity" does not exist`) + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). + Reply("ALTER DATABASE"). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). + ReplyError(pgerrcode.InvalidCatalogName, `database "_supabase" does not exist`). + Query(TERMINATE_BACKENDS). + Query(COUNT_REPLICATION_SLOTS). + ReplyError(pgerrcode.UndefinedTable, `relation "pg_replication_slots" does not exist`) // Run test err := recreateDatabase(context.Background(), conn.Intercept) // Check error - assert.ErrorContains(t, err, `ERROR: relation "pg_stat_activity" does not exist (SQLSTATE 42P01)`) + assert.ErrorContains(t, err, `ERROR: relation "pg_replication_slots" does not exist (SQLSTATE 42P01)`) }) t.Run("throws error on failure to disconnect", func(t *testing.T) { @@ -243,8 +249,10 @@ func TestRecreateDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). - ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`) + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). + ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). + Query(TERMINATE_BACKENDS) // Run test err := recreateDatabase(context.Background(), conn.Intercept) // Check error @@ -256,10 +264,14 @@ func TestRecreateDatabase(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). + conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false"). + Reply("ALTER DATABASE"). + Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false"). Reply("ALTER DATABASE"). - Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). - Reply("DO"). + Query(TERMINATE_BACKENDS). + Reply("SELECT 1"). + Query(COUNT_REPLICATION_SLOTS). + Reply("SELECT 1", []interface{}{0}). Query("DROP DATABASE IF EXISTS postgres WITH (FORCE)"). ReplyError(pgerrcode.ObjectInUse, `database "postgres" is used by an active logical replication slot`). Query("CREATE DATABASE postgres WITH OWNER postgres"). diff --git a/internal/db/start/start.go b/internal/db/start/start.go index a300f5594..c4722f22f 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -160,6 +160,14 @@ EOF`} return initCurrentBranch(fsys) } +func NewBackoffPolicy(ctx context.Context, timeout time.Duration) backoff.BackOff { + policy := backoff.WithMaxRetries( + backoff.NewConstantBackOff(time.Second), + uint64(timeout.Seconds()), + ) + return backoff.WithContext(policy, ctx) +} + func WaitForHealthyService(ctx context.Context, timeout time.Duration, started ...string) error { probe := func() error { var errHealth []error @@ -173,10 +181,7 @@ func WaitForHealthyService(ctx context.Context, timeout time.Duration, started . started = unhealthy return errors.Join(errHealth...) } - policy := backoff.WithContext(backoff.WithMaxRetries( - backoff.NewConstantBackOff(time.Second), - uint64(timeout.Seconds()), - ), ctx) + policy := NewBackoffPolicy(ctx, timeout) err := backoff.Retry(probe, policy) if err != nil && !errors.Is(err, context.Canceled) { // Print container logs for easier debugging @@ -251,6 +256,7 @@ func initRealtimeJob(host string) utils.DockerJob { "DNS_NODES=''", "RLIMIT_NOFILE=", "SEED_SELF_HOST=true", + "RUN_JANITOR=true", fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength), }, Cmd: []string{"/app/bin/realtime", "eval", fmt.Sprintf(`{:ok, _} = Application.ensure_all_started(:realtime) diff --git a/internal/inspect/role_configs/role_configs.sql b/internal/inspect/role_configs/role_configs.sql index d568d6862..fd7f964d0 100644 --- a/internal/inspect/role_configs/role_configs.sql +++ b/internal/inspect/role_configs/role_configs.sql @@ -2,4 +2,4 @@ select rolname as role_name, array_to_string(rolconfig, ',', '*') as custom_config from - pg_roles where rolconfig is not null; + pg_roles where rolconfig is not null diff --git a/internal/link/link.go b/internal/link/link.go index 9d72302c4..e791271b8 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -6,7 +6,6 @@ import ( "net/http" "os" "strconv" - "strings" "sync" "github.com/go-errors/errors" @@ -26,15 +25,14 @@ import ( ) func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { - original, err := cliConfig.ToTomlBytes(map[string]interface{}{ - "api": utils.Config.Api, - "db": utils.Config.Db, - }) + copy := utils.Config.Clone() + copy.Auth.HashSecrets(projectRef) + original, err := cliConfig.ToTomlBytes(copy) if err != nil { fmt.Fprintln(utils.GetDebugLogger(), err) } - if err := checkRemoteProjectStatus(ctx, projectRef); err != nil { + if err := checkRemoteProjectStatus(ctx, projectRef, fsys); err != nil { return err } @@ -64,10 +62,7 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func( fmt.Fprintln(os.Stdout, "Finished "+utils.Aqua("supabase link")+".") // 4. Suggest config update - updated, err := cliConfig.ToTomlBytes(map[string]interface{}{ - "api": utils.Config.Api, - "db": utils.Config.Db, - }) + updated, err := cliConfig.ToTomlBytes(utils.Config.Clone()) if err != nil { fmt.Fprintln(utils.GetDebugLogger(), err) } @@ -82,10 +77,10 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func( func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs) { // Ignore non-fatal errors linking services var wg sync.WaitGroup - wg.Add(6) + wg.Add(8) go func() { defer wg.Done() - if err := linkDatabaseVersion(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { + if err := linkDatabaseSettings(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { fmt.Fprintln(os.Stderr, err) } }() @@ -95,6 +90,18 @@ func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs fmt.Fprintln(os.Stderr, err) } }() + go func() { + defer wg.Done() + if err := linkGotrue(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { + fmt.Fprintln(os.Stderr, err) + } + }() + go func() { + defer wg.Done() + if err := linkStorage(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { + fmt.Fprintln(os.Stderr, err) + } + }() go func() { defer wg.Done() if err := linkPooler(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { @@ -126,12 +133,11 @@ func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs func linkPostgrest(ctx context.Context, projectRef string) error { resp, err := utils.GetSupabase().V1GetPostgrestServiceConfigWithResponse(ctx, projectRef) if err != nil { - return errors.Errorf("failed to get postgrest config: %w", err) - } - if resp.JSON200 == nil { - return errors.Errorf("%w: %s", tenant.ErrAuthToken, string(resp.Body)) + return errors.Errorf("failed to read API config: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected API config status %d: %s", resp.StatusCode(), string(resp.Body)) } - updateApiConfig(*resp.JSON200) + utils.Config.Api.FromRemoteApiConfig(*resp.JSON200) return nil } @@ -143,22 +149,15 @@ func linkPostgrestVersion(ctx context.Context, api tenant.TenantAPI, fsys afero. return utils.WriteFile(utils.RestVersionPath, []byte(version), fsys) } -func updateApiConfig(config api.PostgrestConfigWithJWTSecretResponse) { - utils.Config.Api.MaxRows = cast.IntToUint(config.MaxRows) - utils.Config.Api.ExtraSearchPath = readCsv(config.DbExtraSearchPath) - utils.Config.Api.Schemas = readCsv(config.DbSchema) -} - -func readCsv(line string) []string { - var result []string - tokens := strings.Split(line, ",") - for _, t := range tokens { - trimmed := strings.TrimSpace(t) - if len(trimmed) > 0 { - result = append(result, trimmed) - } +func linkGotrue(ctx context.Context, projectRef string) error { + resp, err := utils.GetSupabase().V1GetAuthServiceConfigWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to read Auth config: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected Auth config status %d: %s", resp.StatusCode(), string(resp.Body)) } - return result + utils.Config.Auth.FromRemoteAuthConfig(*resp.JSON200) + return nil } func linkGotrueVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error { @@ -169,6 +168,17 @@ func linkGotrueVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) return utils.WriteFile(utils.GotrueVersionPath, []byte(version), fsys) } +func linkStorage(ctx context.Context, projectRef string) error { + resp, err := utils.GetSupabase().V1GetStorageConfigWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to read Storage config: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected Storage config status %d: %s", resp.StatusCode(), string(resp.Body)) + } + utils.Config.Storage.FromRemoteStorageConfig(*resp.JSON200) + return nil +} + func linkStorageVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error { version, err := api.GetStorageVersion(ctx) if err != nil { @@ -177,6 +187,17 @@ func linkStorageVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs return utils.WriteFile(utils.StorageVersionPath, []byte(version), fsys) } +func linkDatabaseSettings(ctx context.Context, projectRef string) error { + resp, err := utils.GetSupabase().V1GetPostgresConfigWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to read DB config: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected DB config status %d: %s", resp.StatusCode(), string(resp.Body)) + } + utils.Config.Db.Settings.FromRemotePostgresConfig(*resp.JSON200) + return nil +} + func linkDatabase(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) error { conn, err := utils.ConnectByConfig(ctx, config, options...) if err != nil { @@ -191,14 +212,6 @@ func linkDatabase(ctx context.Context, config pgconn.Config, options ...func(*pg return migration.CreateSeedTable(ctx, conn) } -func linkDatabaseVersion(ctx context.Context, projectRef string, fsys afero.Fs) error { - version, err := tenant.GetDatabaseVersion(ctx, projectRef) - if err != nil { - return err - } - return utils.WriteFile(utils.PostgresVersionPath, []byte(version), fsys) -} - func updatePostgresConfig(conn *pgx.Conn) { serverVersion := conn.PgConn().ParameterStatus("server_version") // Safe to assume that supported Postgres version is 10.0 <= n < 100.0 @@ -241,7 +254,7 @@ func updatePoolerConfig(config api.SupavisorConfigResponse) { var errProjectPaused = errors.New("project is paused") -func checkRemoteProjectStatus(ctx context.Context, projectRef string) error { +func checkRemoteProjectStatus(ctx context.Context, projectRef string, fsys afero.Fs) error { resp, err := utils.GetSupabase().V1GetProjectWithResponse(ctx, projectRef) if err != nil { return errors.Errorf("failed to retrieve remote project status: %w", err) @@ -257,14 +270,18 @@ func checkRemoteProjectStatus(ctx context.Context, projectRef string) error { } switch resp.JSON200.Status { - case api.V1ProjectResponseStatusINACTIVE: + case api.V1ProjectWithDatabaseResponseStatusINACTIVE: utils.CmdSuggestion = fmt.Sprintf("An admin must unpause it from the Supabase dashboard at %s", utils.Aqua(fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), projectRef))) return errors.New(errProjectPaused) - case api.V1ProjectResponseStatusACTIVEHEALTHY: + case api.V1ProjectWithDatabaseResponseStatusACTIVEHEALTHY: // Project is in the desired state, do nothing default: fmt.Fprintf(os.Stderr, "%s: Project status is %s instead of Active Healthy. Some operations might fail.\n", utils.Yellow("WARNING"), resp.JSON200.Status) } + // Update postgres image version to match the remote project + if version := resp.JSON200.Database.Version; len(version) > 0 { + return utils.WriteFile(utils.PostgresVersionPath, []byte(version), fsys) + } return nil } diff --git a/internal/link/link_test.go b/internal/link/link_test.go index c8a867f6b..18c090ad3 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -51,19 +51,38 @@ func TestLinkCommand(t *testing.T) { // Flush pending mocks after test execution defer gock.OffAll() // Mock project status + postgres := api.V1DatabaseResponse{ + Host: utils.GetSupabaseDbHost(project), + Version: "15.1.0.117", + } gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project). Reply(200). - JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY}) + JSON(api.V1ProjectWithDatabaseResponse{ + Status: api.V1ProjectWithDatabaseResponseStatusACTIVEHEALTHY, + Database: postgres, + }) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) // Link configs + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/database/postgres"). + Reply(200). + JSON(api.PostgresConfigResponse{}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/postgrest"). Reply(200). JSON(api.V1PostgrestConfigResponse{}) + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/auth"). + Reply(200). + JSON(api.AuthConfigResponse{}) + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/storage"). + Reply(200). + JSON(api.StorageConfigResponse{}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/config/database/pooler"). Reply(200). @@ -83,23 +102,6 @@ func TestLinkCommand(t *testing.T) { Get("/storage/v1/version"). Reply(200). BodyString("0.40.4") - postgres := api.V1DatabaseResponse{ - Host: utils.GetSupabaseDbHost(project), - Version: "15.1.0.117", - } - gock.New(utils.DefaultApiHost). - Get("/v1/projects"). - Reply(200). - JSON([]api.V1ProjectResponse{ - { - Id: project, - Database: &postgres, - OrganizationId: "combined-fuchsia-lion", - Name: "Test Project", - Region: "us-west-1", - CreatedAt: "2022-04-25T02:14:55.906498Z", - }, - }) // Run test err := Run(context.Background(), project, fsys, conn.Intercept) // Check error @@ -130,15 +132,27 @@ func TestLinkCommand(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project). Reply(200). - JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY}) + JSON(api.V1ProjectWithDatabaseResponse{ + Status: api.V1ProjectWithDatabaseResponseStatusACTIVEHEALTHY, + Database: api.V1DatabaseResponse{}, + }) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) // Link configs + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/database/postgres"). + ReplyError(errors.New("network error")) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/postgrest"). ReplyError(errors.New("network error")) + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/auth"). + ReplyError(errors.New("network error")) + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/storage"). + ReplyError(errors.New("network error")) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/config/database/pooler"). ReplyError(errors.New("network error")) @@ -152,9 +166,6 @@ func TestLinkCommand(t *testing.T) { gock.New("https://" + utils.GetSupabaseHost(project)). Get("/storage/v1/version"). ReplyError(errors.New("network error")) - gock.New(utils.DefaultApiHost). - Get("/v1/projects"). - ReplyError(errors.New("network error")) // Run test err := Run(context.Background(), project, fsys, func(cc *pgx.ConnConfig) { cc.LookupFunc = func(ctx context.Context, host string) (addrs []string, err error) { @@ -175,15 +186,27 @@ func TestLinkCommand(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project). Reply(200). - JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY}) + JSON(api.V1ProjectWithDatabaseResponse{ + Status: api.V1ProjectWithDatabaseResponseStatusACTIVEHEALTHY, + Database: api.V1DatabaseResponse{}, + }) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) // Link configs + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/database/postgres"). + ReplyError(errors.New("network error")) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/postgrest"). ReplyError(errors.New("network error")) + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/auth"). + ReplyError(errors.New("network error")) + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/config/storage"). + ReplyError(errors.New("network error")) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/config/database/pooler"). ReplyError(errors.New("network error")) @@ -215,7 +238,32 @@ func TestLinkCommand(t *testing.T) { func TestStatusCheck(t *testing.T) { project := "test-project" + t.Run("updates postgres version when healthy", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Flush pending mocks after test execution + defer gock.OffAll() + // Mock project status + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project). + Reply(http.StatusOK). + JSON(api.V1ProjectWithDatabaseResponse{ + Status: api.V1ProjectWithDatabaseResponseStatusACTIVEHEALTHY, + Database: api.V1DatabaseResponse{Version: "15.6.1.139"}, + }) + // Run test + err := checkRemoteProjectStatus(context.Background(), project, fsys) + // Check error + assert.NoError(t, err) + version, err := afero.ReadFile(fsys, utils.PostgresVersionPath) + assert.NoError(t, err) + assert.Equal(t, "15.6.1.139", string(version)) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + t.Run("ignores project not found", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() // Flush pending mocks after test execution defer gock.OffAll() // Mock project status @@ -223,24 +271,32 @@ func TestStatusCheck(t *testing.T) { Get("/v1/projects/" + project). Reply(http.StatusNotFound) // Run test - err := checkRemoteProjectStatus(context.Background(), project) + err := checkRemoteProjectStatus(context.Background(), project, fsys) // Check error assert.NoError(t, err) + exists, err := afero.Exists(fsys, utils.PostgresVersionPath) + assert.NoError(t, err) + assert.False(t, exists) assert.Empty(t, apitest.ListUnmatchedRequests()) }) t.Run("throws error on project inactive", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() // Flush pending mocks after test execution defer gock.OffAll() // Mock project status gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project). Reply(http.StatusOK). - JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusINACTIVE}) + JSON(api.V1ProjectWithDatabaseResponse{Status: api.V1ProjectWithDatabaseResponseStatusINACTIVE}) // Run test - err := checkRemoteProjectStatus(context.Background(), project) + err := checkRemoteProjectStatus(context.Background(), project, fsys) // Check error assert.ErrorIs(t, err, errProjectPaused) + exists, err := afero.Exists(fsys, utils.PostgresVersionPath) + assert.NoError(t, err) + assert.False(t, exists) assert.Empty(t, apitest.ListUnmatchedRequests()) }) } @@ -309,7 +365,7 @@ func TestLinkPostgrest(t *testing.T) { // Run test err := linkPostgrest(context.Background(), project) // Validate api - assert.ErrorIs(t, err, tenant.ErrAuthToken) + assert.ErrorContains(t, err, `unexpected API config status 500: {"message":"unavailable"}`) assert.Empty(t, apitest.ListUnmatchedRequests()) }) } diff --git a/internal/projects/apiKeys/api_keys.go b/internal/projects/apiKeys/api_keys.go index 4fcbfe06a..7daddce21 100644 --- a/internal/projects/apiKeys/api_keys.go +++ b/internal/projects/apiKeys/api_keys.go @@ -34,7 +34,7 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs) error { } func RunGetApiKeys(ctx context.Context, projectRef string) ([]api.ApiKeyResponse, error) { - resp, err := utils.GetSupabase().V1GetProjectApiKeysWithResponse(ctx, projectRef) + resp, err := utils.GetSupabase().V1GetProjectApiKeysWithResponse(ctx, projectRef, &api.V1GetProjectApiKeysParams{}) if err != nil { return nil, errors.Errorf("failed to get api keys: %w", err) } diff --git a/internal/projects/create/create.go b/internal/projects/create/create.go index 01ebf6308..ddbd46b47 100644 --- a/internal/projects/create/create.go +++ b/internal/projects/create/create.go @@ -15,7 +15,7 @@ import ( "github.com/supabase/cli/pkg/api" ) -func Run(ctx context.Context, params api.V1CreateProjectBody, fsys afero.Fs) error { +func Run(ctx context.Context, params api.V1CreateProjectBodyDto, fsys afero.Fs) error { if err := promptMissingParams(ctx, ¶ms); err != nil { return err } @@ -49,7 +49,7 @@ func printKeyValue(key, value string) string { return key + ":" + spaces + value } -func promptMissingParams(ctx context.Context, body *api.V1CreateProjectBody) error { +func promptMissingParams(ctx context.Context, body *api.V1CreateProjectBodyDto) error { var err error if len(body.Name) == 0 { if body.Name, err = promptProjectName(ctx); err != nil { @@ -106,7 +106,7 @@ func promptOrgId(ctx context.Context) (string, error) { return choice.Details, nil } -func promptProjectRegion(ctx context.Context) (api.V1CreateProjectBodyRegion, error) { +func promptProjectRegion(ctx context.Context) (api.V1CreateProjectBodyDtoRegion, error) { title := "Which region do you want to host the project in?" items := make([]utils.PromptItem, len(utils.RegionMap)) i := 0 @@ -118,5 +118,5 @@ func promptProjectRegion(ctx context.Context) (api.V1CreateProjectBodyRegion, er if err != nil { return "", err } - return api.V1CreateProjectBodyRegion(choice.Summary), nil + return api.V1CreateProjectBodyDtoRegion(choice.Summary), nil } diff --git a/internal/projects/create/create_test.go b/internal/projects/create/create_test.go index c67c7a35e..879421d72 100644 --- a/internal/projects/create/create_test.go +++ b/internal/projects/create/create_test.go @@ -14,11 +14,11 @@ import ( ) func TestProjectCreateCommand(t *testing.T) { - var params = api.V1CreateProjectBody{ + var params = api.V1CreateProjectBodyDto{ Name: "Test Project", OrganizationId: "combined-fuchsia-lion", DbPass: "redacted", - Region: api.V1CreateProjectBodyRegionUsWest1, + Region: api.V1CreateProjectBodyDtoRegionUsWest1, } t.Run("creates a new project", func(t *testing.T) { diff --git a/internal/projects/list/list.go b/internal/projects/list/list.go index b16b6d4fc..d211f71e9 100644 --- a/internal/projects/list/list.go +++ b/internal/projects/list/list.go @@ -15,8 +15,8 @@ import ( ) type linkedProject struct { - api.V1ProjectResponse `yaml:",inline"` - Linked bool `json:"linked"` + api.V1ProjectWithDatabaseResponse `yaml:",inline"` + Linked bool `json:"linked"` } func Run(ctx context.Context, fsys afero.Fs) error { @@ -37,8 +37,8 @@ func Run(ctx context.Context, fsys afero.Fs) error { var projects []linkedProject for _, project := range *resp.JSON200 { projects = append(projects, linkedProject{ - V1ProjectResponse: project, - Linked: project.Id == projectRef, + V1ProjectWithDatabaseResponse: project, + Linked: project.Id == projectRef, }) } diff --git a/internal/start/start.go b/internal/start/start.go index bdb0989aa..5521161f8 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -506,6 +506,8 @@ EOF fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template), "GOTRUE_SMS_TEST_OTP=" + testOTP.String(), + fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength), + fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation), fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval), fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking), @@ -532,7 +534,8 @@ EOF env = append(env, "GOTRUE_SMTP_HOST="+utils.InbucketId, "GOTRUE_SMTP_PORT=2500", - "GOTRUE_SMTP_ADMIN_EMAIL=admin@email.com", + fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Inbucket.AdminEmail), + fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Inbucket.SenderName), ) } @@ -765,6 +768,7 @@ EOF "DNS_NODES=''", "RLIMIT_NOFILE=", "SEED_SELF_HOST=true", + "RUN_JANITOR=true", fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength), }, ExposedPorts: nat.PortSet{"4000/tcp": {}}, @@ -1055,6 +1059,7 @@ EOF "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret, "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret, "REGION=local", + "RUN_JANITOR=true", "ERL_AFLAGS=-proto_dist inet_tcp", }, Cmd: []string{ diff --git a/internal/utils/misc.go b/internal/utils/misc.go index 0993ae806..202d80d0a 100644 --- a/internal/utils/misc.go +++ b/internal/utils/misc.go @@ -31,17 +31,7 @@ func ShortContainerImageName(imageName string) string { return matches[1] } -const ( - // https://dba.stackexchange.com/a/11895 - // Args: dbname - TerminateDbSqlFmt = ` -SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '%[1]s'; --- Wait for WAL sender to drop replication slot. -DO 'BEGIN WHILE ( - SELECT COUNT(*) FROM pg_replication_slots WHERE database = ''%[1]s'' -) > 0 LOOP END LOOP; END';` - SuggestDebugFlag = "Try rerunning the command with --debug to troubleshoot the error." -) +const SuggestDebugFlag = "Try rerunning the command with --debug to troubleshoot the error." var ( CmdSuggestion string diff --git a/internal/utils/tenant/client.go b/internal/utils/tenant/client.go index 9fc86a670..ad0ef6aa7 100644 --- a/internal/utils/tenant/client.go +++ b/internal/utils/tenant/client.go @@ -39,7 +39,7 @@ func NewApiKey(resp []api.ApiKeyResponse) ApiKey { } func GetApiKeys(ctx context.Context, projectRef string) (ApiKey, error) { - resp, err := utils.GetSupabase().V1GetProjectApiKeysWithResponse(ctx, projectRef) + resp, err := utils.GetSupabase().V1GetProjectApiKeysWithResponse(ctx, projectRef, &api.V1GetProjectApiKeysParams{}) if err != nil { return ApiKey{}, errors.Errorf("failed to get api keys: %w", err) } diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index 05ed76708..fcbe8eaeb 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -140,20 +140,23 @@ type ClientInterface interface { V1GetProject(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) // V1GetProjectApiKeys request - V1GetProjectApiKeys(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) + V1GetProjectApiKeys(ctx context.Context, ref string, params *V1GetProjectApiKeysParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateApiKeyWithBody request with any body - CreateApiKeyWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateApiKeyWithBody(ctx context.Context, ref string, params *CreateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateApiKey(ctx context.Context, ref string, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateApiKey(ctx context.Context, ref string, params *CreateApiKeyParams, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteApiKey request - DeleteApiKey(ctx context.Context, ref string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteApiKey(ctx context.Context, ref string, id string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetApiKey request + GetApiKey(ctx context.Context, ref string, id string, params *GetApiKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) // UpdateApiKeyWithBody request with any body - UpdateApiKeyWithBody(ctx context.Context, ref string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + UpdateApiKeyWithBody(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - UpdateApiKey(ctx context.Context, ref string, id string, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + UpdateApiKey(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // V1DisablePreviewBranching request V1DisablePreviewBranching(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -617,8 +620,20 @@ func (c *Client) V1GetProject(ctx context.Context, ref string, reqEditors ...Req return c.Client.Do(req) } -func (c *Client) V1GetProjectApiKeys(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1GetProjectApiKeysRequest(c.Server, ref) +func (c *Client) V1GetProjectApiKeys(ctx context.Context, ref string, params *V1GetProjectApiKeysParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetProjectApiKeysRequest(c.Server, ref, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateApiKeyWithBody(ctx context.Context, ref string, params *CreateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateApiKeyRequestWithBody(c.Server, ref, params, contentType, body) if err != nil { return nil, err } @@ -629,8 +644,8 @@ func (c *Client) V1GetProjectApiKeys(ctx context.Context, ref string, reqEditors return c.Client.Do(req) } -func (c *Client) CreateApiKeyWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateApiKeyRequestWithBody(c.Server, ref, contentType, body) +func (c *Client) CreateApiKey(ctx context.Context, ref string, params *CreateApiKeyParams, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateApiKeyRequest(c.Server, ref, params, body) if err != nil { return nil, err } @@ -641,8 +656,8 @@ func (c *Client) CreateApiKeyWithBody(ctx context.Context, ref string, contentTy return c.Client.Do(req) } -func (c *Client) CreateApiKey(ctx context.Context, ref string, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateApiKeyRequest(c.Server, ref, body) +func (c *Client) DeleteApiKey(ctx context.Context, ref string, id string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteApiKeyRequest(c.Server, ref, id, params) if err != nil { return nil, err } @@ -653,8 +668,8 @@ func (c *Client) CreateApiKey(ctx context.Context, ref string, body CreateApiKey return c.Client.Do(req) } -func (c *Client) DeleteApiKey(ctx context.Context, ref string, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteApiKeyRequest(c.Server, ref, id) +func (c *Client) GetApiKey(ctx context.Context, ref string, id string, params *GetApiKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetApiKeyRequest(c.Server, ref, id, params) if err != nil { return nil, err } @@ -665,8 +680,8 @@ func (c *Client) DeleteApiKey(ctx context.Context, ref string, id string, reqEdi return c.Client.Do(req) } -func (c *Client) UpdateApiKeyWithBody(ctx context.Context, ref string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateApiKeyRequestWithBody(c.Server, ref, id, contentType, body) +func (c *Client) UpdateApiKeyWithBody(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateApiKeyRequestWithBody(c.Server, ref, id, params, contentType, body) if err != nil { return nil, err } @@ -677,8 +692,8 @@ func (c *Client) UpdateApiKeyWithBody(ctx context.Context, ref string, id string return c.Client.Do(req) } -func (c *Client) UpdateApiKey(ctx context.Context, ref string, id string, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateApiKeyRequest(c.Server, ref, id, body) +func (c *Client) UpdateApiKey(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateApiKeyRequest(c.Server, ref, id, params, body) if err != nil { return nil, err } @@ -2378,7 +2393,7 @@ func NewV1GetProjectRequest(server string, ref string) (*http.Request, error) { } // NewV1GetProjectApiKeysRequest generates requests for V1GetProjectApiKeys -func NewV1GetProjectApiKeysRequest(server string, ref string) (*http.Request, error) { +func NewV1GetProjectApiKeysRequest(server string, ref string, params *V1GetProjectApiKeysParams) (*http.Request, error) { var err error var pathParam0 string @@ -2403,6 +2418,24 @@ func NewV1GetProjectApiKeysRequest(server string, ref string) (*http.Request, er return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "reveal", runtime.ParamLocationQuery, params.Reveal); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2412,18 +2445,18 @@ func NewV1GetProjectApiKeysRequest(server string, ref string) (*http.Request, er } // NewCreateApiKeyRequest calls the generic CreateApiKey builder with application/json body -func NewCreateApiKeyRequest(server string, ref string, body CreateApiKeyJSONRequestBody) (*http.Request, error) { +func NewCreateApiKeyRequest(server string, ref string, params *CreateApiKeyParams, body CreateApiKeyJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateApiKeyRequestWithBody(server, ref, "application/json", bodyReader) + return NewCreateApiKeyRequestWithBody(server, ref, params, "application/json", bodyReader) } // NewCreateApiKeyRequestWithBody generates requests for CreateApiKey with any type of body -func NewCreateApiKeyRequestWithBody(server string, ref string, contentType string, body io.Reader) (*http.Request, error) { +func NewCreateApiKeyRequestWithBody(server string, ref string, params *CreateApiKeyParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -2448,6 +2481,24 @@ func NewCreateApiKeyRequestWithBody(server string, ref string, contentType strin return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "reveal", runtime.ParamLocationQuery, params.Reveal); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -2459,7 +2510,7 @@ func NewCreateApiKeyRequestWithBody(server string, ref string, contentType strin } // NewDeleteApiKeyRequest generates requests for DeleteApiKey -func NewDeleteApiKeyRequest(server string, ref string, id string) (*http.Request, error) { +func NewDeleteApiKeyRequest(server string, ref string, id string, params *DeleteApiKeyParams) (*http.Request, error) { var err error var pathParam0 string @@ -2491,6 +2542,24 @@ func NewDeleteApiKeyRequest(server string, ref string, id string) (*http.Request return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "reveal", runtime.ParamLocationQuery, params.Reveal); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err @@ -2499,19 +2568,78 @@ func NewDeleteApiKeyRequest(server string, ref string, id string) (*http.Request return req, nil } +// NewGetApiKeyRequest generates requests for GetApiKey +func NewGetApiKeyRequest(server string, ref string, id string, params *GetApiKeyParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/api-keys/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "reveal", runtime.ParamLocationQuery, params.Reveal); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewUpdateApiKeyRequest calls the generic UpdateApiKey builder with application/json body -func NewUpdateApiKeyRequest(server string, ref string, id string, body UpdateApiKeyJSONRequestBody) (*http.Request, error) { +func NewUpdateApiKeyRequest(server string, ref string, id string, params *UpdateApiKeyParams, body UpdateApiKeyJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewUpdateApiKeyRequestWithBody(server, ref, id, "application/json", bodyReader) + return NewUpdateApiKeyRequestWithBody(server, ref, id, params, "application/json", bodyReader) } // NewUpdateApiKeyRequestWithBody generates requests for UpdateApiKey with any type of body -func NewUpdateApiKeyRequestWithBody(server string, ref string, id string, contentType string, body io.Reader) (*http.Request, error) { +func NewUpdateApiKeyRequestWithBody(server string, ref string, id string, params *UpdateApiKeyParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -2543,6 +2671,24 @@ func NewUpdateApiKeyRequestWithBody(server string, ref string, id string, conten return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "reveal", runtime.ParamLocationQuery, params.Reveal); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("PATCH", queryURL.String(), body) if err != nil { return nil, err @@ -3921,6 +4067,22 @@ func NewV1CreateAFunctionRequestWithBody(server string, ref string, params *V1Cr } + if params.ComputeMultiplier != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "compute_multiplier", runtime.ParamLocationQuery, *params.ComputeMultiplier); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -4159,6 +4321,22 @@ func NewV1UpdateAFunctionRequestWithBody(server string, ref string, functionSlug } + if params.ComputeMultiplier != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "compute_multiplier", runtime.ParamLocationQuery, *params.ComputeMultiplier); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -5542,20 +5720,23 @@ type ClientWithResponsesInterface interface { V1GetProjectWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectResponse, error) // V1GetProjectApiKeysWithResponse request - V1GetProjectApiKeysWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectApiKeysResponse, error) + V1GetProjectApiKeysWithResponse(ctx context.Context, ref string, params *V1GetProjectApiKeysParams, reqEditors ...RequestEditorFn) (*V1GetProjectApiKeysResponse, error) // CreateApiKeyWithBodyWithResponse request with any body - CreateApiKeyWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) + CreateApiKeyWithBodyWithResponse(ctx context.Context, ref string, params *CreateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) - CreateApiKeyWithResponse(ctx context.Context, ref string, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) + CreateApiKeyWithResponse(ctx context.Context, ref string, params *CreateApiKeyParams, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) // DeleteApiKeyWithResponse request - DeleteApiKeyWithResponse(ctx context.Context, ref string, id string, reqEditors ...RequestEditorFn) (*DeleteApiKeyResponse, error) + DeleteApiKeyWithResponse(ctx context.Context, ref string, id string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*DeleteApiKeyResponse, error) + + // GetApiKeyWithResponse request + GetApiKeyWithResponse(ctx context.Context, ref string, id string, params *GetApiKeyParams, reqEditors ...RequestEditorFn) (*GetApiKeyResponse, error) // UpdateApiKeyWithBodyWithResponse request with any body - UpdateApiKeyWithBodyWithResponse(ctx context.Context, ref string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) + UpdateApiKeyWithBodyWithResponse(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) - UpdateApiKeyWithResponse(ctx context.Context, ref string, id string, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) + UpdateApiKeyWithResponse(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) // V1DisablePreviewBranchingWithResponse request V1DisablePreviewBranchingWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1DisablePreviewBranchingResponse, error) @@ -6025,7 +6206,7 @@ func (r V1ListOrganizationMembersResponse) StatusCode() int { type V1ListAllProjectsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]V1ProjectResponse + JSON200 *[]V1ProjectWithDatabaseResponse } // Status returns HTTPResponse.Status @@ -6091,7 +6272,7 @@ func (r V1DeleteAProjectResponse) StatusCode() int { type V1GetProjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *V1ProjectResponse + JSON200 *V1ProjectWithDatabaseResponse } // Status returns HTTPResponse.Status @@ -6176,6 +6357,28 @@ func (r DeleteApiKeyResponse) StatusCode() int { return 0 } +type GetApiKeyResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ApiKeyResponse +} + +// Status returns HTTPResponse.Status +func (r GetApiKeyResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetApiKeyResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type UpdateApiKeyResponse struct { Body []byte HTTPResponse *http.Response @@ -7774,8 +7977,8 @@ func (c *ClientWithResponses) V1GetProjectWithResponse(ctx context.Context, ref } // V1GetProjectApiKeysWithResponse request returning *V1GetProjectApiKeysResponse -func (c *ClientWithResponses) V1GetProjectApiKeysWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectApiKeysResponse, error) { - rsp, err := c.V1GetProjectApiKeys(ctx, ref, reqEditors...) +func (c *ClientWithResponses) V1GetProjectApiKeysWithResponse(ctx context.Context, ref string, params *V1GetProjectApiKeysParams, reqEditors ...RequestEditorFn) (*V1GetProjectApiKeysResponse, error) { + rsp, err := c.V1GetProjectApiKeys(ctx, ref, params, reqEditors...) if err != nil { return nil, err } @@ -7783,16 +7986,16 @@ func (c *ClientWithResponses) V1GetProjectApiKeysWithResponse(ctx context.Contex } // CreateApiKeyWithBodyWithResponse request with arbitrary body returning *CreateApiKeyResponse -func (c *ClientWithResponses) CreateApiKeyWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) { - rsp, err := c.CreateApiKeyWithBody(ctx, ref, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateApiKeyWithBodyWithResponse(ctx context.Context, ref string, params *CreateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) { + rsp, err := c.CreateApiKeyWithBody(ctx, ref, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseCreateApiKeyResponse(rsp) } -func (c *ClientWithResponses) CreateApiKeyWithResponse(ctx context.Context, ref string, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) { - rsp, err := c.CreateApiKey(ctx, ref, body, reqEditors...) +func (c *ClientWithResponses) CreateApiKeyWithResponse(ctx context.Context, ref string, params *CreateApiKeyParams, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) { + rsp, err := c.CreateApiKey(ctx, ref, params, body, reqEditors...) if err != nil { return nil, err } @@ -7800,25 +8003,34 @@ func (c *ClientWithResponses) CreateApiKeyWithResponse(ctx context.Context, ref } // DeleteApiKeyWithResponse request returning *DeleteApiKeyResponse -func (c *ClientWithResponses) DeleteApiKeyWithResponse(ctx context.Context, ref string, id string, reqEditors ...RequestEditorFn) (*DeleteApiKeyResponse, error) { - rsp, err := c.DeleteApiKey(ctx, ref, id, reqEditors...) +func (c *ClientWithResponses) DeleteApiKeyWithResponse(ctx context.Context, ref string, id string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*DeleteApiKeyResponse, error) { + rsp, err := c.DeleteApiKey(ctx, ref, id, params, reqEditors...) if err != nil { return nil, err } return ParseDeleteApiKeyResponse(rsp) } +// GetApiKeyWithResponse request returning *GetApiKeyResponse +func (c *ClientWithResponses) GetApiKeyWithResponse(ctx context.Context, ref string, id string, params *GetApiKeyParams, reqEditors ...RequestEditorFn) (*GetApiKeyResponse, error) { + rsp, err := c.GetApiKey(ctx, ref, id, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetApiKeyResponse(rsp) +} + // UpdateApiKeyWithBodyWithResponse request with arbitrary body returning *UpdateApiKeyResponse -func (c *ClientWithResponses) UpdateApiKeyWithBodyWithResponse(ctx context.Context, ref string, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) { - rsp, err := c.UpdateApiKeyWithBody(ctx, ref, id, contentType, body, reqEditors...) +func (c *ClientWithResponses) UpdateApiKeyWithBodyWithResponse(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) { + rsp, err := c.UpdateApiKeyWithBody(ctx, ref, id, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseUpdateApiKeyResponse(rsp) } -func (c *ClientWithResponses) UpdateApiKeyWithResponse(ctx context.Context, ref string, id string, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) { - rsp, err := c.UpdateApiKey(ctx, ref, id, body, reqEditors...) +func (c *ClientWithResponses) UpdateApiKeyWithResponse(ctx context.Context, ref string, id string, params *UpdateApiKeyParams, body UpdateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateApiKeyResponse, error) { + rsp, err := c.UpdateApiKey(ctx, ref, id, params, body, reqEditors...) if err != nil { return nil, err } @@ -8875,7 +9087,7 @@ func ParseV1ListAllProjectsResponse(rsp *http.Response) (*V1ListAllProjectsRespo switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []V1ProjectResponse + var dest []V1ProjectWithDatabaseResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -8953,7 +9165,7 @@ func ParseV1GetProjectResponse(rsp *http.Response) (*V1GetProjectResponse, error switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest V1ProjectResponse + var dest V1ProjectWithDatabaseResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -9042,6 +9254,32 @@ func ParseDeleteApiKeyResponse(rsp *http.Response) (*DeleteApiKeyResponse, error return response, nil } +// ParseGetApiKeyResponse parses an HTTP response from a GetApiKeyWithResponse call +func ParseGetApiKeyResponse(rsp *http.Response) (*GetApiKeyResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetApiKeyResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ApiKeyResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseUpdateApiKeyResponse parses an HTTP response from a UpdateApiKeyWithResponse call func ParseUpdateApiKeyResponse(rsp *http.Response) (*UpdateApiKeyResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 9334b80b8..4cfa1556b 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -111,16 +111,16 @@ const ( // Defines values for DesiredInstanceSize. const ( - Large DesiredInstanceSize = "large" - Medium DesiredInstanceSize = "medium" - Micro DesiredInstanceSize = "micro" - N12xlarge DesiredInstanceSize = "12xlarge" - N16xlarge DesiredInstanceSize = "16xlarge" - N2xlarge DesiredInstanceSize = "2xlarge" - N4xlarge DesiredInstanceSize = "4xlarge" - N8xlarge DesiredInstanceSize = "8xlarge" - Small DesiredInstanceSize = "small" - Xlarge DesiredInstanceSize = "xlarge" + DesiredInstanceSizeLarge DesiredInstanceSize = "large" + DesiredInstanceSizeMedium DesiredInstanceSize = "medium" + DesiredInstanceSizeMicro DesiredInstanceSize = "micro" + DesiredInstanceSizeN12xlarge DesiredInstanceSize = "12xlarge" + DesiredInstanceSizeN16xlarge DesiredInstanceSize = "16xlarge" + DesiredInstanceSizeN2xlarge DesiredInstanceSize = "2xlarge" + DesiredInstanceSizeN4xlarge DesiredInstanceSize = "4xlarge" + DesiredInstanceSizeN8xlarge DesiredInstanceSize = "8xlarge" + DesiredInstanceSizeSmall DesiredInstanceSize = "small" + DesiredInstanceSizeXlarge DesiredInstanceSize = "xlarge" ) // Defines values for FunctionResponseStatus. @@ -169,16 +169,16 @@ const ( // Defines values for PostgresEngine. const ( - N15 PostgresEngine = "15" + PostgresEngineN15 PostgresEngine = "15" ) // Defines values for ReleaseChannel. const ( - Alpha ReleaseChannel = "alpha" - Beta ReleaseChannel = "beta" - Ga ReleaseChannel = "ga" - Internal ReleaseChannel = "internal" - Withdrawn ReleaseChannel = "withdrawn" + ReleaseChannelAlpha ReleaseChannel = "alpha" + ReleaseChannelBeta ReleaseChannel = "beta" + ReleaseChannelGa ReleaseChannel = "ga" + ReleaseChannelInternal ReleaseChannel = "internal" + ReleaseChannelWithdrawn ReleaseChannel = "withdrawn" ) // Defines values for SetUpReadReplicaBodyReadReplicaRegion. @@ -297,32 +297,60 @@ const ( V1BackupStatusREMOVED V1BackupStatus = "REMOVED" ) -// Defines values for V1CreateProjectBodyPlan. +// Defines values for V1CreateProjectBodyDtoDesiredInstanceSize. const ( - V1CreateProjectBodyPlanFree V1CreateProjectBodyPlan = "free" - V1CreateProjectBodyPlanPro V1CreateProjectBodyPlan = "pro" + V1CreateProjectBodyDtoDesiredInstanceSizeLarge V1CreateProjectBodyDtoDesiredInstanceSize = "large" + V1CreateProjectBodyDtoDesiredInstanceSizeMedium V1CreateProjectBodyDtoDesiredInstanceSize = "medium" + V1CreateProjectBodyDtoDesiredInstanceSizeMicro V1CreateProjectBodyDtoDesiredInstanceSize = "micro" + V1CreateProjectBodyDtoDesiredInstanceSizeN12xlarge V1CreateProjectBodyDtoDesiredInstanceSize = "12xlarge" + V1CreateProjectBodyDtoDesiredInstanceSizeN16xlarge V1CreateProjectBodyDtoDesiredInstanceSize = "16xlarge" + V1CreateProjectBodyDtoDesiredInstanceSizeN2xlarge V1CreateProjectBodyDtoDesiredInstanceSize = "2xlarge" + V1CreateProjectBodyDtoDesiredInstanceSizeN4xlarge V1CreateProjectBodyDtoDesiredInstanceSize = "4xlarge" + V1CreateProjectBodyDtoDesiredInstanceSizeN8xlarge V1CreateProjectBodyDtoDesiredInstanceSize = "8xlarge" + V1CreateProjectBodyDtoDesiredInstanceSizeSmall V1CreateProjectBodyDtoDesiredInstanceSize = "small" + V1CreateProjectBodyDtoDesiredInstanceSizeXlarge V1CreateProjectBodyDtoDesiredInstanceSize = "xlarge" ) -// Defines values for V1CreateProjectBodyRegion. +// Defines values for V1CreateProjectBodyDtoPlan. const ( - V1CreateProjectBodyRegionApEast1 V1CreateProjectBodyRegion = "ap-east-1" - V1CreateProjectBodyRegionApNortheast1 V1CreateProjectBodyRegion = "ap-northeast-1" - V1CreateProjectBodyRegionApNortheast2 V1CreateProjectBodyRegion = "ap-northeast-2" - V1CreateProjectBodyRegionApSouth1 V1CreateProjectBodyRegion = "ap-south-1" - V1CreateProjectBodyRegionApSoutheast1 V1CreateProjectBodyRegion = "ap-southeast-1" - V1CreateProjectBodyRegionApSoutheast2 V1CreateProjectBodyRegion = "ap-southeast-2" - V1CreateProjectBodyRegionCaCentral1 V1CreateProjectBodyRegion = "ca-central-1" - V1CreateProjectBodyRegionEuCentral1 V1CreateProjectBodyRegion = "eu-central-1" - V1CreateProjectBodyRegionEuCentral2 V1CreateProjectBodyRegion = "eu-central-2" - V1CreateProjectBodyRegionEuNorth1 V1CreateProjectBodyRegion = "eu-north-1" - V1CreateProjectBodyRegionEuWest1 V1CreateProjectBodyRegion = "eu-west-1" - V1CreateProjectBodyRegionEuWest2 V1CreateProjectBodyRegion = "eu-west-2" - V1CreateProjectBodyRegionEuWest3 V1CreateProjectBodyRegion = "eu-west-3" - V1CreateProjectBodyRegionSaEast1 V1CreateProjectBodyRegion = "sa-east-1" - V1CreateProjectBodyRegionUsEast1 V1CreateProjectBodyRegion = "us-east-1" - V1CreateProjectBodyRegionUsEast2 V1CreateProjectBodyRegion = "us-east-2" - V1CreateProjectBodyRegionUsWest1 V1CreateProjectBodyRegion = "us-west-1" - V1CreateProjectBodyRegionUsWest2 V1CreateProjectBodyRegion = "us-west-2" + V1CreateProjectBodyDtoPlanFree V1CreateProjectBodyDtoPlan = "free" + V1CreateProjectBodyDtoPlanPro V1CreateProjectBodyDtoPlan = "pro" +) + +// Defines values for V1CreateProjectBodyDtoPostgresEngine. +const ( + V1CreateProjectBodyDtoPostgresEngineN15 V1CreateProjectBodyDtoPostgresEngine = "15" +) + +// Defines values for V1CreateProjectBodyDtoRegion. +const ( + V1CreateProjectBodyDtoRegionApEast1 V1CreateProjectBodyDtoRegion = "ap-east-1" + V1CreateProjectBodyDtoRegionApNortheast1 V1CreateProjectBodyDtoRegion = "ap-northeast-1" + V1CreateProjectBodyDtoRegionApNortheast2 V1CreateProjectBodyDtoRegion = "ap-northeast-2" + V1CreateProjectBodyDtoRegionApSouth1 V1CreateProjectBodyDtoRegion = "ap-south-1" + V1CreateProjectBodyDtoRegionApSoutheast1 V1CreateProjectBodyDtoRegion = "ap-southeast-1" + V1CreateProjectBodyDtoRegionApSoutheast2 V1CreateProjectBodyDtoRegion = "ap-southeast-2" + V1CreateProjectBodyDtoRegionCaCentral1 V1CreateProjectBodyDtoRegion = "ca-central-1" + V1CreateProjectBodyDtoRegionEuCentral1 V1CreateProjectBodyDtoRegion = "eu-central-1" + V1CreateProjectBodyDtoRegionEuCentral2 V1CreateProjectBodyDtoRegion = "eu-central-2" + V1CreateProjectBodyDtoRegionEuNorth1 V1CreateProjectBodyDtoRegion = "eu-north-1" + V1CreateProjectBodyDtoRegionEuWest1 V1CreateProjectBodyDtoRegion = "eu-west-1" + V1CreateProjectBodyDtoRegionEuWest2 V1CreateProjectBodyDtoRegion = "eu-west-2" + V1CreateProjectBodyDtoRegionEuWest3 V1CreateProjectBodyDtoRegion = "eu-west-3" + V1CreateProjectBodyDtoRegionSaEast1 V1CreateProjectBodyDtoRegion = "sa-east-1" + V1CreateProjectBodyDtoRegionUsEast1 V1CreateProjectBodyDtoRegion = "us-east-1" + V1CreateProjectBodyDtoRegionUsEast2 V1CreateProjectBodyDtoRegion = "us-east-2" + V1CreateProjectBodyDtoRegionUsWest1 V1CreateProjectBodyDtoRegion = "us-west-1" + V1CreateProjectBodyDtoRegionUsWest2 V1CreateProjectBodyDtoRegion = "us-west-2" +) + +// Defines values for V1CreateProjectBodyDtoReleaseChannel. +const ( + V1CreateProjectBodyDtoReleaseChannelAlpha V1CreateProjectBodyDtoReleaseChannel = "alpha" + V1CreateProjectBodyDtoReleaseChannelBeta V1CreateProjectBodyDtoReleaseChannel = "beta" + V1CreateProjectBodyDtoReleaseChannelGa V1CreateProjectBodyDtoReleaseChannel = "ga" + V1CreateProjectBodyDtoReleaseChannelInternal V1CreateProjectBodyDtoReleaseChannel = "internal" + V1CreateProjectBodyDtoReleaseChannelWithdrawn V1CreateProjectBodyDtoReleaseChannel = "withdrawn" ) // Defines values for V1OrganizationSlugResponseOptInTags. @@ -356,6 +384,25 @@ const ( V1ProjectResponseStatusUPGRADING V1ProjectResponseStatus = "UPGRADING" ) +// Defines values for V1ProjectWithDatabaseResponseStatus. +const ( + V1ProjectWithDatabaseResponseStatusACTIVEHEALTHY V1ProjectWithDatabaseResponseStatus = "ACTIVE_HEALTHY" + V1ProjectWithDatabaseResponseStatusACTIVEUNHEALTHY V1ProjectWithDatabaseResponseStatus = "ACTIVE_UNHEALTHY" + V1ProjectWithDatabaseResponseStatusCOMINGUP V1ProjectWithDatabaseResponseStatus = "COMING_UP" + V1ProjectWithDatabaseResponseStatusGOINGDOWN V1ProjectWithDatabaseResponseStatus = "GOING_DOWN" + V1ProjectWithDatabaseResponseStatusINACTIVE V1ProjectWithDatabaseResponseStatus = "INACTIVE" + V1ProjectWithDatabaseResponseStatusINITFAILED V1ProjectWithDatabaseResponseStatus = "INIT_FAILED" + V1ProjectWithDatabaseResponseStatusPAUSEFAILED V1ProjectWithDatabaseResponseStatus = "PAUSE_FAILED" + V1ProjectWithDatabaseResponseStatusPAUSING V1ProjectWithDatabaseResponseStatus = "PAUSING" + V1ProjectWithDatabaseResponseStatusREMOVED V1ProjectWithDatabaseResponseStatus = "REMOVED" + V1ProjectWithDatabaseResponseStatusRESIZING V1ProjectWithDatabaseResponseStatus = "RESIZING" + V1ProjectWithDatabaseResponseStatusRESTARTING V1ProjectWithDatabaseResponseStatus = "RESTARTING" + V1ProjectWithDatabaseResponseStatusRESTOREFAILED V1ProjectWithDatabaseResponseStatus = "RESTORE_FAILED" + V1ProjectWithDatabaseResponseStatusRESTORING V1ProjectWithDatabaseResponseStatus = "RESTORING" + V1ProjectWithDatabaseResponseStatusUNKNOWN V1ProjectWithDatabaseResponseStatus = "UNKNOWN" + V1ProjectWithDatabaseResponseStatusUPGRADING V1ProjectWithDatabaseResponseStatus = "UPGRADING" +) + // Defines values for V1ServiceHealthResponseName. const ( V1ServiceHealthResponseNameAuth V1ServiceHealthResponseName = "auth" @@ -368,9 +415,9 @@ const ( // Defines values for V1ServiceHealthResponseStatus. const ( - V1ServiceHealthResponseStatusACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" - V1ServiceHealthResponseStatusCOMINGUP V1ServiceHealthResponseStatus = "COMING_UP" - V1ServiceHealthResponseStatusUNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" + ACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" + COMINGUP V1ServiceHealthResponseStatus = "COMING_UP" + UNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" ) // Defines values for VanitySubdomainConfigResponseStatus. @@ -725,8 +772,8 @@ type CreateBranchBody struct { ReleaseChannel *ReleaseChannel `json:"release_channel,omitempty"` } -// CreateOrganizationBodyV1 defines model for CreateOrganizationBodyV1. -type CreateOrganizationBodyV1 struct { +// CreateOrganizationV1Dto defines model for CreateOrganizationV1Dto. +type CreateOrganizationV1Dto struct { Name string `json:"name"` } @@ -824,17 +871,18 @@ type Domain struct { // FunctionResponse defines model for FunctionResponse. type FunctionResponse struct { - CreatedAt int64 `json:"created_at"` - EntrypointPath *string `json:"entrypoint_path,omitempty"` - Id string `json:"id"` - ImportMap *bool `json:"import_map,omitempty"` - ImportMapPath *string `json:"import_map_path,omitempty"` - Name string `json:"name"` - Slug string `json:"slug"` - Status FunctionResponseStatus `json:"status"` - UpdatedAt int64 `json:"updated_at"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` - Version int `json:"version"` + ComputeMultiplier *float32 `json:"compute_multiplier,omitempty"` + CreatedAt int64 `json:"created_at"` + EntrypointPath *string `json:"entrypoint_path,omitempty"` + Id string `json:"id"` + ImportMap *bool `json:"import_map,omitempty"` + ImportMapPath *string `json:"import_map_path,omitempty"` + Name string `json:"name"` + Slug string `json:"slug"` + Status FunctionResponseStatus `json:"status"` + UpdatedAt int64 `json:"updated_at"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` + Version int `json:"version"` } // FunctionResponseStatus defines model for FunctionResponse.Status. @@ -842,17 +890,18 @@ type FunctionResponseStatus string // FunctionSlugResponse defines model for FunctionSlugResponse. type FunctionSlugResponse struct { - CreatedAt int64 `json:"created_at"` - EntrypointPath *string `json:"entrypoint_path,omitempty"` - Id string `json:"id"` - ImportMap *bool `json:"import_map,omitempty"` - ImportMapPath *string `json:"import_map_path,omitempty"` - Name string `json:"name"` - Slug string `json:"slug"` - Status FunctionSlugResponseStatus `json:"status"` - UpdatedAt int64 `json:"updated_at"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` - Version int `json:"version"` + ComputeMultiplier *float32 `json:"compute_multiplier,omitempty"` + CreatedAt int64 `json:"created_at"` + EntrypointPath *string `json:"entrypoint_path,omitempty"` + Id string `json:"id"` + ImportMap *bool `json:"import_map,omitempty"` + ImportMapPath *string `json:"import_map_path,omitempty"` + Name string `json:"name"` + Slug string `json:"slug"` + Status FunctionSlugResponseStatus `json:"status"` + UpdatedAt int64 `json:"updated_at"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` + Version int `json:"version"` } // FunctionSlugResponseStatus defines model for FunctionSlugResponse.Status. @@ -1539,17 +1588,18 @@ type V1BackupsResponse struct { // V1CreateFunctionBody defines model for V1CreateFunctionBody. type V1CreateFunctionBody struct { - Body string `json:"body"` - Name string `json:"name"` - Slug string `json:"slug"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` + Body string `json:"body"` + ComputeMultiplier *float32 `json:"compute_multiplier,omitempty"` + Name string `json:"name"` + Slug string `json:"slug"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` } -// V1CreateProjectBody defines model for V1CreateProjectBody. -type V1CreateProjectBody struct { +// V1CreateProjectBodyDto defines model for V1CreateProjectBodyDto. +type V1CreateProjectBodyDto struct { // DbPass Database password - DbPass string `json:"db_pass"` - DesiredInstanceSize *DesiredInstanceSize `json:"desired_instance_size,omitempty"` + DbPass string `json:"db_pass"` + DesiredInstanceSize *V1CreateProjectBodyDtoDesiredInstanceSize `json:"desired_instance_size,omitempty"` // KpsEnabled This field is deprecated and is ignored in this request // Deprecated: @@ -1563,24 +1613,35 @@ type V1CreateProjectBody struct { // Plan Subscription Plan is now set on organization level and is ignored in this request // Deprecated: - Plan *V1CreateProjectBodyPlan `json:"plan,omitempty"` + Plan *V1CreateProjectBodyDtoPlan `json:"plan,omitempty"` // PostgresEngine Postgres engine version. If not provided, the latest version will be used. - PostgresEngine *PostgresEngine `json:"postgres_engine,omitempty"` + PostgresEngine *V1CreateProjectBodyDtoPostgresEngine `json:"postgres_engine,omitempty"` // Region Region you want your server to reside in - Region V1CreateProjectBodyRegion `json:"region"` - ReleaseChannel *ReleaseChannel `json:"release_channel,omitempty"` + Region V1CreateProjectBodyDtoRegion `json:"region"` + + // ReleaseChannel Release channel. If not provided, GA will be used. + ReleaseChannel *V1CreateProjectBodyDtoReleaseChannel `json:"release_channel,omitempty"` // TemplateUrl Template URL used to create the project from the CLI. TemplateUrl *string `json:"template_url,omitempty"` } -// V1CreateProjectBodyPlan Subscription Plan is now set on organization level and is ignored in this request -type V1CreateProjectBodyPlan string +// V1CreateProjectBodyDtoDesiredInstanceSize defines model for V1CreateProjectBodyDto.DesiredInstanceSize. +type V1CreateProjectBodyDtoDesiredInstanceSize string + +// V1CreateProjectBodyDtoPlan Subscription Plan is now set on organization level and is ignored in this request +type V1CreateProjectBodyDtoPlan string + +// V1CreateProjectBodyDtoPostgresEngine Postgres engine version. If not provided, the latest version will be used. +type V1CreateProjectBodyDtoPostgresEngine string -// V1CreateProjectBodyRegion Region you want your server to reside in -type V1CreateProjectBodyRegion string +// V1CreateProjectBodyDtoRegion Region you want your server to reside in +type V1CreateProjectBodyDtoRegion string + +// V1CreateProjectBodyDtoReleaseChannel Release channel. If not provided, GA will be used. +type V1CreateProjectBodyDtoReleaseChannel string // V1DatabaseResponse defines model for V1DatabaseResponse. type V1DatabaseResponse struct { @@ -1656,8 +1717,7 @@ type V1ProjectRefResponse struct { // V1ProjectResponse defines model for V1ProjectResponse. type V1ProjectResponse struct { // CreatedAt Creation timestamp - CreatedAt string `json:"created_at"` - Database *V1DatabaseResponse `json:"database,omitempty"` + CreatedAt string `json:"created_at"` // Id Id of your project Id string `json:"id"` @@ -1676,6 +1736,29 @@ type V1ProjectResponse struct { // V1ProjectResponseStatus defines model for V1ProjectResponse.Status. type V1ProjectResponseStatus string +// V1ProjectWithDatabaseResponse defines model for V1ProjectWithDatabaseResponse. +type V1ProjectWithDatabaseResponse struct { + // CreatedAt Creation timestamp + CreatedAt string `json:"created_at"` + Database V1DatabaseResponse `json:"database"` + + // Id Id of your project + Id string `json:"id"` + + // Name Name of your project + Name string `json:"name"` + + // OrganizationId Slug of your organization + OrganizationId string `json:"organization_id"` + + // Region Region of your project + Region string `json:"region"` + Status V1ProjectWithDatabaseResponseStatus `json:"status"` +} + +// V1ProjectWithDatabaseResponseStatus defines model for V1ProjectWithDatabaseResponse.Status. +type V1ProjectWithDatabaseResponseStatus string + // V1RestorePitrBody defines model for V1RestorePitrBody. type V1RestorePitrBody struct { RecoveryTimeTargetUnix int64 `json:"recovery_time_target_unix"` @@ -1718,9 +1801,10 @@ type V1StorageBucketResponse struct { // V1UpdateFunctionBody defines model for V1UpdateFunctionBody. type V1UpdateFunctionBody struct { - Body *string `json:"body,omitempty"` - Name *string `json:"name,omitempty"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` + Body *string `json:"body,omitempty"` + ComputeMultiplier *float32 `json:"compute_multiplier,omitempty"` + Name *string `json:"name,omitempty"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` } // ValidationError defines model for ValidationError. @@ -1766,24 +1850,51 @@ type V1AuthorizeUserParamsResponseType string // V1AuthorizeUserParamsCodeChallengeMethod defines parameters for V1AuthorizeUser. type V1AuthorizeUserParamsCodeChallengeMethod string +// V1GetProjectApiKeysParams defines parameters for V1GetProjectApiKeys. +type V1GetProjectApiKeysParams struct { + Reveal bool `form:"reveal" json:"reveal"` +} + +// CreateApiKeyParams defines parameters for CreateApiKey. +type CreateApiKeyParams struct { + Reveal bool `form:"reveal" json:"reveal"` +} + +// DeleteApiKeyParams defines parameters for DeleteApiKey. +type DeleteApiKeyParams struct { + Reveal bool `form:"reveal" json:"reveal"` +} + +// GetApiKeyParams defines parameters for GetApiKey. +type GetApiKeyParams struct { + Reveal bool `form:"reveal" json:"reveal"` +} + +// UpdateApiKeyParams defines parameters for UpdateApiKey. +type UpdateApiKeyParams struct { + Reveal bool `form:"reveal" json:"reveal"` +} + // V1CreateAFunctionParams defines parameters for V1CreateAFunction. type V1CreateAFunctionParams struct { - Slug *string `form:"slug,omitempty" json:"slug,omitempty"` - Name *string `form:"name,omitempty" json:"name,omitempty"` - VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` - ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` - EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` - ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + Slug *string `form:"slug,omitempty" json:"slug,omitempty"` + Name *string `form:"name,omitempty" json:"name,omitempty"` + VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` + ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` + EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` + ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + ComputeMultiplier *float32 `form:"compute_multiplier,omitempty" json:"compute_multiplier,omitempty"` } // V1UpdateAFunctionParams defines parameters for V1UpdateAFunction. type V1UpdateAFunctionParams struct { - Slug *string `form:"slug,omitempty" json:"slug,omitempty"` - Name *string `form:"name,omitempty" json:"name,omitempty"` - VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` - ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` - EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` - ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + Slug *string `form:"slug,omitempty" json:"slug,omitempty"` + Name *string `form:"name,omitempty" json:"name,omitempty"` + VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` + ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` + EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` + ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + ComputeMultiplier *float32 `form:"compute_multiplier,omitempty" json:"compute_multiplier,omitempty"` } // V1GetServicesHealthParams defines parameters for V1GetServicesHealth. @@ -1823,10 +1934,10 @@ type V1UpdateABranchConfigJSONRequestBody = UpdateBranchBody type V1ExchangeOauthTokenFormdataRequestBody = OAuthTokenBody // V1CreateAnOrganizationJSONRequestBody defines body for V1CreateAnOrganization for application/json ContentType. -type V1CreateAnOrganizationJSONRequestBody = CreateOrganizationBodyV1 +type V1CreateAnOrganizationJSONRequestBody = CreateOrganizationV1Dto // V1CreateAProjectJSONRequestBody defines body for V1CreateAProject for application/json ContentType. -type V1CreateAProjectJSONRequestBody = V1CreateProjectBody +type V1CreateAProjectJSONRequestBody = V1CreateProjectBodyDto // CreateApiKeyJSONRequestBody defines body for CreateApiKey for application/json ContentType. type CreateApiKeyJSONRequestBody = CreateApiKeyBody diff --git a/pkg/config/api.go b/pkg/config/api.go index 405bcb7bd..ec7c4f86d 100644 --- a/pkg/config/api.go +++ b/pkg/config/api.go @@ -55,7 +55,7 @@ func (a *api) ToUpdatePostgrestConfigBody() v1API.UpdatePostgrestConfigBody { return body } -func (a *api) fromRemoteApiConfig(remoteConfig v1API.PostgrestConfigWithJWTSecretResponse) { +func (a *api) FromRemoteApiConfig(remoteConfig v1API.PostgrestConfigWithJWTSecretResponse) { if a.Enabled = len(remoteConfig.DbSchema) > 0; !a.Enabled { return } @@ -84,7 +84,7 @@ func (a *api) DiffWithRemote(remoteConfig v1API.PostgrestConfigWithJWTSecretResp if err != nil { return nil, err } - copy.fromRemoteApiConfig(remoteConfig) + copy.FromRemoteApiConfig(remoteConfig) remoteCompare, err := ToTomlBytes(copy) if err != nil { return nil, err diff --git a/pkg/config/auth.go b/pkg/config/auth.go index a2ea016f2..10611c23b 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -10,19 +10,54 @@ import ( "github.com/supabase/cli/pkg/diff" ) +type PasswordRequirements string + +const ( + NoRequirements PasswordRequirements = "" + LettersDigits PasswordRequirements = "letters_digits" + LowerUpperLettersDigits PasswordRequirements = "lower_upper_letters_digits" + LowerUpperLettersDigitsSymbols PasswordRequirements = "lower_upper_letters_digits_symbols" +) + +func (r PasswordRequirements) ToChar() v1API.UpdateAuthConfigBodyPasswordRequiredCharacters { + switch r { + case LettersDigits: + return v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + case LowerUpperLettersDigits: + return v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567891 + case LowerUpperLettersDigitsSymbols: + return v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567892 + } + return v1API.Empty +} + +func NewPasswordRequirement(c v1API.UpdateAuthConfigBodyPasswordRequiredCharacters) PasswordRequirements { + switch c { + case v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789: + return LettersDigits + case v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567891: + return LowerUpperLettersDigits + case v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567892: + return LowerUpperLettersDigitsSymbols + } + return NoRequirements +} + type ( auth struct { Enabled bool `toml:"enabled"` Image string `toml:"-"` - SiteUrl string `toml:"site_url"` - AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` - JwtExpiry uint `toml:"jwt_expiry"` - EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` - RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` - EnableManualLinking bool `toml:"enable_manual_linking"` - EnableSignup bool `toml:"enable_signup"` - EnableAnonymousSignIns bool `toml:"enable_anonymous_sign_ins"` + SiteUrl string `toml:"site_url"` + AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` + JwtExpiry uint `toml:"jwt_expiry"` + EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` + RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` + EnableManualLinking bool `toml:"enable_manual_linking"` + EnableSignup bool `toml:"enable_signup"` + EnableAnonymousSignIns bool `toml:"enable_anonymous_sign_ins"` + MinimumPasswordLength uint `toml:"minimum_password_length"` + PasswordRequirements PasswordRequirements `toml:"password_requirements"` Hook hook `toml:"hook"` MFA mfa `toml:"mfa"` @@ -192,6 +227,8 @@ func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody { SecurityManualLinkingEnabled: &a.EnableManualLinking, DisableSignup: cast.Ptr(!a.EnableSignup), ExternalAnonymousUsersEnabled: &a.EnableAnonymousSignIns, + PasswordMinLength: cast.UintToIntPtr(&a.MinimumPasswordLength), + PasswordRequiredCharacters: cast.Ptr(a.PasswordRequirements.ToChar()), } a.Hook.toAuthConfigBody(&body) a.MFA.toAuthConfigBody(&body) @@ -202,7 +239,7 @@ func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody { return body } -func (a *auth) fromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) { +func (a *auth) FromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) { a.SiteUrl = cast.Val(remoteConfig.SiteUrl, "") a.AdditionalRedirectUrls = strToArr(cast.Val(remoteConfig.UriAllowList, "")) a.JwtExpiry = cast.IntToUint(cast.Val(remoteConfig.JwtExp, 0)) @@ -211,6 +248,9 @@ func (a *auth) fromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) { a.EnableManualLinking = cast.Val(remoteConfig.SecurityManualLinkingEnabled, false) a.EnableSignup = !cast.Val(remoteConfig.DisableSignup, false) a.EnableAnonymousSignIns = cast.Val(remoteConfig.ExternalAnonymousUsersEnabled, false) + a.MinimumPasswordLength = cast.IntToUint(cast.Val(remoteConfig.PasswordMinLength, 0)) + prc := cast.Val(remoteConfig.PasswordRequiredCharacters, "") + a.PasswordRequirements = NewPasswordRequirement(v1API.UpdateAuthConfigBodyPasswordRequiredCharacters(prc)) a.Hook.fromAuthConfig(remoteConfig) a.MFA.fromAuthConfig(remoteConfig) a.Sessions.fromAuthConfig(remoteConfig) @@ -222,53 +262,76 @@ func (a *auth) fromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) { func (h hook) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { if body.HookCustomAccessTokenEnabled = &h.CustomAccessToken.Enabled; *body.HookCustomAccessTokenEnabled { body.HookCustomAccessTokenUri = &h.CustomAccessToken.URI - body.HookCustomAccessTokenSecrets = &h.CustomAccessToken.Secrets + if len(h.CustomAccessToken.Secrets) > 0 { + body.HookCustomAccessTokenSecrets = &h.CustomAccessToken.Secrets + } } if body.HookSendEmailEnabled = &h.SendEmail.Enabled; *body.HookSendEmailEnabled { body.HookSendEmailUri = &h.SendEmail.URI - body.HookSendEmailSecrets = &h.SendEmail.Secrets + if len(h.SendEmail.Secrets) > 0 { + body.HookSendEmailSecrets = &h.SendEmail.Secrets + } } if body.HookSendSmsEnabled = &h.SendSMS.Enabled; *body.HookSendSmsEnabled { body.HookSendSmsUri = &h.SendSMS.URI - body.HookSendSmsSecrets = &h.SendSMS.Secrets + if len(h.SendSMS.Secrets) > 0 { + body.HookSendSmsSecrets = &h.SendSMS.Secrets + } } // Enterprise and team only features if body.HookMfaVerificationAttemptEnabled = &h.MFAVerificationAttempt.Enabled; *body.HookMfaVerificationAttemptEnabled { body.HookMfaVerificationAttemptUri = &h.MFAVerificationAttempt.URI - body.HookMfaVerificationAttemptSecrets = &h.MFAVerificationAttempt.Secrets + if len(h.MFAVerificationAttempt.Secrets) > 0 { + body.HookMfaVerificationAttemptSecrets = &h.MFAVerificationAttempt.Secrets + } } if body.HookPasswordVerificationAttemptEnabled = &h.PasswordVerificationAttempt.Enabled; *body.HookPasswordVerificationAttemptEnabled { body.HookPasswordVerificationAttemptUri = &h.PasswordVerificationAttempt.URI - body.HookPasswordVerificationAttemptSecrets = &h.PasswordVerificationAttempt.Secrets + if len(h.PasswordVerificationAttempt.Secrets) > 0 { + body.HookPasswordVerificationAttemptSecrets = &h.PasswordVerificationAttempt.Secrets + } } } - func (h *hook) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { // Ignore disabled hooks because their envs are not loaded if h.CustomAccessToken.Enabled { h.CustomAccessToken.URI = cast.Val(remoteConfig.HookCustomAccessTokenUri, "") - h.CustomAccessToken.Secrets = hashPrefix + cast.Val(remoteConfig.HookCustomAccessTokenSecrets, "") + if remoteConfig.HookCustomAccessTokenSecrets != nil { + h.CustomAccessToken.Secrets = hashPrefix + cast.Val(remoteConfig.HookCustomAccessTokenSecrets, "") + } } h.CustomAccessToken.Enabled = cast.Val(remoteConfig.HookCustomAccessTokenEnabled, false) + if h.SendEmail.Enabled { h.SendEmail.URI = cast.Val(remoteConfig.HookSendEmailUri, "") - h.SendEmail.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendEmailSecrets, "") + if remoteConfig.HookSendEmailSecrets != nil { + h.SendEmail.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendEmailSecrets, "") + } } h.SendEmail.Enabled = cast.Val(remoteConfig.HookSendEmailEnabled, false) + if h.SendSMS.Enabled { h.SendSMS.URI = cast.Val(remoteConfig.HookSendSmsUri, "") - h.SendSMS.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendSmsSecrets, "") + if remoteConfig.HookSendSmsSecrets != nil { + h.SendSMS.Secrets = hashPrefix + cast.Val(remoteConfig.HookSendSmsSecrets, "") + } } h.SendSMS.Enabled = cast.Val(remoteConfig.HookSendSmsEnabled, false) + // Enterprise and team only features if h.MFAVerificationAttempt.Enabled { h.MFAVerificationAttempt.URI = cast.Val(remoteConfig.HookMfaVerificationAttemptUri, "") - h.MFAVerificationAttempt.Secrets = hashPrefix + cast.Val(remoteConfig.HookMfaVerificationAttemptSecrets, "") + if remoteConfig.HookMfaVerificationAttemptSecrets != nil { + h.MFAVerificationAttempt.Secrets = hashPrefix + cast.Val(remoteConfig.HookMfaVerificationAttemptSecrets, "") + } } h.MFAVerificationAttempt.Enabled = cast.Val(remoteConfig.HookMfaVerificationAttemptEnabled, false) + if h.PasswordVerificationAttempt.Enabled { h.PasswordVerificationAttempt.URI = cast.Val(remoteConfig.HookPasswordVerificationAttemptUri, "") - h.PasswordVerificationAttempt.Secrets = hashPrefix + cast.Val(remoteConfig.HookPasswordVerificationAttemptSecrets, "") + if remoteConfig.HookPasswordVerificationAttemptSecrets != nil { + h.PasswordVerificationAttempt.Secrets = hashPrefix + cast.Val(remoteConfig.HookPasswordVerificationAttemptSecrets, "") + } } h.PasswordVerificationAttempt.Enabled = cast.Val(remoteConfig.HookPasswordVerificationAttemptEnabled, false) } @@ -775,13 +838,13 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { func (a *auth) DiffWithRemote(projectRef string, remoteConfig v1API.AuthConfigResponse) ([]byte, error) { copy := a.Clone() - copy.hashSecrets(projectRef) + copy.HashSecrets(projectRef) // Convert the config values into easily comparable remoteConfig values currentValue, err := ToTomlBytes(copy) if err != nil { return nil, err } - copy.fromRemoteAuthConfig(remoteConfig) + copy.FromRemoteAuthConfig(remoteConfig) remoteCompare, err := ToTomlBytes(copy) if err != nil { return nil, err @@ -791,7 +854,7 @@ func (a *auth) DiffWithRemote(projectRef string, remoteConfig v1API.AuthConfigRe const hashPrefix = "hash:" -func (a *auth) hashSecrets(key string) { +func (a *auth) HashSecrets(key string) { hash := func(v string) string { return hashPrefix + sha256Hmac(key, v) } @@ -811,19 +874,19 @@ func (a *auth) hashSecrets(key string) { case a.Sms.Vonage.Enabled: a.Sms.Vonage.ApiSecret = hash(a.Sms.Vonage.ApiSecret) } - if a.Hook.MFAVerificationAttempt.Enabled { + if a.Hook.MFAVerificationAttempt.Enabled && len(a.Hook.MFAVerificationAttempt.Secrets) > 0 { a.Hook.MFAVerificationAttempt.Secrets = hash(a.Hook.MFAVerificationAttempt.Secrets) } - if a.Hook.PasswordVerificationAttempt.Enabled { + if a.Hook.PasswordVerificationAttempt.Enabled && len(a.Hook.PasswordVerificationAttempt.Secrets) > 0 { a.Hook.PasswordVerificationAttempt.Secrets = hash(a.Hook.PasswordVerificationAttempt.Secrets) } - if a.Hook.CustomAccessToken.Enabled { + if a.Hook.CustomAccessToken.Enabled && len(a.Hook.CustomAccessToken.Secrets) > 0 { a.Hook.CustomAccessToken.Secrets = hash(a.Hook.CustomAccessToken.Secrets) } - if a.Hook.SendSMS.Enabled { + if a.Hook.SendSMS.Enabled && len(a.Hook.SendSMS.Secrets) > 0 { a.Hook.SendSMS.Secrets = hash(a.Hook.SendSMS.Secrets) } - if a.Hook.SendEmail.Enabled { + if a.Hook.SendEmail.Enabled && len(a.Hook.SendEmail.Secrets) > 0 { a.Hook.SendEmail.Secrets = hash(a.Hook.SendEmail.Secrets) } for name, provider := range a.External { diff --git a/pkg/config/auth_test.go b/pkg/config/auth_test.go index e8d373b9f..057c5ccdd 100644 --- a/pkg/config/auth_test.go +++ b/pkg/config/auth_test.go @@ -18,6 +18,9 @@ func newWithDefaults() auth { Email: email{ EnableConfirmations: true, }, + Sms: sms{ + TestOTP: map[string]string{}, + }, } } @@ -31,33 +34,134 @@ func assertSnapshotEqual(t *testing.T, actual []byte) { assert.Equal(t, string(expected), string(actual)) } +func TestAuthDiff(t *testing.T) { + t.Run("local and remote enabled", func(t *testing.T) { + c := newWithDefaults() + c.SiteUrl = "http://127.0.0.1:3000" + c.AdditionalRedirectUrls = []string{"https://127.0.0.1:3000"} + c.JwtExpiry = 3600 + c.EnableRefreshTokenRotation = true + c.RefreshTokenReuseInterval = 10 + c.EnableManualLinking = true + c.EnableSignup = true + c.EnableAnonymousSignIns = true + c.MinimumPasswordLength = 6 + c.PasswordRequirements = LettersDigits + // Run test + diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + SiteUrl: cast.Ptr("http://127.0.0.1:3000"), + UriAllowList: cast.Ptr("https://127.0.0.1:3000"), + JwtExp: cast.Ptr(3600), + RefreshTokenRotationEnabled: cast.Ptr(true), + SecurityRefreshTokenReuseInterval: cast.Ptr(10), + SecurityManualLinkingEnabled: cast.Ptr(true), + DisableSignup: cast.Ptr(false), + ExternalAnonymousUsersEnabled: cast.Ptr(true), + PasswordMinLength: cast.Ptr(6), + PasswordRequiredCharacters: cast.Ptr(string(v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)), + }) + // Check error + assert.NoError(t, err) + assert.Empty(t, string(diff)) + }) + + t.Run("local enabled and disabled", func(t *testing.T) { + c := newWithDefaults() + c.SiteUrl = "http://127.0.0.1:3000" + c.AdditionalRedirectUrls = []string{"https://127.0.0.1:3000"} + c.JwtExpiry = 3600 + c.EnableRefreshTokenRotation = false + c.RefreshTokenReuseInterval = 10 + c.EnableManualLinking = false + c.EnableSignup = false + c.EnableAnonymousSignIns = false + c.MinimumPasswordLength = 6 + c.PasswordRequirements = LowerUpperLettersDigitsSymbols + // Run test + diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + SiteUrl: cast.Ptr(""), + UriAllowList: cast.Ptr("https://127.0.0.1:3000,https://ref.supabase.co"), + JwtExp: cast.Ptr(0), + RefreshTokenRotationEnabled: cast.Ptr(true), + SecurityRefreshTokenReuseInterval: cast.Ptr(0), + SecurityManualLinkingEnabled: cast.Ptr(true), + DisableSignup: cast.Ptr(false), + ExternalAnonymousUsersEnabled: cast.Ptr(true), + PasswordMinLength: cast.Ptr(8), + PasswordRequiredCharacters: cast.Ptr(string(v1API.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)), + }) + // Check error + assert.NoError(t, err) + assertSnapshotEqual(t, diff) + }) + + t.Run("local and remote disabled", func(t *testing.T) { + c := newWithDefaults() + c.EnableSignup = false + // Run test + diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ + SiteUrl: cast.Ptr(""), + UriAllowList: cast.Ptr(""), + JwtExp: cast.Ptr(0), + RefreshTokenRotationEnabled: cast.Ptr(false), + SecurityRefreshTokenReuseInterval: cast.Ptr(0), + SecurityManualLinkingEnabled: cast.Ptr(false), + DisableSignup: cast.Ptr(true), + ExternalAnonymousUsersEnabled: cast.Ptr(false), + PasswordMinLength: cast.Ptr(0), + PasswordRequiredCharacters: cast.Ptr(""), + }) + // Check error + assert.NoError(t, err) + assert.Empty(t, string(diff)) + }) +} + func TestHookDiff(t *testing.T) { t.Run("local and remote enabled", func(t *testing.T) { c := newWithDefaults() c.Hook = hook{ - CustomAccessToken: hookConfig{Enabled: true}, - SendSMS: hookConfig{Enabled: true}, - SendEmail: hookConfig{Enabled: true}, - MFAVerificationAttempt: hookConfig{Enabled: true}, - PasswordVerificationAttempt: hookConfig{Enabled: true}, + CustomAccessToken: hookConfig{ + Enabled: true, + URI: "http://example.com", + Secrets: "test-secret", + }, + SendSMS: hookConfig{ + Enabled: true, + URI: "http://example.com", + Secrets: "test-secret", + }, + SendEmail: hookConfig{ + Enabled: true, + URI: "https://example.com", + Secrets: "test-secret", + }, + MFAVerificationAttempt: hookConfig{ + Enabled: true, + URI: "https://example.com", + Secrets: "test-secret", + }, + PasswordVerificationAttempt: hookConfig{ + Enabled: true, + URI: "pg-functions://verifyPassword", + }, } // Run test diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ HookCustomAccessTokenEnabled: cast.Ptr(true), - HookCustomAccessTokenUri: cast.Ptr(""), - HookCustomAccessTokenSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookCustomAccessTokenUri: cast.Ptr("http://example.com"), + HookCustomAccessTokenSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendEmailEnabled: cast.Ptr(true), - HookSendEmailUri: cast.Ptr(""), - HookSendEmailSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookSendEmailUri: cast.Ptr("https://example.com"), + HookSendEmailSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookSendSmsEnabled: cast.Ptr(true), - HookSendSmsUri: cast.Ptr(""), - HookSendSmsSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookSendSmsUri: cast.Ptr("http://example.com"), + HookSendSmsSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookMfaVerificationAttemptEnabled: cast.Ptr(true), - HookMfaVerificationAttemptUri: cast.Ptr(""), - HookMfaVerificationAttemptSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookMfaVerificationAttemptUri: cast.Ptr("https://example.com"), + HookMfaVerificationAttemptSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), HookPasswordVerificationAttemptEnabled: cast.Ptr(true), - HookPasswordVerificationAttemptUri: cast.Ptr(""), - HookPasswordVerificationAttemptSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookPasswordVerificationAttemptUri: cast.Ptr("pg-functions://verifyPassword"), }) // Check error assert.NoError(t, err) @@ -67,17 +171,41 @@ func TestHookDiff(t *testing.T) { t.Run("local enabled and disabled", func(t *testing.T) { c := newWithDefaults() c.Hook = hook{ - CustomAccessToken: hookConfig{Enabled: true}, - MFAVerificationAttempt: hookConfig{Enabled: false}, + CustomAccessToken: hookConfig{ + Enabled: true, + URI: "http://example.com", + Secrets: "test-secret", + }, + SendSMS: hookConfig{ + Enabled: false, + URI: "https://example.com", + Secrets: "test-secret", + }, + SendEmail: hookConfig{ + Enabled: true, + URI: "pg-functions://sendEmail", + }, + MFAVerificationAttempt: hookConfig{ + Enabled: false, + URI: "pg-functions://verifyMFA", + }, + PasswordVerificationAttempt: hookConfig{Enabled: false}, } // Run test diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ - HookCustomAccessTokenEnabled: cast.Ptr(false), - HookCustomAccessTokenUri: cast.Ptr(""), - HookCustomAccessTokenSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), - HookMfaVerificationAttemptEnabled: cast.Ptr(true), - HookMfaVerificationAttemptUri: cast.Ptr(""), - HookMfaVerificationAttemptSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookCustomAccessTokenEnabled: cast.Ptr(false), + HookCustomAccessTokenUri: cast.Ptr(""), + HookCustomAccessTokenSecrets: cast.Ptr("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + HookSendEmailEnabled: cast.Ptr(false), + HookSendEmailUri: cast.Ptr(""), + HookSendSmsEnabled: cast.Ptr(true), + HookSendSmsUri: cast.Ptr("http://example.com"), + HookSendSmsSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), + HookMfaVerificationAttemptEnabled: cast.Ptr(true), + HookMfaVerificationAttemptUri: cast.Ptr("pg-functions://verifyMFA"), + HookPasswordVerificationAttemptEnabled: cast.Ptr(true), + HookPasswordVerificationAttemptUri: cast.Ptr("https://example.com"), + HookPasswordVerificationAttemptSecrets: cast.Ptr("ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252"), }) // Check error assert.NoError(t, err) @@ -563,9 +691,7 @@ func TestSmsDiff(t *testing.T) { // This is not a valid config because platform requires a SMS provider. // For consistency, we handle this in config.Load and emit a warning. c := newWithDefaults() - c.Sms = sms{ - EnableSignup: true, - } + c.Sms.EnableSignup = true // Run test diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), @@ -578,11 +704,7 @@ func TestSmsDiff(t *testing.T) { t.Run("enable provider without sign up", func(t *testing.T) { c := newWithDefaults() - c.Sms = sms{ - Messagebird: messagebirdConfig{ - Enabled: true, - }, - } + c.Sms.Messagebird.Enabled = true // Run test diff, err := c.DiffWithRemote("", v1API.AuthConfigResponse{ ExternalPhoneEnabled: cast.Ptr(false), diff --git a/pkg/config/config.go b/pkg/config/config.go index b1e7ed570..f5a56dfc0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -159,11 +159,13 @@ type ( } inbucket struct { - Enabled bool `toml:"enabled"` - Image string `toml:"-"` - Port uint16 `toml:"port"` - SmtpPort uint16 `toml:"smtp_port"` - Pop3Port uint16 `toml:"pop3_port"` + Enabled bool `toml:"enabled"` + Image string `toml:"-"` + Port uint16 `toml:"port"` + SmtpPort uint16 `toml:"smtp_port"` + Pop3Port uint16 `toml:"pop3_port"` + AdminEmail string `toml:"admin_email"` + SenderName string `toml:"sender_name"` } edgeRuntime struct { @@ -301,6 +303,9 @@ func NewConfig(editors ...ConfigEditor) config { "reauthentication": {}, }, }, + Sms: sms{ + TestOTP: map[string]string{}, + }, External: map[string]provider{ "apple": {}, "azure": {}, @@ -327,7 +332,9 @@ func NewConfig(editors ...ConfigEditor) config { JwtSecret: defaultJwtSecret, }, Inbucket: inbucket{ - Image: inbucketImage, + Image: inbucketImage, + AdminEmail: "admin@email.com", + SenderName: "Admin", }, Studio: studio{ Image: studioImage, @@ -666,6 +673,10 @@ func (c *baseConfig) Validate(fsys fs.FS) error { return errors.Errorf("Invalid config for auth.additional_redirect_urls[%d]: %v", i, err) } } + allowed := []PasswordRequirements{NoRequirements, LettersDigits, LowerUpperLettersDigits, LowerUpperLettersDigitsSymbols} + if !sliceContains(allowed, c.Auth.PasswordRequirements) { + return errors.Errorf("Invalid config for auth.password_requirements. Must be one of: %v", allowed) + } if err := c.Auth.Hook.validate(); err != nil { return err } @@ -982,14 +993,25 @@ func (h *hookConfig) validate(hookType string) (err error) { return nil } if h.URI == "" { - return errors.Errorf("missing required field in config: auth.hook.%s.uri", hookType) - } else if parsed, err := url.Parse(h.URI); err != nil { + return errors.Errorf("Missing required field in config: auth.hook.%s.uri", hookType) + } + parsed, err := url.Parse(h.URI) + if err != nil { return errors.Errorf("failed to parse template url: %w", err) - } else if !(parsed.Scheme == "http" || parsed.Scheme == "https" || parsed.Scheme == "pg-functions") { - return errors.Errorf("Invalid HTTP hook config: auth.hook.%v should be a Postgres function URI, or a HTTP or HTTPS URL", hookType) } - if h.Secrets, err = maybeLoadEnv(h.Secrets); err != nil { - return errors.Errorf("missing required field in config: auth.hook.%s.secrets", hookType) + switch strings.ToLower(parsed.Scheme) { + case "http", "https": + if len(h.Secrets) == 0 { + return errors.Errorf("Missing required field in config: auth.hook.%s.secrets", hookType) + } else if h.Secrets, err = maybeLoadEnv(h.Secrets); err != nil { + return err + } + case "pg-functions": + if len(h.Secrets) > 0 { + return errors.Errorf("Invalid hook config: auth.hook.%s.secrets is unsupported for pg-functions URI", hookType) + } + default: + return errors.Errorf("Invalid hook config: auth.hook.%v should be a HTTP, HTTPS, or pg-functions URI", hookType) } return nil } @@ -1059,19 +1081,16 @@ func (c *tpaCognito) issuerURL() string { return fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", c.UserPoolRegion, c.UserPoolID) } -func (c *tpaCognito) validate() error { +func (c *tpaCognito) validate() (err error) { if c.UserPoolID == "" { return errors.New("Invalid config: auth.third_party.cognito is enabled but without a user_pool_id.") - } - var err error - if c.UserPoolID, err = maybeLoadEnv(c.UserPoolID); err != nil { + } else if c.UserPoolID, err = maybeLoadEnv(c.UserPoolID); err != nil { return err } if c.UserPoolRegion == "" { return errors.New("Invalid config: auth.third_party.cognito is enabled but without a user_pool_region.") - } - if c.UserPoolRegion, err = maybeLoadEnv(c.UserPoolRegion); err != nil { + } else if c.UserPoolRegion, err = maybeLoadEnv(c.UserPoolRegion); err != nil { return err } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 8f1169d5e..4aa7ce243 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -205,55 +205,74 @@ func TestSigningJWT(t *testing.T) { func TestValidateHookURI(t *testing.T) { tests := []struct { - name string - uri string - hookName string - shouldErr bool - errorMsg string + hookConfig + name string + errorMsg string }{ { - name: "valid http URL", - uri: "http://example.com", - hookName: "testHook", - shouldErr: false, + name: "valid http URL", + hookConfig: hookConfig{ + Enabled: true, + URI: "http://example.com", + Secrets: "test-secret", + }, }, { - name: "valid https URL", - uri: "https://example.com", - hookName: "testHook", - shouldErr: false, + name: "valid https URL", + hookConfig: hookConfig{ + Enabled: true, + URI: "https://example.com", + Secrets: "test-secret", + }, + }, + { + name: "valid pg-functions URI", + hookConfig: hookConfig{ + Enabled: true, + URI: "pg-functions://functionName", + }, + }, + { + name: "invalid URI with unsupported scheme", + hookConfig: hookConfig{ + Enabled: true, + URI: "ftp://example.com", + Secrets: "test-secret", + }, + errorMsg: "Invalid hook config: auth.hook.invalid URI with unsupported scheme should be a HTTP, HTTPS, or pg-functions URI", }, { - name: "valid pg-functions URI", - uri: "pg-functions://functionName", - hookName: "pgHook", - shouldErr: false, + name: "invalid URI with parsing error", + hookConfig: hookConfig{ + Enabled: true, + URI: "http://a b.com", + Secrets: "test-secret", + }, + errorMsg: "failed to parse template url: parse \"http://a b.com\": invalid character \" \" in host name", }, { - name: "invalid URI with unsupported scheme", - uri: "ftp://example.com", - hookName: "malformedHook", - shouldErr: true, - errorMsg: "Invalid HTTP hook config: auth.hook.malformedHook should be a Postgres function URI, or a HTTP or HTTPS URL", + name: "valid http URL with missing secrets", + hookConfig: hookConfig{ + Enabled: true, + URI: "http://example.com", + }, + errorMsg: "Missing required field in config: auth.hook.valid http URL with missing secrets.secrets", }, { - name: "invalid URI with parsing error", - uri: "http://a b.com", - hookName: "errorHook", - shouldErr: true, - errorMsg: "failed to parse template url: parse \"http://a b.com\": invalid character \" \" in host name", + name: "valid pg-functions URI with unsupported secrets", + hookConfig: hookConfig{ + Enabled: true, + URI: "pg-functions://functionName", + Secrets: "test-secret", + }, + errorMsg: "Invalid hook config: auth.hook.valid pg-functions URI with unsupported secrets.secrets is unsupported for pg-functions URI", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - h := hookConfig{ - Enabled: true, - URI: tt.uri, - Secrets: "test-secret", - } - err := h.validate(tt.hookName) - if tt.shouldErr { + err := tt.hookConfig.validate(tt.name) + if len(tt.errorMsg) > 0 { assert.Error(t, err, "Expected an error for %v", tt.name) assert.EqualError(t, err, tt.errorMsg, "Expected error message does not match for %v", tt.name) } else { diff --git a/pkg/config/constants.go b/pkg/config/constants.go index 670450d5a..da0a9bf4a 100644 --- a/pkg/config/constants.go +++ b/pkg/config/constants.go @@ -12,11 +12,11 @@ const ( pgmetaImage = "supabase/postgres-meta:v0.84.2" studioImage = "supabase/studio:20241106-f29003e" imageProxyImage = "darthsim/imgproxy:v3.8.0" - edgeRuntimeImage = "supabase/edge-runtime:v1.62.2" + edgeRuntimeImage = "supabase/edge-runtime:v1.65.3" vectorImage = "timberio/vector:0.28.1-alpine" supavisorImage = "supabase/supavisor:1.1.56" gotrueImage = "supabase/gotrue:v2.164.0" - realtimeImage = "supabase/realtime:v2.30.34" + realtimeImage = "supabase/realtime:v2.33.58" storageImage = "supabase/storage-api:v1.11.13" logflareImage = "supabase/logflare:1.4.0" // Append to JobImages when adding new dependencies below diff --git a/pkg/config/db.go b/pkg/config/db.go index 4e22dd53f..2a1d31cad 100644 --- a/pkg/config/db.go +++ b/pkg/config/db.go @@ -112,7 +112,7 @@ func (a *settings) ToUpdatePostgresConfigBody() v1API.UpdatePostgresConfigBody { return body } -func (a *settings) fromRemoteConfig(remoteConfig v1API.PostgresConfigResponse) { +func (a *settings) FromRemotePostgresConfig(remoteConfig v1API.PostgresConfigResponse) { a.EffectiveCacheSize = remoteConfig.EffectiveCacheSize a.LogicalDecodingWorkMem = remoteConfig.LogicalDecodingWorkMem a.MaintenanceWorkMem = remoteConfig.MaintenanceWorkMem @@ -155,7 +155,7 @@ func (a *settings) DiffWithRemote(remoteConfig v1API.PostgresConfigResponse) ([] if err != nil { return nil, err } - copy.fromRemoteConfig(remoteConfig) + copy.FromRemotePostgresConfig(remoteConfig) remoteCompare, err := ToTomlBytes(copy) if err != nil { return nil, err diff --git a/pkg/config/storage.go b/pkg/config/storage.go index 3398dee42..b11b28a10 100644 --- a/pkg/config/storage.go +++ b/pkg/config/storage.go @@ -44,7 +44,7 @@ func (s *storage) ToUpdateStorageConfigBody() v1API.UpdateStorageConfigBody { return body } -func (s *storage) fromRemoteStorageConfig(remoteConfig v1API.StorageConfigResponse) { +func (s *storage) FromRemoteStorageConfig(remoteConfig v1API.StorageConfigResponse) { s.FileSizeLimit = sizeInBytes(remoteConfig.FileSizeLimit) s.ImageTransformation.Enabled = remoteConfig.Features.ImageTransformation.Enabled } @@ -56,7 +56,7 @@ func (s *storage) DiffWithRemote(remoteConfig v1API.StorageConfigResponse) ([]by if err != nil { return nil, err } - copy.fromRemoteStorageConfig(remoteConfig) + copy.FromRemoteStorageConfig(remoteConfig) remoteCompare, err := ToTomlBytes(copy) if err != nil { return nil, err diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index 43854f7d6..12decc3f4 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -1,3 +1,5 @@ +# 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 = "{{ .ProjectId }}" @@ -72,6 +74,8 @@ port = 54324 # Uncomment to expose additional ports for testing user applications that send emails. # smtp_port = 54325 # pop3_port = 54326 +# admin_email = "admin@email.com" +# sender_name = "Admin" [storage] enabled = true @@ -108,6 +112,11 @@ enable_signup = true enable_anonymous_sign_ins = false # 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.email] # Allow/disallow new user signups via email to your project. @@ -146,7 +155,7 @@ 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 }}` }} ." +template = "Your code is {{ `{{ .Code }}` }}" # Controls the minimum amount of time that must pass before sending another sms otp. max_frequency = "5s" @@ -234,8 +243,18 @@ enabled = true # Configure one of the supported request policies: `oneshot`, `per_worker`. # Use `oneshot` for hot reload, or `per_worker` for load testing. policy = "oneshot" +# Port to attach the Chrome inspector for debugging edge functions. inspector_port = 8083 +# Use these configurations to customize your Edge Function. +# [functions.MY_FUNCTION_NAME] +# enabled = true +# verify_jwt = true +# import_map = "./functions/MY_FUNCTION_NAME/deno.json" +# Uncomment to specify a custom file path to the entrypoint. +# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx +# entrypoint = "./functions/MY_FUNCTION_NAME/index.ts" + [analytics] enabled = true port = 54327 diff --git a/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff new file mode 100644 index 000000000..3db4d5462 --- /dev/null +++ b/pkg/config/testdata/TestAuthDiff/local_enabled_and_disabled.diff @@ -0,0 +1,28 @@ +diff remote[auth] local[auth] +--- remote[auth] ++++ local[auth] +@@ -1,14 +1,14 @@ + enabled = false +-site_url = "" +-additional_redirect_urls = ["https://127.0.0.1:3000", "https://ref.supabase.co"] +-jwt_expiry = 0 +-enable_refresh_token_rotation = true +-refresh_token_reuse_interval = 0 +-enable_manual_linking = true +-enable_signup = true +-enable_anonymous_sign_ins = true +-minimum_password_length = 8 +-password_requirements = "letters_digits" ++site_url = "http://127.0.0.1:3000" ++additional_redirect_urls = ["https://127.0.0.1:3000"] ++jwt_expiry = 3600 ++enable_refresh_token_rotation = false ++refresh_token_reuse_interval = 10 ++enable_manual_linking = false ++enable_signup = false ++enable_anonymous_sign_ins = false ++minimum_password_length = 6 ++password_requirements = "lower_upper_letters_digits_symbols" + + [hook] + [hook.mfa_verification_attempt] diff --git a/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff index d6c7f6dca..3123336fa 100644 --- a/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -49,13 +49,13 @@ +@@ -51,13 +51,13 @@ inactivity_timeout = "0s" [email] @@ -22,7 +22,7 @@ diff remote[auth] local[auth] [email.template] [email.template.confirmation] content_path = "" -@@ -69,13 +69,6 @@ +@@ -71,13 +71,6 @@ content_path = "" [email.template.recovery] content_path = "" diff --git a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff index 9bcf8ccba..24386ae91 100644 --- a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -49,28 +49,43 @@ +@@ -51,28 +51,43 @@ inactivity_timeout = "0s" [email] diff --git a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff index fcfb3d1d0..234e422f4 100644 --- a/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestExternalDiff/local_enabled_and_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -88,7 +88,7 @@ +@@ -91,7 +91,7 @@ [external] [external.apple] @@ -10,7 +10,7 @@ diff remote[auth] local[auth] client_id = "test-client-1,test-client-2" secret = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252" url = "" -@@ -144,7 +144,7 @@ +@@ -147,7 +147,7 @@ redirect_uri = "" skip_nonce_check = false [external.google] diff --git a/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff index b45808d4c..e3afeb491 100644 --- a/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestHookDiff/local_enabled_and_disabled.diff @@ -1,21 +1,36 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -9,7 +9,7 @@ +@@ -11,24 +11,24 @@ [hook] [hook.mfa_verification_attempt] -enabled = true +enabled = false - uri = "" + uri = "pg-functions://verifyMFA" secrets = "" [hook.password_verification_attempt] -@@ -17,7 +17,7 @@ +-enabled = true ++enabled = false uri = "" secrets = "" [hook.custom_access_token] -enabled = false +-uri = "" +-secrets = "hash:b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" +enabled = true - uri = "" - secrets = "hash:b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" ++uri = "http://example.com" ++secrets = "hash:ce62bb9bcced294fd4afe668f8ab3b50a89cf433093c526fffa3d0e46bf55252" [hook.send_sms] +-enabled = true ++enabled = false + uri = "https://example.com" + secrets = "test-secret" + [hook.send_email] +-enabled = false +-uri = "" ++enabled = true ++uri = "pg-functions://sendEmail" + secrets = "" + + [mfa] diff --git a/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff b/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff index f7935bfa4..866ce8fae 100644 --- a/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff +++ b/pkg/config/testdata/TestMfaDiff/local_enabled_and_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -30,16 +30,16 @@ +@@ -32,16 +32,16 @@ secrets = "" [mfa] diff --git a/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff b/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff index 2e44496fd..637a0206e 100644 --- a/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff +++ b/pkg/config/testdata/TestSmsDiff/enable_sign_up_without_provider.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -58,7 +58,7 @@ +@@ -60,7 +60,7 @@ otp_expiry = 0 [sms] diff --git a/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff index a173a8adf..4348c80ba 100644 --- a/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_disabled_remote_enabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -58,12 +58,12 @@ +@@ -60,12 +60,12 @@ otp_expiry = 0 [sms] @@ -19,11 +19,10 @@ diff remote[auth] local[auth] account_sid = "" message_service_sid = "" auth_token = "" -@@ -85,9 +85,6 @@ - from = "" +@@ -88,8 +88,6 @@ api_key = "" api_secret = "" --[sms.test_otp] + [sms.test_otp] -123 = "456" -456 = "123" diff --git a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff index f9ee1d7f0..e29c287ed 100644 --- a/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestSmsDiff/local_enabled_remote_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -58,12 +58,12 @@ +@@ -60,12 +60,12 @@ otp_expiry = 0 [sms] @@ -19,7 +19,7 @@ diff remote[auth] local[auth] account_sid = "" message_service_sid = "" auth_token = "" -@@ -73,9 +73,9 @@ +@@ -75,9 +75,9 @@ message_service_sid = "" auth_token = "" [sms.messagebird] @@ -32,11 +32,10 @@ diff remote[auth] local[auth] [sms.textlocal] enabled = false sender = "" -@@ -85,6 +85,8 @@ - from = "" +@@ -88,6 +88,7 @@ api_key = "" api_secret = "" -+[sms.test_otp] + [sms.test_otp] +123 = "456" [third_party] diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index 9aba86c3d..ce845743e 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -106,6 +106,11 @@ refresh_token_reuse_interval = 10 enable_signup = true # Allow/disallow testing manual linking of accounts enable_manual_linking = true +# 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.email] # Allow/disallow new user signups via email to your project. @@ -142,7 +147,7 @@ enable_signup = true # 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 }}` }} ." +template = "Your code is {{ `{{ .Code }}` }}" # Controls the minimum amount of time that must pass before sending another sms otp. max_frequency = "5s" diff --git a/pkg/config/updater_test.go b/pkg/config/updater_test.go index 3e549e055..471e84963 100644 --- a/pkg/config/updater_test.go +++ b/pkg/config/updater_test.go @@ -193,6 +193,7 @@ func TestUpdateAuthConfig(t *testing.T) { Enabled: true, EnableSignup: true, Email: email{EnableConfirmations: true}, + Sms: sms{TestOTP: map[string]string{}}, }) // Check result assert.NoError(t, err) diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 2dd9c1aae..9a46d94a9 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -103,9 +103,6 @@ func mapToEnv(input map[string]string) string { func envToMap(input string) map[string]string { env := strToArr(input) - if len(env) == 0 { - return nil - } result := make(map[string]string, len(env)) for _, kv := range env { if parts := strings.Split(kv, "="); len(parts) > 1 {