diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 25b00f97ac..e58bd32eb0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @supabase/dev-workflows +* @supabase/cli diff --git a/.github/workflows/api-sync.yml b/.github/workflows/api-sync.yml index aeb0e8505d..bf0b2568d3 100644 --- a/.github/workflows/api-sync.yml +++ b/.github/workflows/api-sync.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 1a43c8c7b4..67609662d1 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -18,7 +18,7 @@ jobs: # will not occur. - name: Dependabot metadata id: meta - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 + uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edd915571d..d32a2c6e3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: CI on: pull_request: + merge_group: push: branches: - develop @@ -16,7 +17,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -53,7 +54,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod # Linter requires no cache @@ -70,7 +71,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -89,11 +90,11 @@ jobs: link: name: Link - if: ${{ !github.event.pull_request.head.repo.fork }} + if: ${{ github.event_name == 'merge_group' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) }} runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -109,7 +110,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2929f82355..2421a2a7fe 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,6 +13,7 @@ name: "CodeQL" on: pull_request: + merge_group: push: branches: - develop @@ -60,7 +61,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -88,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/mirror-image.yml b/.github/workflows/mirror-image.yml index 8029e04613..481d9b903e 100644 --- a/.github/workflows/mirror-image.yml +++ b/.github/workflows/mirror-image.yml @@ -34,10 +34,10 @@ jobs: with: role-to-assume: ${{ secrets.PROD_AWS_ROLE }} aws-region: us-east-1 - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: public.ecr.aws - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 19840d7a05..df93dac907 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -27,7 +27,7 @@ jobs: curr: ${{ steps.curr.outputs.tags }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/pg-prove.yml b/.github/workflows/pg-prove.yml index 74eb258e1c..e9cbcd3461 100644 --- a/.github/workflows/pg-prove.yml +++ b/.github/workflows/pg-prove.yml @@ -46,7 +46,7 @@ jobs: - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 with: endpoint: builders - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/publish-migra.yml b/.github/workflows/publish-migra.yml index dd7627b999..98d69264ad 100644 --- a/.github/workflows/publish-migra.yml +++ b/.github/workflows/publish-migra.yml @@ -46,7 +46,7 @@ jobs: - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 with: endpoint: builders - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 7bb2afd0d2..60777958b3 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -39,7 +39,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63f813e7f2..6782bbb443 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true @@ -116,7 +116,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/tag-pkg.yml b/.github/workflows/tag-pkg.yml new file mode 100644 index 0000000000..8eaf266109 --- /dev/null +++ b/.github/workflows/tag-pkg.yml @@ -0,0 +1,37 @@ +name: Tag pkg + +on: + workflow_dispatch: + inputs: + version: + description: "pkg version to tag (e.g. v1.2.2)" + required: true + type: string + +permissions: + contents: write + +jobs: + tag: + name: Create pkg tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: develop + fetch-depth: 0 + + - name: Create and push pkg tag + run: | + VERSION="${{ inputs.version }}" + if ! [[ "$VERSION" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "Error: version '$VERSION' does not match semver format (e.g. v1.2.2)" + exit 1 + fi + TAG="pkg/$VERSION" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Error: tag '$TAG' already exists" + exit 1 + fi + git tag "$TAG" + git push origin "$TAG" diff --git a/go.mod b/go.mod index 267f334caf..5aba7d4d1b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/BurntSushi/toml v1.6.0 github.com/Netflix/go-env v0.1.2 - github.com/andybalholm/brotli v1.2.0 + github.com/andybalholm/brotli v1.2.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 @@ -22,8 +22,8 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/getsentry/sentry-go v0.44.1 github.com/go-errors/errors v1.5.1 - github.com/go-git/go-git/v5 v5.17.0 - github.com/go-playground/validator/v10 v10.30.1 + github.com/go-git/go-git/v5 v5.17.2 + github.com/go-playground/validator/v10 v10.30.2 github.com/go-viper/mapstructure/v2 v2.5.0 github.com/go-xmlfmt/xmlfmt v1.1.3 github.com/golang-jwt/jwt/v5 v5.3.1 @@ -42,7 +42,7 @@ require ( github.com/multigres/multigres v0.0.0-20260126223308-f5a52171bbc4 github.com/oapi-codegen/nullable v1.1.0 github.com/olekukonko/tablewriter v1.1.4 - github.com/slack-go/slack v0.20.0 + github.com/slack-go/slack v0.21.0 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 @@ -53,12 +53,12 @@ require ( github.com/tidwall/jsonc v0.3.3 github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 github.com/zalando/go-keyring v0.2.8 - go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel v1.43.0 golang.org/x/mod v0.34.0 golang.org/x/net v0.52.0 golang.org/x/oauth2 v0.36.0 golang.org/x/term v0.41.0 - google.golang.org/grpc v1.79.3 + google.golang.org/grpc v1.80.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -175,7 +175,7 @@ require ( github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/getkin/kin-openapi v0.131.0 // indirect github.com/ghostiam/protogetter v0.3.15 // indirect github.com/go-critic/go-critic v0.13.0 // indirect @@ -317,7 +317,7 @@ require ( github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.19.1 // indirect github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect - github.com/oapi-codegen/runtime v1.3.0 // indirect + github.com/oapi-codegen/runtime v1.3.1 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect @@ -415,10 +415,10 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -434,8 +434,8 @@ require ( golang.org/x/tools v0.42.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index cd384a30e4..5fb812d205 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEW github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= @@ -339,8 +339,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/getsentry/sentry-go v0.44.1 h1:/cPtrA5qB7uMRrhgSn9TYtcEF36auGP3Y6+ThvD/yaI= @@ -359,8 +359,8 @@ github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDz github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM= -github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI= +github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= +github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -385,8 +385,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= -github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= +github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -830,8 +830,8 @@ github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/ github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= -github.com/oapi-codegen/runtime v1.3.0 h1:vyK1zc0gDWWXgk2xoQa4+X4RNNc5SL2RbTpJS/4vMYA= -github.com/oapi-codegen/runtime v1.3.0/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= +github.com/oapi-codegen/runtime v1.3.1 h1:RgDY6J4OGQLbRXhG/Xpt3vSVqYpHQS7hN4m85+5xB9g= +github.com/oapi-codegen/runtime v1.3.1/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= @@ -1000,8 +1000,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/slack-go/slack v0.20.0 h1:gbDdbee8+Z2o+DWx05Spq3GzbrLLleiRwHUKs+hZLSU= -github.com/slack-go/slack v0.20.0/go.mod h1:K81UmCivcYd/5Jmz8vLBfuyoZ3B4rQC2GHVXHteXiAE= +github.com/slack-go/slack v0.21.0 h1:TAGnZYFp79LAG/oqFzYhFJ9LwEwXJ93heCkPvwjxc7o= +github.com/slack-go/slack v0.21.0/go.mod h1:K81UmCivcYd/5Jmz8vLBfuyoZ3B4rQC2GHVXHteXiAE= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= @@ -1162,8 +1162,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0. go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= @@ -1174,14 +1174,14 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1409,15 +1409,15 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E= -google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/internal/start/start.go b/internal/start/start.go index a48cc0f51c..5d98941945 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -378,8 +378,10 @@ EOF case "unix": if dindHost, err = client.ParseHostURL(client.DefaultDockerHost); err != nil { return errors.Errorf("failed to parse default host: %w", err) - } else if strings.HasSuffix(parsed.Host, "/.docker/run/docker.sock") { - fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "analytics requires mounting default docker socket:", dindHost.Host) + } else if strings.HasSuffix(parsed.Host, "/.docker/run/docker.sock") || + strings.HasSuffix(parsed.Host, "/.docker/desktop/docker.sock") { + // Docker will not mount rootless socket directly; + // instead, specify root socket to have it handled under the hood binds = append(binds, fmt.Sprintf("%[1]s:%[1]s:ro", dindHost.Host)) } else { // Podman and OrbStack can mount root-less socket without issue @@ -470,12 +472,12 @@ vector --config /etc/vector/vector.yaml } binds := []string{} - for id, tmpl := range utils.Config.Auth.Email.Template { - if len(tmpl.ContentPath) == 0 { - continue + mountEmailTemplates := func(id, contentPath string) error { + if len(contentPath) == 0 { + return nil } - hostPath := tmpl.ContentPath - if !filepath.IsAbs(tmpl.ContentPath) { + hostPath := contentPath + if !filepath.IsAbs(contentPath) { var err error hostPath, err = filepath.Abs(hostPath) if err != nil { @@ -484,6 +486,23 @@ vector --config /etc/vector/vector.yaml } dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath)) binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath)) + return nil + } + + for id, tmpl := range utils.Config.Auth.Email.Template { + err := mountEmailTemplates(id, tmpl.ContentPath) + if err != nil { + return err + } + } + + for id, tmpl := range utils.Config.Auth.Email.Notification { + if tmpl.Enabled { + err := mountEmailTemplates(id+"_notification", tmpl.ContentPath) + if err != nil { + return err + } + } } dockerPort := uint16(8000) @@ -661,23 +680,34 @@ EOF env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout)) } - for id, tmpl := range utils.Config.Auth.Email.Template { - if len(tmpl.ContentPath) > 0 { + addMailerEnvVars := func(id, contentPath string, subject *string) { + if len(contentPath) > 0 { env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s", strings.ToUpper(id), utils.KongId, nginxTemplateServerPort, - id+filepath.Ext(tmpl.ContentPath), + id+filepath.Ext(contentPath), )) } - if tmpl.Subject != nil { + if subject != nil { env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s", strings.ToUpper(id), - *tmpl.Subject, + *subject, )) } } + for id, tmpl := range utils.Config.Auth.Email.Template { + addMailerEnvVars(id, tmpl.ContentPath, tmpl.Subject) + } + + for id, tmpl := range utils.Config.Auth.Email.Notification { + if tmpl.Enabled { + env = append(env, fmt.Sprintf("GOTRUE_MAILER_NOTIFICATIONS_%s_ENABLED=true", strings.ToUpper(id))) + addMailerEnvVars(id+"_notification", tmpl.ContentPath, tmpl.Subject) + } + } + switch { case utils.Config.Auth.Sms.Twilio.Enabled: env = append( diff --git a/internal/utils/connect.go b/internal/utils/connect.go index eca6b410aa..b9e2d39df9 100644 --- a/internal/utils/connect.go +++ b/internal/utils/connect.go @@ -167,23 +167,35 @@ func ConnectByUrl(ctx context.Context, url string, options ...func(*pgx.ConnConf cc.Fallbacks = fallbacks }) conn, err := pgxv5.Connect(ctx, url, options...) - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { - if strings.Contains(pgErr.Message, "connect: connection refused") { - CmdSuggestion = fmt.Sprintf("Make sure your local IP is allowed in Network Restrictions and Network Bans.\n%s/project/_/database/settings", CurrentProfile.DashboardURL) - } else if strings.Contains(pgErr.Message, "SSL connection is required") && viper.GetBool("DEBUG") { - CmdSuggestion = "SSL connection is not supported with --debug flag" - } else if strings.Contains(pgErr.Message, "SCRAM exchange: Wrong password") || strings.Contains(pgErr.Message, "failed SASL auth") { - // password authentication failed for user / invalid SCRAM server-final-message received from server - CmdSuggestion = "Try setting the SUPABASE_DB_PASSWORD environment variable" - } else if strings.Contains(pgErr.Message, "connect: no route to host") || strings.Contains(pgErr.Message, "Tenant or user not found") { - // Assumes IPv6 check has been performed before this - CmdSuggestion = "Make sure your project exists on profile: " + CurrentProfile.Name - } - } + SetConnectSuggestion(err) return conn, err } +const SuggestEnvVar = "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD" + +// Sets CmdSuggestion to an actionable hint based on the given pg connection error. +func SetConnectSuggestion(err error) { + if err == nil { + return + } + msg := err.Error() + if strings.Contains(msg, "connect: connection refused") || + strings.Contains(msg, "Address not in tenant allow_list") { + CmdSuggestion = fmt.Sprintf( + "Make sure your local IP is allowed in Network Restrictions and Network Bans.\n%s/project/_/database/settings", + CurrentProfile.DashboardURL, + ) + } else if strings.Contains(msg, "SSL connection is required") && viper.GetBool("DEBUG") { + CmdSuggestion = "SSL connection is not supported with --debug flag" + } else if strings.Contains(msg, "SCRAM exchange: Wrong password") || strings.Contains(msg, "failed SASL auth") { + // password authentication failed for user / invalid SCRAM server-final-message received from server + CmdSuggestion = SuggestEnvVar + } else if strings.Contains(msg, "connect: no route to host") || strings.Contains(msg, "Tenant or user not found") { + // Assumes IPv6 check has been performed before this + CmdSuggestion = "Make sure your project exists on profile: " + CurrentProfile.Name + } +} + const ( SUPERUSER_ROLE = "supabase_admin" CLI_LOGIN_PREFIX = "cli_login_" diff --git a/internal/utils/connect_test.go b/internal/utils/connect_test.go index 140b0c92e8..55a81c0ca1 100644 --- a/internal/utils/connect_test.go +++ b/internal/utils/connect_test.go @@ -168,6 +168,83 @@ func TestPoolerConfig(t *testing.T) { }) } +func TestSetConnectSuggestion(t *testing.T) { + oldProfile := CurrentProfile + CurrentProfile = allProfiles[0] + defer t.Cleanup(func() { CurrentProfile = oldProfile }) + + cases := []struct { + name string + err error + suggestion string + debug bool + }{ + { + name: "no-op on nil error", + err: nil, + suggestion: "", + }, + { + name: "no-op on unrecognised error", + err: errors.New("some unknown error"), + suggestion: "", + }, + { + name: "connection refused", + err: errors.New("connect: connection refused"), + suggestion: "Make sure your local IP is allowed in Network Restrictions and Network Bans", + }, + { + name: "address not in allow list", + err: errors.New("server error (FATAL: Address not in tenant allow_list: {1,2,3} (SQLSTATE XX000))"), + suggestion: "Make sure your local IP is allowed in Network Restrictions and Network Bans", + }, + { + name: "ssl required without debug flag", + err: errors.New("SSL connection is required"), + suggestion: "", + }, + { + name: "ssl required with debug flag", + err: errors.New("SSL connection is required"), + debug: true, + suggestion: "SSL connection is not supported with --debug flag", + }, + { + name: "wrong password via SCRAM", + err: errors.New("SCRAM exchange: Wrong password"), + suggestion: "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD", + }, + { + name: "failed SASL auth", + err: errors.New("failed SASL auth"), + suggestion: "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD", + }, + { + name: "no route to host", + err: errors.New("connect: no route to host"), + suggestion: "Make sure your project exists on profile: " + CurrentProfile.Name, + }, + { + name: "tenant or user not found", + err: errors.New("Tenant or user not found"), + suggestion: "Make sure your project exists on profile: " + CurrentProfile.Name, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + CmdSuggestion = "" + viper.Set("DEBUG", tc.debug) + SetConnectSuggestion(tc.err) + if tc.suggestion == "" { + assert.Empty(t, CmdSuggestion) + } else { + assert.Contains(t, CmdSuggestion, tc.suggestion) + } + }) + } +} + func TestPostgresURL(t *testing.T) { url := ToPostgresURL(pgconn.Config{ Host: "2406:da18:4fd:9b0d:80ec:9812:3e65:450b", diff --git a/internal/utils/flags/db_url.go b/internal/utils/flags/db_url.go index 3ec94073b4..b3fda6a007 100644 --- a/internal/utils/flags/db_url.go +++ b/internal/utils/flags/db_url.go @@ -120,8 +120,6 @@ func RandomString(size int) (string, error) { return string(data), nil } -const suggestEnvVar = "Connect to your database by setting the env var: SUPABASE_DB_PASSWORD" - func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Config, error) { config := pgconn.Config{ Host: utils.GetSupabaseDbHost(projectRef), @@ -144,7 +142,10 @@ func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Con fmt.Fprintln(logger, "Using database password from env var...") poolerConfig.Password = config.Password } else if err := initPoolerLogin(ctx, projectRef, poolerConfig); err != nil { - utils.CmdSuggestion = suggestEnvVar + utils.SetConnectSuggestion(err) + if utils.CmdSuggestion == "" { + utils.CmdSuggestion = utils.SuggestEnvVar + } return *poolerConfig, err } return *poolerConfig, nil @@ -157,7 +158,7 @@ func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Con fmt.Fprintln(logger, "Using database password from env var...") } else if err := initLoginRole(ctx, projectRef, &config); err != nil { // Do not prompt because reading masked input is buggy on windows - utils.CmdSuggestion = suggestEnvVar + utils.CmdSuggestion = utils.SuggestEnvVar return config, err } return config, nil diff --git a/package.json b/package.json index 7bf1c01ad9..23afccb1db 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "bin-links": "^6.0.0", - "https-proxy-agent": "^8.0.0", + "https-proxy-agent": "^9.0.0", "node-fetch": "^3.3.2", "tar": "7.5.13" }, diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index 1c7d29e1dc..5d7941a128 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -149,6 +149,9 @@ type ClientInterface interface { // V1GetAnOrganization request V1GetAnOrganization(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetOrganizationEntitlements request + V1GetOrganizationEntitlements(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1ListOrganizationMembers request V1ListOrganizationMembers(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -161,6 +164,9 @@ type ClientInterface interface { // V1GetAllProjectsForOrganization request V1GetAllProjectsForOrganization(ctx context.Context, slug string, params *V1GetAllProjectsForOrganizationParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetProfile request + V1GetProfile(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1ListAllProjects request V1ListAllProjects(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -944,6 +950,18 @@ func (c *Client) V1GetAnOrganization(ctx context.Context, slug string, reqEditor return c.Client.Do(req) } +func (c *Client) V1GetOrganizationEntitlements(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetOrganizationEntitlementsRequest(c.Server, slug) + 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) V1ListOrganizationMembers(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1ListOrganizationMembersRequest(c.Server, slug) if err != nil { @@ -992,6 +1010,18 @@ func (c *Client) V1GetAllProjectsForOrganization(ctx context.Context, slug strin return c.Client.Do(req) } +func (c *Client) V1GetProfile(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetProfileRequest(c.Server) + 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) V1ListAllProjects(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1ListAllProjectsRequest(c.Server) if err != nil { @@ -4141,6 +4171,40 @@ func NewV1GetAnOrganizationRequest(server string, slug string) (*http.Request, e return req, nil } +// NewV1GetOrganizationEntitlementsRequest generates requests for V1GetOrganizationEntitlements +func NewV1GetOrganizationEntitlementsRequest(server string, slug string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "slug", runtime.ParamLocationPath, slug) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/organizations/%s/entitlements", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewV1ListOrganizationMembersRequest generates requests for V1ListOrganizationMembers func NewV1ListOrganizationMembersRequest(server string, slug string) (*http.Request, error) { var err error @@ -4377,6 +4441,33 @@ func NewV1GetAllProjectsForOrganizationRequest(server string, slug string, param return req, nil } +// NewV1GetProfileRequest generates requests for V1GetProfile +func NewV1GetProfileRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/profile") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewV1ListAllProjectsRequest generates requests for V1ListAllProjects func NewV1ListAllProjectsRequest(server string) (*http.Request, error) { var err error @@ -10906,6 +10997,9 @@ type ClientWithResponsesInterface interface { // V1GetAnOrganizationWithResponse request V1GetAnOrganizationWithResponse(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*V1GetAnOrganizationResponse, error) + // V1GetOrganizationEntitlementsWithResponse request + V1GetOrganizationEntitlementsWithResponse(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*V1GetOrganizationEntitlementsResponse, error) + // V1ListOrganizationMembersWithResponse request V1ListOrganizationMembersWithResponse(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*V1ListOrganizationMembersResponse, error) @@ -10918,6 +11012,9 @@ type ClientWithResponsesInterface interface { // V1GetAllProjectsForOrganizationWithResponse request V1GetAllProjectsForOrganizationWithResponse(ctx context.Context, slug string, params *V1GetAllProjectsForOrganizationParams, reqEditors ...RequestEditorFn) (*V1GetAllProjectsForOrganizationResponse, error) + // V1GetProfileWithResponse request + V1GetProfileWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*V1GetProfileResponse, error) + // V1ListAllProjectsWithResponse request V1ListAllProjectsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*V1ListAllProjectsResponse, error) @@ -11763,6 +11860,28 @@ func (r V1GetAnOrganizationResponse) StatusCode() int { return 0 } +type V1GetOrganizationEntitlementsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *V1ListEntitlementsResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetOrganizationEntitlementsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetOrganizationEntitlementsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1ListOrganizationMembersResponse struct { Body []byte HTTPResponse *http.Response @@ -11850,6 +11969,28 @@ func (r V1GetAllProjectsForOrganizationResponse) StatusCode() int { return 0 } +type V1GetProfileResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *V1ProfileResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetProfileResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetProfileResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1ListAllProjectsResponse struct { Body []byte HTTPResponse *http.Response @@ -15089,6 +15230,15 @@ func (c *ClientWithResponses) V1GetAnOrganizationWithResponse(ctx context.Contex return ParseV1GetAnOrganizationResponse(rsp) } +// V1GetOrganizationEntitlementsWithResponse request returning *V1GetOrganizationEntitlementsResponse +func (c *ClientWithResponses) V1GetOrganizationEntitlementsWithResponse(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*V1GetOrganizationEntitlementsResponse, error) { + rsp, err := c.V1GetOrganizationEntitlements(ctx, slug, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetOrganizationEntitlementsResponse(rsp) +} + // V1ListOrganizationMembersWithResponse request returning *V1ListOrganizationMembersResponse func (c *ClientWithResponses) V1ListOrganizationMembersWithResponse(ctx context.Context, slug string, reqEditors ...RequestEditorFn) (*V1ListOrganizationMembersResponse, error) { rsp, err := c.V1ListOrganizationMembers(ctx, slug, reqEditors...) @@ -15125,6 +15275,15 @@ func (c *ClientWithResponses) V1GetAllProjectsForOrganizationWithResponse(ctx co return ParseV1GetAllProjectsForOrganizationResponse(rsp) } +// V1GetProfileWithResponse request returning *V1GetProfileResponse +func (c *ClientWithResponses) V1GetProfileWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*V1GetProfileResponse, error) { + rsp, err := c.V1GetProfile(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetProfileResponse(rsp) +} + // V1ListAllProjectsWithResponse request returning *V1ListAllProjectsResponse func (c *ClientWithResponses) V1ListAllProjectsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*V1ListAllProjectsResponse, error) { rsp, err := c.V1ListAllProjects(ctx, reqEditors...) @@ -17121,6 +17280,32 @@ func ParseV1GetAnOrganizationResponse(rsp *http.Response) (*V1GetAnOrganizationR return response, nil } +// ParseV1GetOrganizationEntitlementsResponse parses an HTTP response from a V1GetOrganizationEntitlementsWithResponse call +func ParseV1GetOrganizationEntitlementsResponse(rsp *http.Response) (*V1GetOrganizationEntitlementsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetOrganizationEntitlementsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest V1ListEntitlementsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1ListOrganizationMembersResponse parses an HTTP response from a V1ListOrganizationMembersWithResponse call func ParseV1ListOrganizationMembersResponse(rsp *http.Response) (*V1ListOrganizationMembersResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -17215,6 +17400,32 @@ func ParseV1GetAllProjectsForOrganizationResponse(rsp *http.Response) (*V1GetAll return response, nil } +// ParseV1GetProfileResponse parses an HTTP response from a V1GetProfileWithResponse call +func ParseV1GetProfileResponse(rsp *http.Response) (*V1GetProfileResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetProfileResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest V1ProfileResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1ListAllProjectsResponse parses an HTTP response from a V1ListAllProjectsWithResponse call func ParseV1ListAllProjectsResponse(rsp *http.Response) (*V1ListAllProjectsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 5b3dd02903..eb77eefff1 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -598,13 +598,13 @@ const ( // Defines values for ListProjectAddonsResponseSelectedAddonsType. const ( - AuthMfaPhone ListProjectAddonsResponseSelectedAddonsType = "auth_mfa_phone" - AuthMfaWebAuthn ListProjectAddonsResponseSelectedAddonsType = "auth_mfa_web_authn" - ComputeInstance ListProjectAddonsResponseSelectedAddonsType = "compute_instance" - CustomDomain ListProjectAddonsResponseSelectedAddonsType = "custom_domain" - Ipv4 ListProjectAddonsResponseSelectedAddonsType = "ipv4" - LogDrain ListProjectAddonsResponseSelectedAddonsType = "log_drain" - Pitr ListProjectAddonsResponseSelectedAddonsType = "pitr" + ListProjectAddonsResponseSelectedAddonsTypeAuthMfaPhone ListProjectAddonsResponseSelectedAddonsType = "auth_mfa_phone" + ListProjectAddonsResponseSelectedAddonsTypeAuthMfaWebAuthn ListProjectAddonsResponseSelectedAddonsType = "auth_mfa_web_authn" + ListProjectAddonsResponseSelectedAddonsTypeComputeInstance ListProjectAddonsResponseSelectedAddonsType = "compute_instance" + ListProjectAddonsResponseSelectedAddonsTypeCustomDomain ListProjectAddonsResponseSelectedAddonsType = "custom_domain" + ListProjectAddonsResponseSelectedAddonsTypeIpv4 ListProjectAddonsResponseSelectedAddonsType = "ipv4" + ListProjectAddonsResponseSelectedAddonsTypeLogDrain ListProjectAddonsResponseSelectedAddonsType = "log_drain" + ListProjectAddonsResponseSelectedAddonsTypePitr ListProjectAddonsResponseSelectedAddonsType = "pitr" ) // Defines values for ListProjectAddonsResponseSelectedAddonsVariantId0. @@ -1370,6 +1370,80 @@ const ( SmartGroup V1CreateProjectBodyRegionSelection1Type = "smartGroup" ) +// Defines values for V1ListEntitlementsResponseEntitlementsFeatureKey. +const ( + V1ListEntitlementsResponseEntitlementsFeatureKeyAssistantAdvanceModel V1ListEntitlementsResponseEntitlementsFeatureKey = "assistant.advance_model" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthAdvancedAuthSettings V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.advanced_auth_settings" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthCustomJwtTemplate V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.custom_jwt_template" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthHooks V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.hooks" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthLeakedPasswordProtection V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.leaked_password_protection" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthMfaEnhancedSecurity V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.mfa_enhanced_security" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthMfaPhone V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.mfa_phone" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthMfaWebAuthn V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.mfa_web_authn" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthPasswordHibp V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.password_hibp" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthPerformanceSettings V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.performance_settings" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthPlatformSso V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.platform.sso" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthSaml2 V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.saml_2" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthUserSessions V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.user_sessions" + V1ListEntitlementsResponseEntitlementsFeatureKeyBackupRestoreToNewProject V1ListEntitlementsResponseEntitlementsFeatureKey = "backup.restore_to_new_project" + V1ListEntitlementsResponseEntitlementsFeatureKeyBackupRetentionDays V1ListEntitlementsResponseEntitlementsFeatureKey = "backup.retention_days" + V1ListEntitlementsResponseEntitlementsFeatureKeyBranchingLimit V1ListEntitlementsResponseEntitlementsFeatureKey = "branching_limit" + V1ListEntitlementsResponseEntitlementsFeatureKeyBranchingPersistent V1ListEntitlementsResponseEntitlementsFeatureKey = "branching_persistent" + V1ListEntitlementsResponseEntitlementsFeatureKeyCustomDomain V1ListEntitlementsResponseEntitlementsFeatureKey = "custom_domain" + V1ListEntitlementsResponseEntitlementsFeatureKeyDedicatedPooler V1ListEntitlementsResponseEntitlementsFeatureKey = "dedicated_pooler" + V1ListEntitlementsResponseEntitlementsFeatureKeyFunctionMaxCount V1ListEntitlementsResponseEntitlementsFeatureKey = "function.max_count" + V1ListEntitlementsResponseEntitlementsFeatureKeyFunctionSizeLimitMb V1ListEntitlementsResponseEntitlementsFeatureKey = "function.size_limit_mb" + V1ListEntitlementsResponseEntitlementsFeatureKeyInstancesComputeUpdateAvailableSizes V1ListEntitlementsResponseEntitlementsFeatureKey = "instances.compute_update_available_sizes" + V1ListEntitlementsResponseEntitlementsFeatureKeyInstancesDiskModifications V1ListEntitlementsResponseEntitlementsFeatureKey = "instances.disk_modifications" + V1ListEntitlementsResponseEntitlementsFeatureKeyInstancesHighAvailability V1ListEntitlementsResponseEntitlementsFeatureKey = "instances.high_availability" + V1ListEntitlementsResponseEntitlementsFeatureKeyInstancesOrioledb V1ListEntitlementsResponseEntitlementsFeatureKey = "instances.orioledb" + V1ListEntitlementsResponseEntitlementsFeatureKeyInstancesReadReplicas V1ListEntitlementsResponseEntitlementsFeatureKey = "instances.read_replicas" + V1ListEntitlementsResponseEntitlementsFeatureKeyIntegrationsGithubConnections V1ListEntitlementsResponseEntitlementsFeatureKey = "integrations.github_connections" + V1ListEntitlementsResponseEntitlementsFeatureKeyIpv4 V1ListEntitlementsResponseEntitlementsFeatureKey = "ipv4" + V1ListEntitlementsResponseEntitlementsFeatureKeyLogDrains V1ListEntitlementsResponseEntitlementsFeatureKey = "log_drains" + V1ListEntitlementsResponseEntitlementsFeatureKeyLogRetentionDays V1ListEntitlementsResponseEntitlementsFeatureKey = "log.retention_days" + V1ListEntitlementsResponseEntitlementsFeatureKeyObservabilityDashboardAdvancedMetrics V1ListEntitlementsResponseEntitlementsFeatureKey = "observability.dashboard_advanced_metrics" + V1ListEntitlementsResponseEntitlementsFeatureKeyPitrAvailableVariants V1ListEntitlementsResponseEntitlementsFeatureKey = "pitr.available_variants" + V1ListEntitlementsResponseEntitlementsFeatureKeyProjectCloning V1ListEntitlementsResponseEntitlementsFeatureKey = "project_cloning" + V1ListEntitlementsResponseEntitlementsFeatureKeyProjectPausing V1ListEntitlementsResponseEntitlementsFeatureKey = "project_pausing" + V1ListEntitlementsResponseEntitlementsFeatureKeyProjectRestoreAfterExpiry V1ListEntitlementsResponseEntitlementsFeatureKey = "project_restore_after_expiry" + V1ListEntitlementsResponseEntitlementsFeatureKeyProjectScopedRoles V1ListEntitlementsResponseEntitlementsFeatureKey = "project_scoped_roles" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxBytesPerSecond V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_bytes_per_second" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxChannelsPerClient V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_channels_per_client" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxConcurrentUsers V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_concurrent_users" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxEventsPerSecond V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_events_per_second" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxJoinsPerSecond V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_joins_per_second" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxPayloadSizeInKb V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_payload_size_in_kb" + V1ListEntitlementsResponseEntitlementsFeatureKeyRealtimeMaxPresenceEventsPerSecond V1ListEntitlementsResponseEntitlementsFeatureKey = "realtime.max_presence_events_per_second" + V1ListEntitlementsResponseEntitlementsFeatureKeyReplicationEtl V1ListEntitlementsResponseEntitlementsFeatureKey = "replication.etl" + V1ListEntitlementsResponseEntitlementsFeatureKeySecurityAuditLogsDays V1ListEntitlementsResponseEntitlementsFeatureKey = "security.audit_logs_days" + V1ListEntitlementsResponseEntitlementsFeatureKeySecurityEnforceMfa V1ListEntitlementsResponseEntitlementsFeatureKey = "security.enforce_mfa" + V1ListEntitlementsResponseEntitlementsFeatureKeySecurityMemberRoles V1ListEntitlementsResponseEntitlementsFeatureKey = "security.member_roles" + V1ListEntitlementsResponseEntitlementsFeatureKeySecurityPrivateLink V1ListEntitlementsResponseEntitlementsFeatureKey = "security.private_link" + V1ListEntitlementsResponseEntitlementsFeatureKeySecurityQuestionnaire V1ListEntitlementsResponseEntitlementsFeatureKey = "security.questionnaire" + V1ListEntitlementsResponseEntitlementsFeatureKeySecuritySoc2Report V1ListEntitlementsResponseEntitlementsFeatureKey = "security.soc2_report" + V1ListEntitlementsResponseEntitlementsFeatureKeyStorageIcebergCatalog V1ListEntitlementsResponseEntitlementsFeatureKey = "storage.iceberg_catalog" + V1ListEntitlementsResponseEntitlementsFeatureKeyStorageImageTransformations V1ListEntitlementsResponseEntitlementsFeatureKey = "storage.image_transformations" + V1ListEntitlementsResponseEntitlementsFeatureKeyStorageMaxFileSize V1ListEntitlementsResponseEntitlementsFeatureKey = "storage.max_file_size" + V1ListEntitlementsResponseEntitlementsFeatureKeyStorageMaxFileSizeConfigurable V1ListEntitlementsResponseEntitlementsFeatureKey = "storage.max_file_size.configurable" + V1ListEntitlementsResponseEntitlementsFeatureKeyStorageVectorBuckets V1ListEntitlementsResponseEntitlementsFeatureKey = "storage.vector_buckets" + V1ListEntitlementsResponseEntitlementsFeatureKeyVanitySubdomain V1ListEntitlementsResponseEntitlementsFeatureKey = "vanity_subdomain" +) + +// Defines values for V1ListEntitlementsResponseEntitlementsFeatureType. +const ( + V1ListEntitlementsResponseEntitlementsFeatureTypeBoolean V1ListEntitlementsResponseEntitlementsFeatureType = "boolean" + V1ListEntitlementsResponseEntitlementsFeatureTypeNumeric V1ListEntitlementsResponseEntitlementsFeatureType = "numeric" + V1ListEntitlementsResponseEntitlementsFeatureTypeSet V1ListEntitlementsResponseEntitlementsFeatureType = "set" +) + +// Defines values for V1ListEntitlementsResponseEntitlementsType. +const ( + V1ListEntitlementsResponseEntitlementsTypeBoolean V1ListEntitlementsResponseEntitlementsType = "boolean" + V1ListEntitlementsResponseEntitlementsTypeNumeric V1ListEntitlementsResponseEntitlementsType = "numeric" + V1ListEntitlementsResponseEntitlementsTypeSet V1ListEntitlementsResponseEntitlementsType = "set" +) + // Defines values for V1OrganizationSlugResponseAllowedReleaseChannels. const ( V1OrganizationSlugResponseAllowedReleaseChannelsAlpha V1OrganizationSlugResponseAllowedReleaseChannels = "alpha" @@ -1976,6 +2050,7 @@ type AuthConfigResponse struct { OauthServerAllowDynamicRegistration bool `json:"oauth_server_allow_dynamic_registration"` OauthServerAuthorizationPath nullable.Nullable[string] `json:"oauth_server_authorization_path"` OauthServerEnabled bool `json:"oauth_server_enabled"` + PasskeyEnabled bool `json:"passkey_enabled"` PasswordHibpEnabled nullable.Nullable[bool] `json:"password_hibp_enabled"` PasswordMinLength nullable.Nullable[int] `json:"password_min_length"` PasswordRequiredCharacters nullable.Nullable[AuthConfigResponsePasswordRequiredCharacters] `json:"password_required_characters"` @@ -1997,10 +2072,10 @@ type AuthConfigResponse struct { SecurityRefreshTokenReuseInterval nullable.Nullable[int] `json:"security_refresh_token_reuse_interval"` SecuritySbForwardedForEnabled nullable.Nullable[bool] `json:"security_sb_forwarded_for_enabled"` SecurityUpdatePasswordRequireReauthentication nullable.Nullable[bool] `json:"security_update_password_require_reauthentication"` - SessionsInactivityTimeout nullable.Nullable[int] `json:"sessions_inactivity_timeout"` + SessionsInactivityTimeout nullable.Nullable[float32] `json:"sessions_inactivity_timeout"` SessionsSinglePerUser nullable.Nullable[bool] `json:"sessions_single_per_user"` SessionsTags nullable.Nullable[string] `json:"sessions_tags"` - SessionsTimebox nullable.Nullable[int] `json:"sessions_timebox"` + SessionsTimebox nullable.Nullable[float32] `json:"sessions_timebox"` SiteUrl nullable.Nullable[string] `json:"site_url"` SmsAutoconfirm nullable.Nullable[bool] `json:"sms_autoconfirm"` SmsMaxFrequency nullable.Nullable[int] `json:"sms_max_frequency"` @@ -2032,6 +2107,9 @@ type AuthConfigResponse struct { SmtpSenderName nullable.Nullable[string] `json:"smtp_sender_name"` SmtpUser nullable.Nullable[string] `json:"smtp_user"` UriAllowList nullable.Nullable[string] `json:"uri_allow_list"` + WebauthnRpDisplayName nullable.Nullable[string] `json:"webauthn_rp_display_name"` + WebauthnRpId nullable.Nullable[string] `json:"webauthn_rp_id"` + WebauthnRpOrigins nullable.Nullable[string] `json:"webauthn_rp_origins"` } // AuthConfigResponseDbMaxPoolSizeUnit defines model for AuthConfigResponse.DbMaxPoolSizeUnit. @@ -3893,6 +3971,7 @@ type UpdateAuthConfigBody struct { OauthServerAllowDynamicRegistration nullable.Nullable[bool] `json:"oauth_server_allow_dynamic_registration,omitempty"` OauthServerAuthorizationPath nullable.Nullable[string] `json:"oauth_server_authorization_path,omitempty"` OauthServerEnabled nullable.Nullable[bool] `json:"oauth_server_enabled,omitempty"` + PasskeyEnabled *bool `json:"passkey_enabled,omitempty"` PasswordHibpEnabled nullable.Nullable[bool] `json:"password_hibp_enabled,omitempty"` PasswordMinLength nullable.Nullable[int] `json:"password_min_length,omitempty"` PasswordRequiredCharacters nullable.Nullable[UpdateAuthConfigBodyPasswordRequiredCharacters] `json:"password_required_characters,omitempty"` @@ -3913,10 +3992,10 @@ type UpdateAuthConfigBody struct { SecurityRefreshTokenReuseInterval nullable.Nullable[int] `json:"security_refresh_token_reuse_interval,omitempty"` SecuritySbForwardedForEnabled nullable.Nullable[bool] `json:"security_sb_forwarded_for_enabled,omitempty"` SecurityUpdatePasswordRequireReauthentication nullable.Nullable[bool] `json:"security_update_password_require_reauthentication,omitempty"` - SessionsInactivityTimeout nullable.Nullable[int] `json:"sessions_inactivity_timeout,omitempty"` + SessionsInactivityTimeout nullable.Nullable[float32] `json:"sessions_inactivity_timeout,omitempty"` SessionsSinglePerUser nullable.Nullable[bool] `json:"sessions_single_per_user,omitempty"` SessionsTags nullable.Nullable[string] `json:"sessions_tags,omitempty"` - SessionsTimebox nullable.Nullable[int] `json:"sessions_timebox,omitempty"` + SessionsTimebox nullable.Nullable[float32] `json:"sessions_timebox,omitempty"` SiteUrl nullable.Nullable[string] `json:"site_url,omitempty"` SmsAutoconfirm nullable.Nullable[bool] `json:"sms_autoconfirm,omitempty"` SmsMaxFrequency nullable.Nullable[int] `json:"sms_max_frequency,omitempty"` @@ -3948,6 +4027,9 @@ type UpdateAuthConfigBody struct { SmtpSenderName nullable.Nullable[string] `json:"smtp_sender_name,omitempty"` SmtpUser nullable.Nullable[string] `json:"smtp_user,omitempty"` UriAllowList nullable.Nullable[string] `json:"uri_allow_list,omitempty"` + WebauthnRpDisplayName nullable.Nullable[string] `json:"webauthn_rp_display_name,omitempty"` + WebauthnRpId nullable.Nullable[string] `json:"webauthn_rp_id,omitempty"` + WebauthnRpOrigins nullable.Nullable[string] `json:"webauthn_rp_origins,omitempty"` } // UpdateAuthConfigBodyDbMaxPoolSizeUnit defines model for UpdateAuthConfigBody.DbMaxPoolSizeUnit. @@ -4454,6 +4536,52 @@ type V1GetUsageApiRequestsCountResponse_Error struct { union json.RawMessage } +// V1ListEntitlementsResponse defines model for V1ListEntitlementsResponse. +type V1ListEntitlementsResponse struct { + Entitlements []struct { + Config V1ListEntitlementsResponse_Entitlements_Config `json:"config"` + Feature struct { + Key V1ListEntitlementsResponseEntitlementsFeatureKey `json:"key"` + Type V1ListEntitlementsResponseEntitlementsFeatureType `json:"type"` + } `json:"feature"` + HasAccess bool `json:"hasAccess"` + Type V1ListEntitlementsResponseEntitlementsType `json:"type"` + } `json:"entitlements"` +} + +// V1ListEntitlementsResponseEntitlementsConfig0 defines model for . +type V1ListEntitlementsResponseEntitlementsConfig0 struct { + Enabled bool `json:"enabled"` +} + +// V1ListEntitlementsResponseEntitlementsConfig1 defines model for . +type V1ListEntitlementsResponseEntitlementsConfig1 struct { + Enabled bool `json:"enabled"` + Unit string `json:"unit"` + Unlimited bool `json:"unlimited"` + Value float32 `json:"value"` +} + +// V1ListEntitlementsResponseEntitlementsConfig2 defines model for . +type V1ListEntitlementsResponseEntitlementsConfig2 struct { + Enabled bool `json:"enabled"` + Set []string `json:"set"` +} + +// V1ListEntitlementsResponse_Entitlements_Config defines model for V1ListEntitlementsResponse.Entitlements.Config. +type V1ListEntitlementsResponse_Entitlements_Config struct { + union json.RawMessage +} + +// V1ListEntitlementsResponseEntitlementsFeatureKey defines model for V1ListEntitlementsResponse.Entitlements.Feature.Key. +type V1ListEntitlementsResponseEntitlementsFeatureKey string + +// V1ListEntitlementsResponseEntitlementsFeatureType defines model for V1ListEntitlementsResponse.Entitlements.Feature.Type. +type V1ListEntitlementsResponseEntitlementsFeatureType string + +// V1ListEntitlementsResponseEntitlementsType defines model for V1ListEntitlementsResponse.Entitlements.Type. +type V1ListEntitlementsResponseEntitlementsType string + // V1ListMigrationsResponse defines model for V1ListMigrationsResponse. type V1ListMigrationsResponse = []struct { Name *string `json:"name,omitempty"` @@ -4519,6 +4647,13 @@ type V1PostgrestConfigResponse struct { MaxRows int `json:"max_rows"` } +// V1ProfileResponse defines model for V1ProfileResponse. +type V1ProfileResponse struct { + GotrueId string `json:"gotrue_id"` + PrimaryEmail string `json:"primary_email"` + Username string `json:"username"` +} + // V1ProjectAdvisorsResponse defines model for V1ProjectAdvisorsResponse. type V1ProjectAdvisorsResponse struct { Lints []struct { @@ -6591,6 +6726,94 @@ func (t *V1GetUsageApiRequestsCountResponse_Error) UnmarshalJSON(b []byte) error return err } +// AsV1ListEntitlementsResponseEntitlementsConfig0 returns the union data inside the V1ListEntitlementsResponse_Entitlements_Config as a V1ListEntitlementsResponseEntitlementsConfig0 +func (t V1ListEntitlementsResponse_Entitlements_Config) AsV1ListEntitlementsResponseEntitlementsConfig0() (V1ListEntitlementsResponseEntitlementsConfig0, error) { + var body V1ListEntitlementsResponseEntitlementsConfig0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromV1ListEntitlementsResponseEntitlementsConfig0 overwrites any union data inside the V1ListEntitlementsResponse_Entitlements_Config as the provided V1ListEntitlementsResponseEntitlementsConfig0 +func (t *V1ListEntitlementsResponse_Entitlements_Config) FromV1ListEntitlementsResponseEntitlementsConfig0(v V1ListEntitlementsResponseEntitlementsConfig0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeV1ListEntitlementsResponseEntitlementsConfig0 performs a merge with any union data inside the V1ListEntitlementsResponse_Entitlements_Config, using the provided V1ListEntitlementsResponseEntitlementsConfig0 +func (t *V1ListEntitlementsResponse_Entitlements_Config) MergeV1ListEntitlementsResponseEntitlementsConfig0(v V1ListEntitlementsResponseEntitlementsConfig0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsV1ListEntitlementsResponseEntitlementsConfig1 returns the union data inside the V1ListEntitlementsResponse_Entitlements_Config as a V1ListEntitlementsResponseEntitlementsConfig1 +func (t V1ListEntitlementsResponse_Entitlements_Config) AsV1ListEntitlementsResponseEntitlementsConfig1() (V1ListEntitlementsResponseEntitlementsConfig1, error) { + var body V1ListEntitlementsResponseEntitlementsConfig1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromV1ListEntitlementsResponseEntitlementsConfig1 overwrites any union data inside the V1ListEntitlementsResponse_Entitlements_Config as the provided V1ListEntitlementsResponseEntitlementsConfig1 +func (t *V1ListEntitlementsResponse_Entitlements_Config) FromV1ListEntitlementsResponseEntitlementsConfig1(v V1ListEntitlementsResponseEntitlementsConfig1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeV1ListEntitlementsResponseEntitlementsConfig1 performs a merge with any union data inside the V1ListEntitlementsResponse_Entitlements_Config, using the provided V1ListEntitlementsResponseEntitlementsConfig1 +func (t *V1ListEntitlementsResponse_Entitlements_Config) MergeV1ListEntitlementsResponseEntitlementsConfig1(v V1ListEntitlementsResponseEntitlementsConfig1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsV1ListEntitlementsResponseEntitlementsConfig2 returns the union data inside the V1ListEntitlementsResponse_Entitlements_Config as a V1ListEntitlementsResponseEntitlementsConfig2 +func (t V1ListEntitlementsResponse_Entitlements_Config) AsV1ListEntitlementsResponseEntitlementsConfig2() (V1ListEntitlementsResponseEntitlementsConfig2, error) { + var body V1ListEntitlementsResponseEntitlementsConfig2 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromV1ListEntitlementsResponseEntitlementsConfig2 overwrites any union data inside the V1ListEntitlementsResponse_Entitlements_Config as the provided V1ListEntitlementsResponseEntitlementsConfig2 +func (t *V1ListEntitlementsResponse_Entitlements_Config) FromV1ListEntitlementsResponseEntitlementsConfig2(v V1ListEntitlementsResponseEntitlementsConfig2) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeV1ListEntitlementsResponseEntitlementsConfig2 performs a merge with any union data inside the V1ListEntitlementsResponse_Entitlements_Config, using the provided V1ListEntitlementsResponseEntitlementsConfig2 +func (t *V1ListEntitlementsResponse_Entitlements_Config) MergeV1ListEntitlementsResponseEntitlementsConfig2(v V1ListEntitlementsResponseEntitlementsConfig2) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t V1ListEntitlementsResponse_Entitlements_Config) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *V1ListEntitlementsResponse_Entitlements_Config) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // AsV1ServiceHealthResponseInfo0 returns the union data inside the V1ServiceHealthResponse_Info as a V1ServiceHealthResponseInfo0 func (t V1ServiceHealthResponse_Info) AsV1ServiceHealthResponseInfo0() (V1ServiceHealthResponseInfo0, error) { var body V1ServiceHealthResponseInfo0 diff --git a/pkg/config/auth.go b/pkg/config/auth.go index 46df702d67..82c708e37c 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -162,6 +162,7 @@ type ( PasswordRequirements PasswordRequirements `toml:"password_requirements" json:"password_requirements"` SigningKeysPath string `toml:"signing_keys_path" json:"signing_keys_path"` SigningKeys []JWK `toml:"-" json:"-"` + Passkey *passkey `toml:"passkey" json:"passkey"` RateLimit rateLimit `toml:"rate_limit" json:"rate_limit"` Captcha *captcha `toml:"captcha" json:"captcha"` @@ -378,6 +379,13 @@ type ( Ethereum ethereum `toml:"ethereum" json:"ethereum"` } + passkey struct { + Enabled bool `toml:"enabled" json:"enabled"` + RpDisplayName string `toml:"rp_display_name" json:"rp_display_name"` + RpId string `toml:"rp_id" json:"rp_id"` + RpOrigins []string `toml:"rp_origins" json:"rp_origins"` + } + OAuthServer struct { Enabled bool `toml:"enabled" json:"enabled"` AllowDynamicRegistration bool `toml:"allow_dynamic_registration" json:"allow_dynamic_registration"` @@ -407,6 +415,9 @@ func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody { if a.Captcha != nil { a.Captcha.toAuthConfigBody(&body) } + if a.Passkey != nil { + a.Passkey.toAuthConfigBody(&body) + } a.Hook.toAuthConfigBody(&body) a.MFA.toAuthConfigBody(&body) a.Sessions.toAuthConfigBody(&body) @@ -430,6 +441,7 @@ func (a *auth) FromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) { a.MinimumPasswordLength = cast.IntToUint(ValOrDefault(remoteConfig.PasswordMinLength, 0)) prc := ValOrDefault(remoteConfig.PasswordRequiredCharacters, "") a.PasswordRequirements = NewPasswordRequirement(v1API.UpdateAuthConfigBodyPasswordRequiredCharacters(prc)) + a.Passkey.fromAuthConfig(remoteConfig) a.RateLimit.fromAuthConfig(remoteConfig) if s := a.Email.Smtp; s != nil && s.Enabled { a.RateLimit.EmailSent = cast.IntToUint(ValOrDefault(remoteConfig.RateLimitEmailSent, 0)) @@ -489,6 +501,28 @@ func (c *captcha) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { c.Enabled = ValOrDefault(remoteConfig.SecurityCaptchaEnabled, false) } +func (p passkey) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { + if body.PasskeyEnabled = cast.Ptr(p.Enabled); p.Enabled { + body.WebauthnRpDisplayName = nullable.NewNullableWithValue(p.RpDisplayName) + body.WebauthnRpId = nullable.NewNullableWithValue(p.RpId) + body.WebauthnRpOrigins = nullable.NewNullableWithValue(strings.Join(p.RpOrigins, ",")) + } +} + +func (p *passkey) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { + // When local config is not set, we assume platform defaults should not change + if p == nil { + return + } + // Ignore disabled passkey fields to minimise config diff + if p.Enabled { + p.RpDisplayName = ValOrDefault(remoteConfig.WebauthnRpDisplayName, "") + p.RpId = ValOrDefault(remoteConfig.WebauthnRpId, "") + p.RpOrigins = strToArr(ValOrDefault(remoteConfig.WebauthnRpOrigins, "")) + } + p.Enabled = remoteConfig.PasskeyEnabled +} + func (h hook) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { // When local config is not set, we assume platform defaults should not change if hook := h.BeforeUserCreated; hook != nil { @@ -629,8 +663,8 @@ func (m *mfa) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { } func (s sessions) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { - body.SessionsTimebox = nullable.NewNullableWithValue(int(s.Timebox.Hours())) - body.SessionsInactivityTimeout = nullable.NewNullableWithValue(int(s.InactivityTimeout.Hours())) + body.SessionsTimebox = nullable.NewNullableWithValue(float32(s.Timebox.Hours())) + body.SessionsInactivityTimeout = nullable.NewNullableWithValue(float32(s.InactivityTimeout.Hours())) } func (s *sessions) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { diff --git a/pkg/config/auth_test.go b/pkg/config/auth_test.go index fc0b2c6f44..65f0066da9 100644 --- a/pkg/config/auth_test.go +++ b/pkg/config/auth_test.go @@ -212,6 +212,105 @@ func TestCaptchaDiff(t *testing.T) { }) } +func TestPasskeyConfigMapping(t *testing.T) { + t.Run("serializes passkey config to update body", func(t *testing.T) { + c := newWithDefaults() + c.Passkey = &passkey{ + Enabled: true, + RpDisplayName: "Supabase CLI", + RpId: "localhost", + RpOrigins: []string{ + "http://127.0.0.1:3000", + "https://localhost:3000", + }, + } + // Run test + body := c.ToUpdateAuthConfigBody() + // Check result + if assert.NotNil(t, body.PasskeyEnabled) { + assert.True(t, *body.PasskeyEnabled) + } + assert.Equal(t, "Supabase CLI", ValOrDefault(body.WebauthnRpDisplayName, "")) + assert.Equal(t, "localhost", ValOrDefault(body.WebauthnRpId, "")) + assert.Equal(t, "http://127.0.0.1:3000,https://localhost:3000", ValOrDefault(body.WebauthnRpOrigins, "")) + }) + + t.Run("does not serialize rp fields when passkey is disabled", func(t *testing.T) { + c := newWithDefaults() + c.Passkey = &passkey{ + Enabled: false, + RpDisplayName: "Supabase CLI", + RpId: "localhost", + RpOrigins: []string{"http://127.0.0.1:3000"}, + } + // Run test + body := c.ToUpdateAuthConfigBody() + // Check result + if assert.NotNil(t, body.PasskeyEnabled) { + assert.False(t, *body.PasskeyEnabled) + } + _, err := body.WebauthnRpDisplayName.Get() + assert.Error(t, err) + _, err = body.WebauthnRpId.Get() + assert.Error(t, err) + _, err = body.WebauthnRpOrigins.Get() + assert.Error(t, err) + }) + + t.Run("hydrates passkey config from remote", func(t *testing.T) { + c := newWithDefaults() + c.Passkey = &passkey{ + Enabled: true, + } + // Run test + c.FromRemoteAuthConfig(v1API.AuthConfigResponse{ + PasskeyEnabled: true, + WebauthnRpDisplayName: nullable.NewNullableWithValue("Supabase CLI"), + WebauthnRpId: nullable.NewNullableWithValue("localhost"), + WebauthnRpOrigins: nullable.NewNullableWithValue("http://127.0.0.1:3000,https://localhost:3000"), + }) + // Check result + if assert.NotNil(t, c.Passkey) { + assert.True(t, c.Passkey.Enabled) + assert.Equal(t, "Supabase CLI", c.Passkey.RpDisplayName) + assert.Equal(t, "localhost", c.Passkey.RpId) + assert.Equal(t, []string{ + "http://127.0.0.1:3000", + "https://localhost:3000", + }, c.Passkey.RpOrigins) + } + }) + + t.Run("ignores remote settings when local passkey config is undefined", func(t *testing.T) { + c := newWithDefaults() + // Run test + c.FromRemoteAuthConfig(v1API.AuthConfigResponse{ + PasskeyEnabled: true, + WebauthnRpDisplayName: nullable.NewNullableWithValue("Supabase CLI"), + WebauthnRpId: nullable.NewNullableWithValue("localhost"), + WebauthnRpOrigins: nullable.NewNullableWithValue("http://127.0.0.1:3000"), + }) + // Check result + assert.Nil(t, c.Passkey) + }) +} + +func TestPasskeyDiff(t *testing.T) { + t.Run("ignores undefined config", func(t *testing.T) { + c := newWithDefaults() + // Run test + diff, err := c.DiffWithRemote(v1API.AuthConfigResponse{ + PasskeyEnabled: true, + WebauthnRpDisplayName: nullable.NewNullableWithValue("Supabase CLI"), + WebauthnRpId: nullable.NewNullableWithValue("localhost"), + WebauthnRpOrigins: nullable.NewNullableWithValue("http://127.0.0.1:3000"), + }) + // 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() diff --git a/pkg/config/config.go b/pkg/config/config.go index 018528a50a..90d81741b1 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -260,6 +260,11 @@ func (a *auth) Clone() auth { capt := *a.Captcha copy.Captcha = &capt } + if copy.Passkey != nil { + passkey := *a.Passkey + passkey.RpOrigins = slices.Clone(a.Passkey.RpOrigins) + copy.Passkey = &passkey + } copy.External = maps.Clone(a.External) if a.Email.Smtp != nil { mailer := *a.Email.Smtp @@ -916,6 +921,24 @@ func (c *config) Validate(fsys fs.FS) error { return errors.Errorf("failed to decode signing keys: %w", err) } } + if c.Auth.Passkey != nil { + if c.Auth.Passkey.Enabled { + if len(c.Auth.Passkey.RpId) == 0 { + return errors.New("Missing required field in config: auth.passkey.rp_id") + } + if len(c.Auth.Passkey.RpOrigins) == 0 { + return errors.New("Missing required field in config: auth.passkey.rp_origins") + } + if err := assertEnvLoaded(c.Auth.Passkey.RpId); err != nil { + return errors.Errorf("Invalid config for auth.passkey.rp_id: %v", err) + } + for i, origin := range c.Auth.Passkey.RpOrigins { + if err := assertEnvLoaded(origin); err != nil { + return errors.Errorf("Invalid config for auth.passkey.rp_origins[%d]: %v", i, err) + } + } + } + } if err := c.Auth.Hook.validate(); err != nil { return err } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 42c28acfd4..6957331161 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -74,6 +74,69 @@ func TestConfigParsing(t *testing.T) { // Run test assert.Error(t, config.Load("", fsys)) }) + t.Run("config file with passkey settings", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "supabase/config.toml": &fs.MapFile{Data: []byte(` +[auth] +enabled = true +site_url = "http://127.0.0.1:3000" +[auth.passkey] +enabled = true +rp_display_name = "Supabase CLI" +rp_id = "localhost" +rp_origins = ["http://127.0.0.1:3000", "https://localhost:3000"] +`)}, + } + // Run test + assert.NoError(t, config.Load("", fsys)) + // Check result + if assert.NotNil(t, config.Auth.Passkey) { + assert.True(t, config.Auth.Passkey.Enabled) + assert.Equal(t, "Supabase CLI", config.Auth.Passkey.RpDisplayName) + assert.Equal(t, "localhost", config.Auth.Passkey.RpId) + assert.Equal(t, []string{ + "http://127.0.0.1:3000", + "https://localhost:3000", + }, config.Auth.Passkey.RpOrigins) + } + }) + + t.Run("passkey enabled requires rp_id", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "supabase/config.toml": &fs.MapFile{Data: []byte(` +[auth] +enabled = true +site_url = "http://127.0.0.1:3000" +[auth.passkey] +enabled = true +rp_origins = ["http://127.0.0.1:3000"] +`)}, + } + // Run test + err := config.Load("", fsys) + // Check result + assert.ErrorContains(t, err, "Missing required field in config: auth.passkey.rp_id") + }) + + t.Run("passkey enabled requires rp_origins", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "supabase/config.toml": &fs.MapFile{Data: []byte(` +[auth] +enabled = true +site_url = "http://127.0.0.1:3000" +[auth.passkey] +enabled = true +rp_id = "localhost" +`)}, + } + // Run test + err := config.Load("", fsys) + // Check result + assert.ErrorContains(t, err, "Missing required field in config: auth.passkey.rp_origins") + }) t.Run("parses experimental pgdelta config", func(t *testing.T) { config := NewConfig() diff --git a/pkg/config/templates/Dockerfile b/pkg/config/templates/Dockerfile index db20fa4731..a1bd00e929 100644 --- a/pkg/config/templates/Dockerfile +++ b/pkg/config/templates/Dockerfile @@ -1,19 +1,19 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.095 AS pg +FROM supabase/postgres:17.6.1.104 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.22.3 AS mailpit FROM postgrest/postgrest:v14.7 AS postgrest -FROM supabase/postgres-meta:v0.96.1 AS pgmeta -FROM supabase/studio:2026.03.23-sha-b7847b7 AS studio +FROM supabase/postgres-meta:v0.96.2 AS pgmeta +FROM supabase/studio:2026.03.30-sha-12a43e5 AS studio FROM darthsim/imgproxy:v3.8.0 AS imgproxy -FROM supabase/edge-runtime:v1.73.0 AS edgeruntime +FROM supabase/edge-runtime:v1.73.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.7.4 AS supavisor FROM supabase/gotrue:v2.188.1 AS gotrue -FROM supabase/realtime:v2.78.18 AS realtime -FROM supabase/storage-api:v1.44.11 AS storage -FROM supabase/logflare:1.34.14 AS logflare +FROM supabase/realtime:v2.80.7 AS realtime +FROM supabase/storage-api:v1.48.13 AS storage +FROM supabase/logflare:1.36.1 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ FROM supabase/migra:3.0.1663481299 AS migra diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index f4d5a7961e..93426ddd53 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -177,6 +177,13 @@ minimum_password_length = 6 # are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` password_requirements = "" +# Configure passkey sign-ins. +# [auth.passkey] +# enabled = false +# rp_display_name = "Supabase" +# rp_id = "localhost" +# rp_origins = ["http://127.0.0.1:3000"] + [auth.rate_limit] # Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. email_sent = 2 diff --git a/pkg/go.mod b/pkg/go.mod index 5eebfd7245..0c2cc154fc 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( github.com/BurntSushi/toml v1.6.0 - github.com/andybalholm/brotli v1.2.0 + github.com/andybalholm/brotli v1.2.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/docker/go-units v0.5.0 github.com/ecies/go/v2 v2.0.11 @@ -20,13 +20,13 @@ require ( github.com/jackc/pgx/v4 v4.18.3 github.com/joho/godotenv v1.5.1 github.com/oapi-codegen/nullable v1.1.0 - github.com/oapi-codegen/runtime v1.3.0 + github.com/oapi-codegen/runtime v1.3.1 github.com/spf13/afero v1.15.0 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/tidwall/jsonc v0.3.3 golang.org/x/mod v0.34.0 - google.golang.org/grpc v1.79.3 + google.golang.org/grpc v1.80.0 ) require ( @@ -51,8 +51,8 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/go.sum b/pkg/go.sum index 20e4a10560..dc8be0f4e4 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -3,8 +3,8 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= @@ -132,8 +132,8 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= -github.com/oapi-codegen/runtime v1.3.0 h1:vyK1zc0gDWWXgk2xoQa4+X4RNNc5SL2RbTpJS/4vMYA= -github.com/oapi-codegen/runtime v1.3.0/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= +github.com/oapi-codegen/runtime v1.3.1 h1:RgDY6J4OGQLbRXhG/Xpt3vSVqYpHQS7hN4m85+5xB9g= +github.com/oapi-codegen/runtime v1.3.1/go.mod h1:kOdeacKy7t40Rclb1je37ZLFboFxh+YLy0zaPCMibPY= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -217,8 +217,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -255,8 +255,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -272,8 +272,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -290,8 +290,8 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=