From 50706f02f5f719f53939d92fc1aa4c57c7bade60 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 10:46:49 -0600 Subject: [PATCH 1/9] add scope support for client creds. --- cmd/auth/clientCredentials.go | 13 ++++++++ cmd/common/common.go | 1 + docs/man/_index.md | 4 +-- docs/man/auth/client-credentials.md | 9 ++++++ go.mod | 10 +++---- go.sum | 12 ++++---- pkg/auth/auth.go | 46 ++++++++++++++++++++++++----- pkg/profiles/profileAuthCreds.go | 1 + 8 files changed, 75 insertions(+), 21 deletions(-) diff --git a/cmd/auth/clientCredentials.go b/cmd/auth/clientCredentials.go index fda9d834..98012e06 100644 --- a/cmd/auth/clientCredentials.go +++ b/cmd/auth/clientCredentials.go @@ -11,6 +11,8 @@ import ( "github.com/spf13/cobra" ) +var clientCredScopes []string + func clientCredentialsRun(cmd *cobra.Command, args []string) { c := cli.New(cmd, args) cp := common.InitProfile(c) @@ -32,11 +34,16 @@ func clientCredentialsRun(cmd *cobra.Command, args []string) { clientSecret = cli.AskForSecret("Enter client secret: ") } + if !cmd.Flags().Changed("scopes") { + clientCredScopes = cp.GetAuthCredentials().Scopes + } + // Set the client credentials err := cp.SetAuthCredentials(profiles.AuthCredentials{ AuthType: profiles.AuthTypeClientCredentials, ClientID: clientID, ClientSecret: clientSecret, + Scopes: clientCredScopes, }) if err != nil { c.ExitWithError("Failed to set client credentials", err) @@ -56,5 +63,11 @@ func newClientCredentialsCmd() *cobra.Command { man.WithRun(clientCredentialsRun), man.WithHiddenFlags("with-client-creds", "with-client-creds-file"), ) + doc.Flags().StringSliceVar( + &clientCredScopes, + doc.GetDocFlag("scopes").Name, + []string{}, + doc.GetDocFlag("scopes").Description, + ) return &doc.Command } diff --git a/cmd/common/common.go b/cmd/common/common.go index 779c95c3..5bdd315a 100644 --- a/cmd/common/common.go +++ b/cmd/common/common.go @@ -168,6 +168,7 @@ func NewHandler(c *cli.Cli) handlers.Handler { AuthType: profiles.AuthTypeClientCredentials, ClientID: cc.ClientID, ClientSecret: cc.ClientSecret, + Scopes: cc.Scopes, }); err != nil { cli.ExitWithError("Failed to set client credentials", err) } diff --git a/docs/man/_index.md b/docs/man/_index.md index 318cca97..bd72aa8c 100644 --- a/docs/man/_index.md +++ b/docs/man/_index.md @@ -26,9 +26,9 @@ command: - name: with-access-token description: access token for authentication via bearer token - name: with-client-creds-file - description: path to a JSON file containing a 'clientId' and 'clientSecret' for auth via client-credentials flow + description: path to a JSON file containing a 'clientId', 'clientSecret', and optional 'scopes' for auth via client-credentials flow - name: with-client-creds - description: JSON string containing a 'clientId' and 'clientSecret' for auth via client-credentials flow + description: JSON string containing a 'clientId', 'clientSecret', and optional 'scopes' for auth via client-credentials flow default: "" - name: json description: output in JSON format diff --git a/docs/man/auth/client-credentials.md b/docs/man/auth/client-credentials.md index 498afa98..1398b854 100644 --- a/docs/man/auth/client-credentials.md +++ b/docs/man/auth/client-credentials.md @@ -7,6 +7,9 @@ command: - client-id arbitrary_args: - client-secret + flags: + - name: scopes + description: OAuth scopes to request (space-separated). If omitted, existing profile scopes are reused; otherwise the server's default scopes are used. --- > [!NOTE] @@ -40,3 +43,9 @@ Authenticate with client credentials (secret provided as argument) ```shell otdfctl auth client-credentials ``` + +Authenticate with client credentials and explicit scopes + +```shell +otdfctl auth client-credentials --scopes "api:access:read api:access:write" +``` diff --git a/go.mod b/go.mod index 59bf7a38..49a6b99f 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/opentdf/otdfctl -go 1.24.0 - -toolchain go1.24.13 +go 1.25.0 require ( github.com/adrg/frontmatter v0.2.0 @@ -18,9 +16,9 @@ require ( github.com/google/uuid v1.6.0 github.com/jrschumacher/go-osprofiles v0.0.0-20251201220924-3d077c5481e5 github.com/opentdf/platform/lib/flattening v0.1.3 - github.com/opentdf/platform/lib/ocrypto v0.9.0 - github.com/opentdf/platform/protocol/go v0.15.0 - github.com/opentdf/platform/sdk v0.12.0 + github.com/opentdf/platform/lib/ocrypto v0.10.0 + github.com/opentdf/platform/protocol/go v0.16.0 + github.com/opentdf/platform/sdk v0.13.0 github.com/spf13/cobra v1.10.1 github.com/stretchr/testify v1.11.1 github.com/zitadel/oidc/v3 v3.45.1 diff --git a/go.sum b/go.sum index 62d378b2..2d9b1382 100644 --- a/go.sum +++ b/go.sum @@ -238,12 +238,12 @@ github.com/opentdf/platform/lib/fixtures v0.4.0 h1:p3Y5MLJEBaWiSmo+QyRNTirvI8LqY github.com/opentdf/platform/lib/fixtures v0.4.0/go.mod h1:ctyrVn+eTObHAPy3vrdPO0O1mc3vgQ6lc9pBTdhBAfo= github.com/opentdf/platform/lib/flattening v0.1.3 h1:IuOm/wJVXNrzOV676Ticgr0wyBkL+lVjsoSfh+WSkNo= github.com/opentdf/platform/lib/flattening v0.1.3/go.mod h1:Gs/T+6FGZKk9OAdz2Jf1R8CTGeNRYrq1lZGDeYT3hrY= -github.com/opentdf/platform/lib/ocrypto v0.9.0 h1:ZEJRFLR549unvP6aMWt2j3HT29wqBBhO9P7uudho6Ho= -github.com/opentdf/platform/lib/ocrypto v0.9.0/go.mod h1:/TtiJldbP/LO1cvX8bwhnd7SVHSUImBt1EfjG9qEo78= -github.com/opentdf/platform/protocol/go v0.15.0 h1:7m1iBCxklQy/inIonmGJnhfjkr4ZFLXVt1dL5aiO+sY= -github.com/opentdf/platform/protocol/go v0.15.0/go.mod h1:m6hTbcBrtp2jRhsAstLvPSAnm8v055fUppveG3iI6tw= -github.com/opentdf/platform/sdk v0.12.0 h1:5LkVf5Ktjt5tsc5YBxloJUYNHJ9pE5IMqjswZwBwrRE= -github.com/opentdf/platform/sdk v0.12.0/go.mod h1:jLXYHV3Am2Fq5RSaCLUDLVocwA9iO7mJkGTUqX/HOr8= +github.com/opentdf/platform/lib/ocrypto v0.10.0 h1:7dn/z/1qH3p+gWCrfOoU7hj9XF/p5N+b2JBJuWF9aK0= +github.com/opentdf/platform/lib/ocrypto v0.10.0/go.mod h1:WASkoHreqgTFImB/gJW42VTdpi9AkgkmaW19y/fU+Ew= +github.com/opentdf/platform/protocol/go v0.16.0 h1:/EumdEZY7ujYyQF+EzCBCtIMiJPvGIZpgFgg8bORiFk= +github.com/opentdf/platform/protocol/go v0.16.0/go.mod h1:4lsBu86yrOWdhqIko8/x5ndamOtM8iDNZYBguF9ZiQQ= +github.com/opentdf/platform/sdk v0.13.0 h1:jhhCLE1Y57Y20g6TUEg/zqCfid8m4KPfbaaBlg6oAsM= +github.com/opentdf/platform/sdk v0.13.0/go.mod h1:x80F65+dGzxDTq8iqVIbCrbDDB9oYCYGjh4duoH6biM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 48a694fe..8a958cea 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -28,8 +28,9 @@ import ( const authCallbackPath = "/callback" type ClientCredentials struct { - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + Scopes []string `json:"scopes,omitempty"` } type platformConfiguration struct { @@ -48,6 +49,31 @@ type JWTClaims struct { Expiration int64 `json:"exp"` } +func normalizeScopes(scopes []string) []string { + if len(scopes) == 0 { + return nil + } + normalized := make([]string, 0, len(scopes)) + for _, scope := range scopes { + for _, part := range strings.Fields(scope) { + if part != "" { + normalized = append(normalized, part) + } + } + } + if len(normalized) == 0 { + return nil + } + return normalized +} + +func normalizeClientCredScopes(creds *ClientCredentials) { + if creds == nil { + return + } + creds.Scopes = normalizeScopes(creds.Scopes) +} + // Retrieves credentials by reading specified file func GetClientCredsFromFile(filepath string) (ClientCredentials, error) { creds := ClientCredentials{} @@ -60,6 +86,7 @@ func GetClientCredsFromFile(filepath string) (ClientCredentials, error) { if err := json.NewDecoder(f).Decode(&creds); err != nil { return creds, errors.Join(errors.New("failed to decode creds file"), err) } + normalizeClientCredScopes(&creds) return creds, nil } @@ -70,6 +97,7 @@ func GetClientCredsFromJSON(credsJSON []byte) (ClientCredentials, error) { if err := json.Unmarshal(credsJSON, &creds); err != nil { return creds, errors.Join(errors.New("failed to decode creds JSON"), err) } + normalizeClientCredScopes(&creds) return creds, nil } @@ -144,7 +172,7 @@ func GetSDKAuthOptionFromProfile(profile *profiles.OtdfctlProfileStore) (sdk.Opt switch c.AuthType { case profiles.AuthTypeClientCredentials: - return sdk.WithClientCredentials(c.ClientID, c.ClientSecret, nil), nil + return sdk.WithClientCredentials(c.ClientID, c.ClientSecret, normalizeScopes(c.Scopes)), nil case profiles.AuthTypeAccessToken: tokenSource := oauth2.StaticTokenSource(buildToken(&c)) return sdk.WithOAuthAccessTokenSource(tokenSource), nil @@ -160,7 +188,7 @@ func ValidateProfileAuthCredentials(ctx context.Context, profile *profiles.Otdfc case "": return ErrProfileCredentialsNotFound case profiles.AuthTypeClientCredentials: - _, err := GetTokenWithClientCreds(ctx, profile.GetEndpoint(), c.ClientID, c.ClientSecret, profile.GetTLSNoVerify()) + _, err := GetTokenWithClientCreds(ctx, profile.GetEndpoint(), c.ClientID, c.ClientSecret, profile.GetTLSNoVerify(), c.Scopes) if err != nil { return err } @@ -180,7 +208,7 @@ func GetTokenWithProfile(ctx context.Context, profile *profiles.OtdfctlProfileSt switch c.AuthType { case profiles.AuthTypeClientCredentials: - return GetTokenWithClientCreds(ctx, profile.GetEndpoint(), c.ClientID, c.ClientSecret, profile.GetTLSNoVerify()) + return GetTokenWithClientCreds(ctx, profile.GetEndpoint(), c.ClientID, c.ClientSecret, profile.GetTLSNoVerify(), c.Scopes) case profiles.AuthTypeAccessToken: return buildToken(&c), nil default: @@ -189,7 +217,7 @@ func GetTokenWithProfile(ctx context.Context, profile *profiles.OtdfctlProfileSt } // Uses the OAuth2 client credentials flow to obtain a token. -func GetTokenWithClientCreds(ctx context.Context, endpoint string, clientID string, clientSecret string, tlsNoVerify bool) (*oauth2.Token, error) { +func GetTokenWithClientCreds(ctx context.Context, endpoint string, clientID string, clientSecret string, tlsNoVerify bool, scopes []string) (*oauth2.Token, error) { rp, err := newOidcRelyingParty(ctx, endpoint, tlsNoVerify, oidcClientCredentials{ clientID: clientID, clientSecret: clientSecret, @@ -197,7 +225,11 @@ func GetTokenWithClientCreds(ctx context.Context, endpoint string, clientID stri if err != nil { return nil, err } - return oidcrp.ClientCredentials(ctx, rp, url.Values{}) + params := url.Values{} + if normalized := normalizeScopes(scopes); len(normalized) > 0 { + params.Set("scope", strings.Join(normalized, " ")) + } + return oidcrp.ClientCredentials(ctx, rp, params) } const ( diff --git a/pkg/profiles/profileAuthCreds.go b/pkg/profiles/profileAuthCreds.go index fd795f01..10db5b98 100644 --- a/pkg/profiles/profileAuthCreds.go +++ b/pkg/profiles/profileAuthCreds.go @@ -10,6 +10,7 @@ type AuthCredentials struct { ClientID string `json:"clientId"` // Used for client credentials ClientSecret string `json:"clientSecret,omitempty"` + Scopes []string `json:"scopes,omitempty"` AccessToken AuthCredentialsAccessToken `json:"accessToken,omitempty"` } From 321094b2847414b73302c686eb2457bf6e9a60e8 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 10:57:13 -0600 Subject: [PATCH 2/9] bump to latest golangci-lint. --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7e33abcd..cf21baf0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: - version: v2.1.0 + version: v2.10.1 # Optional: golangci-lint command line arguments. args: --timeout=10m only-new-issues: true From dc00d88fe21f7f074aca7989a8b955fd7a015e7b Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 11:12:39 -0600 Subject: [PATCH 3/9] gemini comments. --- cmd/auth/clientCredentials.go | 21 +++++++++++++-------- pkg/auth/auth.go | 6 +----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cmd/auth/clientCredentials.go b/cmd/auth/clientCredentials.go index 98012e06..fdc7a052 100644 --- a/cmd/auth/clientCredentials.go +++ b/cmd/auth/clientCredentials.go @@ -2,6 +2,7 @@ package auth import ( "fmt" + "strings" "github.com/opentdf/otdfctl/cmd/common" "github.com/opentdf/otdfctl/pkg/auth" @@ -11,8 +12,6 @@ import ( "github.com/spf13/cobra" ) -var clientCredScopes []string - func clientCredentialsRun(cmd *cobra.Command, args []string) { c := cli.New(cmd, args) cp := common.InitProfile(c) @@ -33,9 +32,16 @@ func clientCredentialsRun(cmd *cobra.Command, args []string) { if clientSecret == "" { clientSecret = cli.AskForSecret("Enter client secret: ") } - - if !cmd.Flags().Changed("scopes") { - clientCredScopes = cp.GetAuthCredentials().Scopes + var scopes []string + if cmd.Flags().Changed("scopes") { + flagScopes, err := cmd.Flags().GetStringSlice("scopes") + if err != nil { + c.ExitWithError("Failed to read scopes flag", err) + } + scopes = make([]string, 0, len(flagScopes)) + for _, scope := range flagScopes { + scopes = append(scopes, strings.TrimSpace(scope)) + } } // Set the client credentials @@ -43,7 +49,7 @@ func clientCredentialsRun(cmd *cobra.Command, args []string) { AuthType: profiles.AuthTypeClientCredentials, ClientID: clientID, ClientSecret: clientSecret, - Scopes: clientCredScopes, + Scopes: scopes, }) if err != nil { c.ExitWithError("Failed to set client credentials", err) @@ -63,8 +69,7 @@ func newClientCredentialsCmd() *cobra.Command { man.WithRun(clientCredentialsRun), man.WithHiddenFlags("with-client-creds", "with-client-creds-file"), ) - doc.Flags().StringSliceVar( - &clientCredScopes, + doc.Flags().StringSlice( doc.GetDocFlag("scopes").Name, []string{}, doc.GetDocFlag("scopes").Description, diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 8a958cea..f5a04489 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -55,11 +55,7 @@ func normalizeScopes(scopes []string) []string { } normalized := make([]string, 0, len(scopes)) for _, scope := range scopes { - for _, part := range strings.Fields(scope) { - if part != "" { - normalized = append(normalized, part) - } - } + normalized = append(normalized, strings.Fields(scope)...) } if len(normalized) == 0 { return nil From c4a4d9e7396d675a6edd321504380f0e80ac91bc Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 11:21:03 -0600 Subject: [PATCH 4/9] fetch depth 0. --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cf21baf0..28266de0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,6 +26,9 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version-file: "go.mod" From fdb21d0c322dd800a8355f2a5652b60f1fadd5a0 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 11:27:55 -0600 Subject: [PATCH 5/9] suggestions. --- go.mod | 2 +- go.sum | 5 +++-- pkg/auth/auth.go | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 27e794c9..803191f9 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/opentdf/platform/lib/ocrypto v0.10.0 github.com/opentdf/platform/protocol/go v0.16.0 github.com/opentdf/platform/sdk v0.13.0 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 github.com/zitadel/oidc/v3 v3.45.1 golang.org/x/oauth2 v0.35.0 diff --git a/go.sum b/go.sum index 2b569a1c..4854bca3 100644 --- a/go.sum +++ b/go.sum @@ -272,8 +272,8 @@ github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dI github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -330,6 +330,7 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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= diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index f5a04489..5e194446 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -49,7 +49,7 @@ type JWTClaims struct { Expiration int64 `json:"exp"` } -func normalizeScopes(scopes []string) []string { +func NormalizeScopes(scopes []string) []string { if len(scopes) == 0 { return nil } @@ -67,7 +67,7 @@ func normalizeClientCredScopes(creds *ClientCredentials) { if creds == nil { return } - creds.Scopes = normalizeScopes(creds.Scopes) + creds.Scopes = NormalizeScopes(creds.Scopes) } // Retrieves credentials by reading specified file @@ -168,7 +168,7 @@ func GetSDKAuthOptionFromProfile(profile *profiles.OtdfctlProfileStore) (sdk.Opt switch c.AuthType { case profiles.AuthTypeClientCredentials: - return sdk.WithClientCredentials(c.ClientID, c.ClientSecret, normalizeScopes(c.Scopes)), nil + return sdk.WithClientCredentials(c.ClientID, c.ClientSecret, NormalizeScopes(c.Scopes)), nil case profiles.AuthTypeAccessToken: tokenSource := oauth2.StaticTokenSource(buildToken(&c)) return sdk.WithOAuthAccessTokenSource(tokenSource), nil @@ -222,7 +222,7 @@ func GetTokenWithClientCreds(ctx context.Context, endpoint string, clientID stri return nil, err } params := url.Values{} - if normalized := normalizeScopes(scopes); len(normalized) > 0 { + if normalized := NormalizeScopes(scopes); len(normalized) > 0 { params.Set("scope", strings.Join(normalized, " ")) } return oidcrp.ClientCredentials(ctx, rp, params) From d8a21122bbce228b1f555027742f276a628db2c7 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 11:32:58 -0600 Subject: [PATCH 6/9] ignore lint. --- pkg/auth/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 5e194446..ee9c0b1d 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -29,7 +29,7 @@ const authCallbackPath = "/callback" type ClientCredentials struct { ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` + ClientSecret string `json:"clientSecret"` //nolint:gosec // not a hard-coded secret; populated at runtime Scopes []string `json:"scopes,omitempty"` } From 98f4f3f8f606664ad544c8f4f8a9541b443bad3e Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 11:39:46 -0600 Subject: [PATCH 7/9] toolchain to 1.25.7 --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 803191f9..fdfb8578 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/opentdf/otdfctl go 1.25.0 +toolchain go1.25.7 + require ( github.com/adrg/frontmatter v0.2.0 github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 From 5fc5622956990181b9d2b19204ae6212dbcce329 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Thu, 19 Feb 2026 11:41:44 -0600 Subject: [PATCH 8/9] change docs. --- docs/man/auth/client-credentials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/auth/client-credentials.md b/docs/man/auth/client-credentials.md index 1398b854..f440d43a 100644 --- a/docs/man/auth/client-credentials.md +++ b/docs/man/auth/client-credentials.md @@ -9,7 +9,7 @@ command: - client-secret flags: - name: scopes - description: OAuth scopes to request (space-separated). If omitted, existing profile scopes are reused; otherwise the server's default scopes are used. + description: OAuth scopes to request (space-separated). --- > [!NOTE] From 3c9e1ed3ae3051ed124e121c5b52ec58db2c2f3a Mon Sep 17 00:00:00 2001 From: Chris Reed <87077975+c-r33d@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:58:30 -0600 Subject: [PATCH 9/9] Update docs/man/auth/client-credentials.md Co-authored-by: Jake Van Vorhis <83739412+jakedoublev@users.noreply.github.com> --- docs/man/auth/client-credentials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/auth/client-credentials.md b/docs/man/auth/client-credentials.md index f440d43a..a2a95270 100644 --- a/docs/man/auth/client-credentials.md +++ b/docs/man/auth/client-credentials.md @@ -9,7 +9,7 @@ command: - client-secret flags: - name: scopes - description: OAuth scopes to request (space-separated). + description: OIDC scopes to request (space-separated). --- > [!NOTE]