diff --git a/cmd/gen.go b/cmd/gen.go index 75377505b5..1c6566af48 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -1,12 +1,15 @@ package cmd import ( + "encoding/json" "os" "os/signal" + "strings" "time" env "github.com/Netflix/go-env" "github.com/go-errors/errors" + "github.com/go-viper/mapstructure/v2" "github.com/golang-jwt/jwt/v5" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -124,17 +127,18 @@ Supported algorithms: claims config.CustomClaims expiry time.Time validFor time.Duration + payload string genJWTCmd = &cobra.Command{ Use: "bearer-jwt", Short: "Generate a Bearer Auth JWT for accessing Data API", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - if expiry.IsZero() { - expiry = time.Now().Add(validFor) + custom := jwt.MapClaims{} + if err := parseClaims(custom); err != nil { + return err } - claims.ExpiresAt = jwt.NewNumericDate(expiry) - return bearerjwt.Run(cmd.Context(), claims, os.Stdout, afero.NewOsFs()) + return bearerjwt.Run(cmd.Context(), custom, os.Stdout, afero.NewOsFs()) }, } ) @@ -166,12 +170,42 @@ func init() { genCmd.AddCommand(genSigningKeyCmd) tokenFlags := genJWTCmd.Flags() tokenFlags.StringVar(&claims.Role, "role", "", "Postgres role to use.") + cobra.CheckErr(genJWTCmd.MarkFlagRequired("role")) tokenFlags.StringVar(&claims.Subject, "sub", "", "User ID to impersonate.") genJWTCmd.Flag("sub").DefValue = "anonymous" tokenFlags.TimeVar(&expiry, "exp", time.Time{}, []string{time.RFC3339}, "Expiry timestamp for this token.") tokenFlags.DurationVar(&validFor, "valid-for", time.Minute*30, "Validity duration for this token.") - genJWTCmd.MarkFlagsMutuallyExclusive("exp", "valid-for") - cobra.CheckErr(genJWTCmd.MarkFlagRequired("role")) + tokenFlags.StringVar(&payload, "payload", "{}", "Custom claims in JSON format.") genCmd.AddCommand(genJWTCmd) rootCmd.AddCommand(genCmd) } + +func parseClaims(custom jwt.MapClaims) error { + // Initialise default claims + now := time.Now() + if expiry.IsZero() { + expiry = now.Add(validFor) + } else { + now = expiry.Add(-validFor) + } + claims.IssuedAt = jwt.NewNumericDate(now) + claims.ExpiresAt = jwt.NewNumericDate(expiry) + // Set is_anonymous = true for authenticated role without explicit user ID + if strings.EqualFold(claims.Role, "authenticated") && len(claims.Subject) == 0 { + claims.IsAnon = true + } + // Override with custom claims + if dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + TagName: "json", + Squash: true, + Result: &custom, + }); err != nil { + return errors.Errorf("failed to init decoder: %w", err) + } else if err := dec.Decode(claims); err != nil { + return errors.Errorf("failed to decode claims: %w", err) + } + if err := json.Unmarshal([]byte(payload), &custom); err != nil { + return errors.Errorf("failed to parse payload: %w", err) + } + return nil +} diff --git a/internal/functions/download/download.go b/internal/functions/download/download.go index d37d4a54ea..e75ec7d9fa 100644 --- a/internal/functions/download/download.go +++ b/internal/functions/download/download.go @@ -1,6 +1,7 @@ package download import ( + "bufio" "bytes" "context" "fmt" @@ -200,10 +201,40 @@ func extractOne(ctx context.Context, slug, eszipPath string) error { network.NetworkingConfig{}, "", os.Stdout, - os.Stderr, + getErrorLogger(), ) } +func getErrorLogger() io.Writer { + if utils.Config.EdgeRuntime.DenoVersion > 1 { + return os.Stderr + } + // Additional error handling for deno v1 + r, w := io.Pipe() + go func() { + logs := bufio.NewScanner(r) + for logs.Scan() { + line := logs.Text() + fmt.Fprintln(os.Stderr, line) + if strings.EqualFold(line, "invalid eszip v2") { + utils.CmdSuggestion = suggestDenoV2() + } + } + if err := logs.Err(); err != nil { + fmt.Fprintln(os.Stderr, err) + } + }() + return w +} + +func suggestDenoV2() string { + return fmt.Sprintf(`Please use deno v2 in %s to download this Function: + +[edge_runtime] +deno_version = 2 +`, utils.Bold(utils.ConfigPath)) +} + func suggestLegacyBundle(slug string) string { return fmt.Sprintf("\nIf your function is deployed using CLI < 1.120.0, trying running %s instead.", utils.Aqua("supabase functions download --legacy-bundle "+slug)) } diff --git a/internal/gen/bearerjwt/bearerjwt.go b/internal/gen/bearerjwt/bearerjwt.go index 4c686f9918..e81f79e6a5 100644 --- a/internal/gen/bearerjwt/bearerjwt.go +++ b/internal/gen/bearerjwt/bearerjwt.go @@ -2,41 +2,80 @@ package bearerjwt import ( "context" + "encoding/json" "fmt" "io" "os" "strings" "github.com/go-errors/errors" + "github.com/golang-jwt/jwt/v5" "github.com/spf13/afero" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/config" ) -func Run(ctx context.Context, claims config.CustomClaims, w io.Writer, fsys afero.Fs) error { +func Run(ctx context.Context, claims jwt.Claims, w io.Writer, fsys afero.Fs) error { if err := flags.LoadConfig(fsys); err != nil { return err } - // Set is_anonymous = true for authenticated role without explicit user ID - if strings.EqualFold(claims.Role, "authenticated") && len(claims.Subject) == 0 { - claims.IsAnon = true - } - // Use the first signing key that passes validation - for _, k := range utils.Config.Auth.SigningKeys { - fmt.Fprintln(os.Stderr, "Using signing key ID:", k.KeyID.String()) - if token, err := config.GenerateAsymmetricJWT(k, claims); err != nil { - fmt.Fprintln(os.Stderr, err) - } else { - fmt.Fprintln(w, token) - return nil - } + key, err := getSigningKey(ctx) + if err != nil { + return err } - fmt.Fprintln(os.Stderr, "Using legacy JWT secret...") - token, err := claims.NewToken().SignedString([]byte(utils.Config.Auth.JwtSecret.Value)) + token, err := config.GenerateAsymmetricJWT(*key, claims) if err != nil { - return errors.Errorf("failed to generate auth token: %w", err) + return err } fmt.Fprintln(w, token) return nil } + +func getSigningKey(ctx context.Context) (*config.JWK, error) { + console := utils.NewConsole() + if len(utils.Config.Auth.SigningKeysPath) == 0 { + title := "Enter your signing key in JWK format: " + kid, err := console.PromptText(ctx, title) + if err != nil { + return nil, err + } + key := config.JWK{} + if err := json.Unmarshal([]byte(kid), &key); err != nil { + return nil, errors.Errorf("failed to parse JWK: %w", err) + } + return &key, nil + } + // Allow manual kid entry on CI + if !console.IsTTY { + title := "Enter the kid of your signing key (or leave blank to use the first one): " + kid, err := console.PromptText(ctx, title) + if err != nil { + return nil, err + } + for i, k := range utils.Config.Auth.SigningKeys { + if k.KeyID == kid { + return &utils.Config.Auth.SigningKeys[i], nil + } + } + if len(kid) == 0 && len(utils.Config.Auth.SigningKeys) > 0 { + return &utils.Config.Auth.SigningKeys[0], nil + } + return nil, errors.Errorf("signing key not found: %s", kid) + } + // Let user choose from a list of signing keys + items := make([]utils.PromptItem, len(utils.Config.Auth.SigningKeys)) + for i, k := range utils.Config.Auth.SigningKeys { + items[i] = utils.PromptItem{ + Index: i, + Summary: k.KeyID, + Details: fmt.Sprintf("%s (%s)", k.Algorithm, strings.Join(k.KeyOps, ",")), + } + } + choice, err := utils.PromptChoice(ctx, "Select a signing key:", items) + if err != nil { + return nil, err + } + fmt.Fprintln(os.Stderr, "Selected key ID:", choice.Summary) + return &utils.Config.Auth.SigningKeys[choice.Index], nil +} diff --git a/internal/gen/bearerjwt/bearerjwt_test.go b/internal/gen/bearerjwt/bearerjwt_test.go index 47b307932e..4bce6c1a9d 100644 --- a/internal/gen/bearerjwt/bearerjwt_test.go +++ b/internal/gen/bearerjwt/bearerjwt_test.go @@ -5,41 +5,56 @@ import ( "context" "crypto/ecdsa" "crypto/elliptic" + "crypto/rsa" _ "embed" "encoding/json" + "io" "testing" "github.com/golang-jwt/jwt/v5" - "github.com/google/uuid" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/gen/signingkeys" + "github.com/supabase/cli/internal/testing/fstest" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/config" ) func TestGenerateToken(t *testing.T) { + // Setup private key - ECDSA + privateKeyECDSA, err := signingkeys.GeneratePrivateKey(config.AlgES256) + require.NoError(t, err) + // Setup public key for validation + publicKeyECDSA := ecdsa.PublicKey{Curve: elliptic.P256()} + publicKeyECDSA.X, err = config.NewBigIntFromBase64(privateKeyECDSA.X) + require.NoError(t, err) + publicKeyECDSA.Y, err = config.NewBigIntFromBase64(privateKeyECDSA.Y) + require.NoError(t, err) + + // Setup private key - RSA + privateKeyRSA, err := signingkeys.GeneratePrivateKey(config.AlgRS256) + require.NoError(t, err) + // Setup public key for validation + publicKeyRSA := rsa.PublicKey{} + publicKeyRSA.N, err = config.NewBigIntFromBase64(privateKeyRSA.Modulus) + require.NoError(t, err) + bigE, err := config.NewBigIntFromBase64(privateKeyRSA.Exponent) + require.NoError(t, err) + publicKeyRSA.E = int(bigE.Int64()) + t.Run("mints custom JWT", func(t *testing.T) { claims := config.CustomClaims{ - Role: "authenticated", + IsAnon: true, + Role: "authenticated", } - // Setup private key - privateKey, err := signingkeys.GeneratePrivateKey(config.AlgES256) - require.NoError(t, err) - // Setup public key for validation - publicKey := ecdsa.PublicKey{Curve: elliptic.P256()} - publicKey.X, err = config.NewBigIntFromBase64(privateKey.X) - require.NoError(t, err) - publicKey.Y, err = config.NewBigIntFromBase64(privateKey.Y) - require.NoError(t, err) // Setup in-memory fs fsys := afero.NewMemMapFs() require.NoError(t, utils.WriteFile("supabase/config.toml", []byte(` [auth] signing_keys_path = "./keys.json" `), fsys)) - testKey, err := json.Marshal([]config.JWK{*privateKey}) + testKey, err := json.Marshal([]config.JWK{*privateKeyECDSA}) require.NoError(t, err) require.NoError(t, utils.WriteFile("supabase/keys.json", testKey, fsys)) // Run test @@ -48,13 +63,13 @@ func TestGenerateToken(t *testing.T) { // Check error assert.NoError(t, err) token, err := jwt.NewParser().Parse(buf.String(), func(t *jwt.Token) (any, error) { - return &publicKey, nil + return &publicKeyECDSA, nil }) assert.NoError(t, err) assert.True(t, token.Valid) assert.Equal(t, map[string]any{ "alg": "ES256", - "kid": privateKey.KeyID.String(), + "kid": privateKeyECDSA.KeyID, "typ": "JWT", }, token.Header) assert.Equal(t, jwt.MapClaims{ @@ -63,36 +78,116 @@ func TestGenerateToken(t *testing.T) { }, token.Claims) }) - t.Run("mints legacy JWT", func(t *testing.T) { + t.Run("throws error on unsupported kty", func(t *testing.T) { + claims := jwt.MapClaims{} + // Setup in-memory fs + fsys := afero.NewMemMapFs() + require.NoError(t, utils.WriteFile("supabase/config.toml", []byte(` + [auth] + signing_keys_path = "./keys.json" + `), fsys)) + testKey, err := json.Marshal([]config.JWK{{KeyType: "oct"}}) + require.NoError(t, err) + require.NoError(t, utils.WriteFile("supabase/keys.json", testKey, fsys)) + // Run test + err = Run(context.Background(), claims, io.Discard, fsys) + // Check error + assert.ErrorContains(t, err, "failed to convert JWK to private key: unsupported key type: oct") + }) + + t.Run("accepts signing key from stdin", func(t *testing.T) { utils.Config.Auth.SigningKeysPath = "" utils.Config.Auth.SigningKeys = nil claims := config.CustomClaims{ - RegisteredClaims: jwt.RegisteredClaims{ - Subject: uuid.New().String(), - }, - Role: "authenticated", + Role: "service_role", } // Setup in-memory fs fsys := afero.NewMemMapFs() + testKey, err := json.Marshal(privateKeyRSA) + require.NoError(t, err) + t.Cleanup(fstest.MockStdin(t, string(testKey))) // Run test var buf bytes.Buffer - err := Run(context.Background(), claims, &buf, fsys) + err = Run(context.Background(), claims, &buf, fsys) // Check error assert.NoError(t, err) token, err := jwt.NewParser().Parse(buf.String(), func(t *jwt.Token) (any, error) { - return []byte(utils.Config.Auth.JwtSecret.Value), nil + return &publicKeyRSA, nil }) assert.NoError(t, err) assert.True(t, token.Valid) assert.Equal(t, map[string]any{ - "alg": "HS256", + "alg": "RS256", + "kid": privateKeyRSA.KeyID, "typ": "JWT", }, token.Header) assert.Equal(t, jwt.MapClaims{ - "exp": float64(1983812996), - "iss": "supabase-demo", - "role": "authenticated", - "sub": claims.Subject, + "role": "service_role", }, token.Claims) }) + + t.Run("throws error on invalid key", func(t *testing.T) { + claims := jwt.MapClaims{} + // Setup in-memory fs + fsys := afero.NewMemMapFs() + t.Cleanup(fstest.MockStdin(t, "")) + // Run test + err = Run(context.Background(), claims, io.Discard, fsys) + // Check error + assert.ErrorContains(t, err, "failed to parse JWK: unexpected end of JSON input") + }) + + t.Run("accepts kid from stdin", func(t *testing.T) { + claims := jwt.MapClaims{ + "role": "postgres", + "sb-role": "mgmt-api", + } + // Setup in-memory fs + fsys := afero.NewMemMapFs() + require.NoError(t, utils.WriteFile("supabase/config.toml", []byte(` + [auth] + signing_keys_path = "./keys.json" + `), fsys)) + testKey, err := json.Marshal([]config.JWK{ + *privateKeyECDSA, + *privateKeyRSA, + }) + require.NoError(t, err) + require.NoError(t, utils.WriteFile("supabase/keys.json", testKey, fsys)) + t.Cleanup(fstest.MockStdin(t, privateKeyRSA.KeyID)) + // Run test + var buf bytes.Buffer + err = Run(context.Background(), claims, &buf, fsys) + // Check error + assert.NoError(t, err) + token, err := jwt.NewParser().Parse(buf.String(), func(t *jwt.Token) (any, error) { + return &publicKeyRSA, nil + }) + assert.NoError(t, err) + assert.True(t, token.Valid) + assert.Equal(t, map[string]any{ + "alg": "RS256", + "kid": privateKeyRSA.KeyID, + "typ": "JWT", + }, token.Header) + assert.Equal(t, jwt.MapClaims{ + "role": "postgres", + "sb-role": "mgmt-api", + }, token.Claims) + }) + + t.Run("throws error on missing key", func(t *testing.T) { + claims := jwt.MapClaims{} + // Setup in-memory fs + fsys := afero.NewMemMapFs() + require.NoError(t, utils.WriteFile("supabase/config.toml", []byte(` + [auth] + signing_keys_path = "./keys.json" + `), fsys)) + t.Cleanup(fstest.MockStdin(t, "test-key")) + // Run test + err = Run(context.Background(), claims, io.Discard, fsys) + // Check error + assert.ErrorContains(t, err, "signing key not found: test-key") + }) } diff --git a/internal/gen/signingkeys/signingkeys.go b/internal/gen/signingkeys/signingkeys.go index 22946df67a..5651ac2651 100644 --- a/internal/gen/signingkeys/signingkeys.go +++ b/internal/gen/signingkeys/signingkeys.go @@ -52,7 +52,7 @@ func generateRSAKeyPair(keyID uuid.UUID) (*config.JWK, error) { // Convert to JWK format privateJWK := config.JWK{ KeyType: "RSA", - KeyID: keyID, + KeyID: keyID.String(), Use: "sig", KeyOps: []string{"sign", "verify"}, Algorithm: "RS256", @@ -82,7 +82,7 @@ func generateECDSAKeyPair(keyID uuid.UUID) (*config.JWK, error) { // Convert to JWK format privateJWK := config.JWK{ KeyType: "EC", - KeyID: keyID, + KeyID: keyID.String(), Use: "sig", KeyOps: []string{"sign", "verify"}, Algorithm: "ES256", @@ -110,64 +110,65 @@ func Run(ctx context.Context, algorithm string, appendMode bool, fsys afero.Fs) return err } - out := io.Writer(os.Stdout) + // Only serialise a single key to stdout + if len(outputPath) == 0 { + enc := json.NewEncoder(os.Stdout) + if err := enc.Encode(privateJWK); err != nil { + return errors.Errorf("failed to encode signing key: %w", err) + } + utils.CmdSuggestion = fmt.Sprintf(` +To enable JWT signing keys in your local project: +1. Save the generated key to %s +2. Update your %s with the new keys path + +[auth] +signing_keys_path = "./signing_key.json" +`, utils.Bold(filepath.Join(utils.SupabaseDirPath, "signing_key.json")), utils.Bold(utils.ConfigPath)) + return nil + } + var jwkArray []config.JWK - if len(outputPath) > 0 { - if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(outputPath)); err != nil { - return err + if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(outputPath)); err != nil { + return err + } + f, err := fsys.OpenFile(outputPath, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return errors.Errorf("failed to open signing key: %w", err) + } + defer f.Close() + if appendMode { + // Load existing key and reset file + dec := json.NewDecoder(f) + // Since a new file is empty, we must ignore EOF error + if err := dec.Decode(&jwkArray); err != nil && !errors.Is(err, io.EOF) { + return errors.Errorf("failed to decode signing key: %w", err) + } + if _, err = f.Seek(0, io.SeekStart); err != nil { + return errors.Errorf("failed to seek signing key: %w", err) } - f, err := fsys.OpenFile(outputPath, os.O_RDWR|os.O_CREATE, 0600) + } else if fi, err := f.Stat(); fi.Size() > 0 { if err != nil { - return errors.Errorf("failed to open signing key: %w", err) + fmt.Fprintln(utils.GetDebugLogger(), err) + } + label := fmt.Sprintf("Do you want to overwrite the existing %s file?", utils.Bold(outputPath)) + if shouldOverwrite, err := utils.NewConsole().PromptYesNo(ctx, label, true); err != nil { + return err + } else if !shouldOverwrite { + return errors.New(context.Canceled) } - defer f.Close() - if appendMode { - // Load existing key and reset file - dec := json.NewDecoder(f) - // Since a new file is empty, we must ignore EOF error - if err := dec.Decode(&jwkArray); err != nil && !errors.Is(err, io.EOF) { - return errors.Errorf("failed to decode signing key: %w", err) - } - if _, err = f.Seek(0, io.SeekStart); err != nil { - return errors.Errorf("failed to seek signing key: %w", err) - } - } else if fi, err := f.Stat(); fi.Size() > 0 { - if err != nil { - fmt.Fprintln(utils.GetDebugLogger(), err) - } - label := fmt.Sprintf("Do you want to overwrite the existing %s file?", utils.Bold(outputPath)) - if shouldOverwrite, err := utils.NewConsole().PromptYesNo(ctx, label, true); err != nil { - return err - } else if !shouldOverwrite { - return errors.New(context.Canceled) - } - if err := f.Truncate(0); err != nil { - return errors.Errorf("failed to truncate signing key: %w", err) - } + if err := f.Truncate(0); err != nil { + return errors.Errorf("failed to truncate signing key: %w", err) } - out = f } jwkArray = append(jwkArray, *privateJWK) // Write to file - enc := json.NewEncoder(out) + enc := json.NewEncoder(f) enc.SetIndent("", " ") if err := enc.Encode(jwkArray); err != nil { return errors.Errorf("failed to encode signing key: %w", err) } - if len(outputPath) == 0 { - utils.CmdSuggestion = fmt.Sprintf(` -To enable JWT signing keys in your local project: -1. Save the generated key to %s -2. Update your %s with the new keys path - -[auth] -signing_keys_path = "./signing_key.json" -`, utils.Bold(filepath.Join(utils.SupabaseDirPath, "signing_key.json")), utils.Bold(utils.ConfigPath)) - return nil - } - fmt.Fprintf(os.Stderr, "JWT signing key appended to: %s (now contains %d keys)\n", utils.Bold(outputPath), len(jwkArray)) if len(jwkArray) == 1 { if ignored, err := utils.IsGitIgnored(outputPath); err != nil { diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index 704a94372b..e3513b91a3 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -10571,7 +10571,7 @@ func (r V1GetActionRunLogsResponse) StatusCode() int { type V1UpdateActionRunStatusResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *ActionRunResponse + JSON200 *UpdateRunStatusResponse } // Status returns HTTPResponse.Status @@ -15315,7 +15315,7 @@ func ParseV1UpdateActionRunStatusResponse(rsp *http.Response) (*V1UpdateActionRu switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ActionRunResponse + var dest UpdateRunStatusResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index b7c5738292..1fa2f30a60 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -396,7 +396,7 @@ const ( // Defines values for DeleteRolesResponseMessage. const ( - Ok DeleteRolesResponseMessage = "ok" + DeleteRolesResponseMessageOk DeleteRolesResponseMessage = "ok" ) // Defines values for DeployFunctionResponseStatus. @@ -952,6 +952,11 @@ const ( UpdateRunStatusBodySeedRUNNING UpdateRunStatusBodySeed = "RUNNING" ) +// Defines values for UpdateRunStatusResponseMessage. +const ( + UpdateRunStatusResponseMessageOk UpdateRunStatusResponseMessage = "ok" +) + // Defines values for UpdateSigningKeyBodyStatus. const ( UpdateSigningKeyBodyStatusInUse UpdateSigningKeyBodyStatus = "in_use" @@ -3328,6 +3333,14 @@ type UpdateRunStatusBodyPull string // UpdateRunStatusBodySeed defines model for UpdateRunStatusBody.Seed. type UpdateRunStatusBodySeed string +// UpdateRunStatusResponse defines model for UpdateRunStatusResponse. +type UpdateRunStatusResponse struct { + Message UpdateRunStatusResponseMessage `json:"message"` +} + +// UpdateRunStatusResponseMessage defines model for UpdateRunStatusResponse.Message. +type UpdateRunStatusResponseMessage string + // UpdateSigningKeyBody defines model for UpdateSigningKeyBody. type UpdateSigningKeyBody struct { Status UpdateSigningKeyBodyStatus `json:"status"` diff --git a/pkg/config/apikeys.go b/pkg/config/apikeys.go index 879ef9fea8..12ecb20762 100644 --- a/pkg/config/apikeys.go +++ b/pkg/config/apikeys.go @@ -11,7 +11,6 @@ import ( "github.com/go-errors/errors" "github.com/golang-jwt/jwt/v5" - "github.com/google/uuid" ) const ( @@ -88,7 +87,7 @@ func (a auth) generateJWT(role string) (string, error) { } // GenerateAsymmetricJWT generates a JWT token signed with the provided JWK private key -func GenerateAsymmetricJWT(jwk JWK, claims CustomClaims) (string, error) { +func GenerateAsymmetricJWT(jwk JWK, claims jwt.Claims) (string, error) { privateKey, err := jwkToPrivateKey(jwk) if err != nil { return "", errors.Errorf("failed to convert JWK to private key: %w", err) @@ -105,8 +104,8 @@ func GenerateAsymmetricJWT(jwk JWK, claims CustomClaims) (string, error) { return "", errors.Errorf("unsupported algorithm: %s", jwk.Algorithm) } - if jwk.KeyID != uuid.Nil { - token.Header["kid"] = jwk.KeyID.String() + if len(jwk.KeyID) > 0 { + token.Header["kid"] = jwk.KeyID } tokenString, err := token.SignedString(privateKey) diff --git a/pkg/config/auth.go b/pkg/config/auth.go index b25c7db78b..053c8296c1 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -6,7 +6,6 @@ import ( "time" "github.com/go-errors/errors" - "github.com/google/uuid" "github.com/oapi-codegen/nullable" openapi_types "github.com/oapi-codegen/runtime/types" v1API "github.com/supabase/cli/pkg/api" @@ -87,7 +86,7 @@ func (p *Algorithm) UnmarshalText(text []byte) error { type JWK struct { KeyType string `json:"kty"` - KeyID uuid.UUID `json:"kid,omitempty"` + KeyID string `json:"kid,omitempty"` Use string `json:"use,omitempty"` KeyOps []string `json:"key_ops,omitempty"` Algorithm Algorithm `json:"alg,omitempty"` diff --git a/pkg/config/templates/Dockerfile b/pkg/config/templates/Dockerfile index 5876fc0d4c..1afea64353 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.010 AS pg +FROM supabase/postgres:17.6.1.011 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:v13.0.7 AS postgrest FROM supabase/postgres-meta:v0.91.6 AS pgmeta -FROM supabase/studio:2025.09.23-sha-081a753 AS studio +FROM supabase/studio:2025.10.01-sha-8460121 AS studio FROM darthsim/imgproxy:v3.8.0 AS imgproxy FROM supabase/edge-runtime:v1.69.12 AS edgeruntime FROM timberio/vector:0.28.1-alpine AS vector FROM supabase/supavisor:2.7.0 AS supavisor FROM supabase/gotrue:v2.180.0 AS gotrue -FROM supabase/realtime:v2.51.3 AS realtime -FROM supabase/storage-api:v1.27.6 AS storage -FROM supabase/logflare:1.22.3 AS logflare +FROM supabase/realtime:v2.51.11 AS realtime +FROM supabase/storage-api:v1.28.0 AS storage +FROM supabase/logflare:1.22.4 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/updater.go b/pkg/config/updater.go index d53b43fa80..8915f43fb3 100644 --- a/pkg/config/updater.go +++ b/pkg/config/updater.go @@ -6,7 +6,6 @@ import ( "os" "github.com/go-errors/errors" - "github.com/google/uuid" v1API "github.com/supabase/cli/pkg/api" ) @@ -174,10 +173,10 @@ func (u *ConfigUpdater) UpdateSigningKeys(ctx context.Context, projectRef string } else if resp.JSON200 == nil { return errors.Errorf("unexpected status %d: %s", resp.StatusCode(), string(resp.Body)) } - exists := map[uuid.UUID]struct{}{} + exists := map[string]struct{}{} for _, k := range resp.JSON200.Keys { if k.PublicJwk != nil { - exists[k.Id] = struct{}{} + exists[k.Id.String()] = struct{}{} } } var toInsert []JWK diff --git a/pkg/function/batch.go b/pkg/function/batch.go index a98e0d5ed6..fad4098536 100644 --- a/pkg/function/batch.go +++ b/pkg/function/batch.go @@ -61,7 +61,10 @@ OUTER: } var body bytes.Buffer meta, err := s.eszip.Bundle(ctx, slug, function.Entrypoint, function.ImportMap, function.StaticFiles, &body) - if err != nil { + if errors.Is(err, ErrNoDeploy) { + fmt.Fprintln(os.Stderr, "Skipping undeployable Function:", slug) + continue + } else if err != nil { return err } meta.VerifyJwt = &function.VerifyJWT