← back to Cli Printing Press
fix(cli): kind-aware auth env-vars and promoted-command --limit (#645)
0cf04f73a03c16f87ca3c504bce7cceb3d8ed0e3 · 2026-05-05 23:11:12 -0700 · Trevin Chow
* fix(cli): stop treating OAuth setup inputs as request credentials
The generator treated all auth env vars equally, so Client ID and
Client Secret (auth_flow_input) and harvested session cookies flowed
into bearer-header fallbacks, the verifier's --api-key fan-out, and
auth-status hint output as if they were per-call credentials.
Add IsRequestCredential() / EffectiveKind() on AuthEnvVar and thread
the predicate through:
- spec: CanonicalEnvVar, IsAuthEnvVarORCase, independentAuthORGroupsExample
- pipeline/runtime: --api-key fan-out and mock-token injection use only
request-credential names; summarizeAuthEnvVars classifies by predicate
- templates/config.go.tmpl: drop the canonical-env-var-as-Bearer fallback
under client_credentials (Client ID is not a usable bearer)
- templates/auth_simple.go.tmpl: status hints suggest only request creds
- templates/client.go.tmpl: under verify mode, plant a mock access token
before attempting to mint via the unreachable token endpoint
Goldens regenerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): apply --limit truncation to promoted commands
Promoted commands lacked the truncateJSONArray block that endpoint
commands already had, so calling the typed subcommand `app endpoint-list
--limit 10` truncated client-side, but the promoted alias `app list
--limit 10` returned every row.
Add the same block in command_promoted.go.tmpl. Shrink the verbose
explanatory comment in command_endpoint.go.tmpl to one line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M internal/generator/auth_env_precedence_test.goM internal/generator/generator.goM internal/generator/limit_truncation_test.goM internal/generator/templates/auth_simple.go.tmplM internal/generator/templates/client.go.tmplM internal/generator/templates/command_endpoint.go.tmplM internal/generator/templates/command_promoted.go.tmplM internal/generator/templates/config.go.tmplM internal/generator/templates_test.goM internal/pipeline/runtime.goM internal/pipeline/runtime_test.goM internal/spec/spec.goM testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.goM testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/config/config.goM testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/auth.go
Diff
commit 0cf04f73a03c16f87ca3c504bce7cceb3d8ed0e3
Author: Trevin Chow <trevin@trevinchow.com>
Date: Tue May 5 23:11:12 2026 -0700
fix(cli): kind-aware auth env-vars and promoted-command --limit (#645)
* fix(cli): stop treating OAuth setup inputs as request credentials
The generator treated all auth env vars equally, so Client ID and
Client Secret (auth_flow_input) and harvested session cookies flowed
into bearer-header fallbacks, the verifier's --api-key fan-out, and
auth-status hint output as if they were per-call credentials.
Add IsRequestCredential() / EffectiveKind() on AuthEnvVar and thread
the predicate through:
- spec: CanonicalEnvVar, IsAuthEnvVarORCase, independentAuthORGroupsExample
- pipeline/runtime: --api-key fan-out and mock-token injection use only
request-credential names; summarizeAuthEnvVars classifies by predicate
- templates/config.go.tmpl: drop the canonical-env-var-as-Bearer fallback
under client_credentials (Client ID is not a usable bearer)
- templates/auth_simple.go.tmpl: status hints suggest only request creds
- templates/client.go.tmpl: under verify mode, plant a mock access token
before attempting to mint via the unreachable token endpoint
Goldens regenerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): apply --limit truncation to promoted commands
Promoted commands lacked the truncateJSONArray block that endpoint
commands already had, so calling the typed subcommand `app endpoint-list
--limit 10` truncated client-side, but the promoted alias `app list
--limit 10` returned every row.
Add the same block in command_promoted.go.tmpl. Shrink the verbose
explanatory comment in command_endpoint.go.tmpl to one line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
internal/generator/auth_env_precedence_test.go | 37 ++++++++++++-------
internal/generator/generator.go | 5 +++
internal/generator/limit_truncation_test.go | 40 ++++++++++++++++++++
internal/generator/templates/auth_simple.go.tmpl | 8 ++++
internal/generator/templates/client.go.tmpl | 4 ++
.../generator/templates/command_endpoint.go.tmpl | 7 +---
.../generator/templates/command_promoted.go.tmpl | 4 ++
internal/generator/templates/config.go.tmpl | 14 -------
internal/generator/templates_test.go | 36 ++++++++++++++++++
internal/pipeline/runtime.go | 43 ++++++++++++++--------
internal/pipeline/runtime_test.go | 20 +++++++++-
internal/spec/spec.go | 19 ++++++++--
.../internal/client/client.go | 4 ++
.../internal/config/config.go | 4 --
.../printing-press-rich-auth/internal/cli/auth.go | 3 --
15 files changed, 187 insertions(+), 61 deletions(-)
diff --git a/internal/generator/auth_env_precedence_test.go b/internal/generator/auth_env_precedence_test.go
index 381fba78..2b8921d6 100644
--- a/internal/generator/auth_env_precedence_test.go
+++ b/internal/generator/auth_env_precedence_test.go
@@ -11,17 +11,20 @@ import (
"github.com/stretchr/testify/require"
)
-// TestAuthHeader_ClientCredentialsAccessTokenWinsOverEnv pins that under
-// OAuth2 client_credentials the cached AccessToken wins over the env-var
-// fallback. The env var is the Client ID, not a usable bearer JWT.
-func TestAuthHeader_ClientCredentialsAccessTokenWinsOverEnv(t *testing.T) {
+// TestAuthHeader_ClientCredentialsDoesNotUseSetupEnvVars pins that under
+// OAuth2 client_credentials the setup inputs are never emitted as bearer
+// headers. Only a minted AccessToken is usable for API requests.
+func TestAuthHeader_ClientCredentialsDoesNotUseSetupEnvVars(t *testing.T) {
t.Parallel()
apiSpec := minimalSpec("cc-precedence")
apiSpec.Auth = spec.AuthConfig{
- Type: "bearer_token",
- Header: "Authorization",
- EnvVars: []string{"CC_AUTH_TEST_CLIENT_ID"},
+ Type: "bearer_token",
+ Header: "Authorization",
+ EnvVarSpecs: []spec.AuthEnvVar{
+ {Name: "CC_AUTH_TEST_CLIENT_ID", Kind: spec.AuthEnvVarKindAuthFlowInput, Required: false, Sensitive: false},
+ {Name: "CC_AUTH_TEST_CLIENT_SECRET", Kind: spec.AuthEnvVarKindAuthFlowInput, Required: false, Sensitive: true},
+ },
OAuth2Grant: spec.OAuth2GrantClientCredentials,
TokenURL: "https://example.com/token",
}
@@ -34,18 +37,24 @@ func TestAuthHeader_ClientCredentialsAccessTokenWinsOverEnv(t *testing.T) {
content := string(cfgSrc)
envCheck := "if c." + resolveEnvVarField("CC_AUTH_TEST_CLIENT_ID") + ` != ""`
+ envSecretCheck := "if c." + resolveEnvVarField("CC_AUTH_TEST_CLIENT_SECRET") + ` != ""`
tokenCheck := `if c.AccessToken != ""`
- require.Contains(t, content, envCheck, "AuthHeader must keep the env-var fallback")
require.Contains(t, content, tokenCheck, "AuthHeader must check AccessToken")
body := authHeaderBody(t, content)
- envIdx := strings.Index(body, envCheck)
- tokenIdx := strings.Index(body, tokenCheck)
- require.NotEqual(t, -1, envIdx)
- require.NotEqual(t, -1, tokenIdx)
- assert.Less(t, tokenIdx, envIdx,
- "AccessToken check must appear BEFORE env-var fallback under OAuth2 client_credentials")
+ require.Contains(t, body, tokenCheck)
+ require.NotContains(t, body, envCheck, "client ID must not be used as a bearer token")
+ require.NotContains(t, body, envSecretCheck, "client secret must not be used as a bearer token")
+
+ clientSrc, err := os.ReadFile(filepath.Join(outputDir, "internal", "client", "client.go"))
+ require.NoError(t, err)
+ clientContent := string(clientSrc)
+ verifyIdx := strings.Index(clientContent, `cliutil.IsVerifyEnv()`)
+ mintIdx := strings.Index(clientContent, `c.mintClientCredentials(clientID, clientSecret)`)
+ require.NotEqual(t, -1, verifyIdx, "mock verification should short-circuit before token minting")
+ require.NotEqual(t, -1, mintIdx, "client_credentials mint path should still be emitted")
+ assert.Less(t, verifyIdx, mintIdx, "mock verification must not dial the real token endpoint")
}
func TestAuthHeader_OAuth2AuthorizationCodeUsesToken(t *testing.T) {
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index ada9d804..3fa5ebf8 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -215,6 +215,7 @@ func New(s *spec.APISpec, outputDir string) *Generator {
"authCommandShort": authCommandShort,
"authHarvestedEnvHint": authHarvestedEnvHint,
"hasAuthEnvVarKind": hasAuthEnvVarKind,
+ "isRequestAuthEnvVar": isRequestAuthEnvVar,
"effectiveTier": effectiveTier,
"effectiveSubTier": effectiveSubTier,
"add": func(a, b int) int { return a + b },
@@ -834,6 +835,10 @@ func hasAuthEnvVarKind(envVarSpecs []spec.AuthEnvVar, kind string) bool {
return false
}
+func isRequestAuthEnvVar(envVar spec.AuthEnvVar) bool {
+ return envVar.IsRequestCredential()
+}
+
func effectiveTier(api *spec.APISpec, resource spec.Resource, endpoint spec.Endpoint) string {
if api == nil {
return ""
diff --git a/internal/generator/limit_truncation_test.go b/internal/generator/limit_truncation_test.go
index 4b1ea04d..220d7dac 100644
--- a/internal/generator/limit_truncation_test.go
+++ b/internal/generator/limit_truncation_test.go
@@ -1,10 +1,13 @@
package generator
import (
+ "os"
+ "path/filepath"
"testing"
"github.com/mvanhorn/cli-printing-press/v3/internal/spec"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
// TestEndpointNeedsClientLimit verifies the gate that controls when
@@ -96,3 +99,40 @@ func TestEndpointNeedsClientLimit(t *testing.T) {
})
}
}
+
+func TestClientLimitGenerationEmitsHelperAndBuilds(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("limit-truncate")
+ apiSpec.Resources = map[string]spec.Resource{
+ "orders": {
+ Description: "Manage orders",
+ Endpoints: map[string]spec.Endpoint{
+ "list": {
+ Method: "GET",
+ Path: "/orders",
+ Description: "List orders",
+ Params: []spec.Param{{
+ Name: "limit",
+ Type: "integer",
+ Default: 5,
+ }},
+ Response: spec.ResponseDef{Type: "array", Item: "Order"},
+ },
+ },
+ },
+ }
+
+ outputDir := filepath.Join(t.TempDir(), "limit-truncate-pp-cli")
+ require.NoError(t, New(apiSpec, outputDir).Generate())
+
+ helpersSrc, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "helpers.go"))
+ require.NoError(t, err)
+ require.Contains(t, string(helpersSrc), "func truncateJSONArray(")
+
+ commandSrc := readGeneratedFile(t, outputDir, "internal", "cli", "promoted_orders.go")
+ require.Contains(t, commandSrc, "truncateJSONArray(data,",
+ "generated command should call truncateJSONArray for non-paginated limit endpoint")
+
+ runGoCommand(t, outputDir, "build", "./internal/cli")
+}
diff --git a/internal/generator/templates/auth_simple.go.tmpl b/internal/generator/templates/auth_simple.go.tmpl
index c415e494..73d923ed 100644
--- a/internal/generator/templates/auth_simple.go.tmpl
+++ b/internal/generator/templates/auth_simple.go.tmpl
@@ -61,8 +61,16 @@ func newAuthStatusCmd(flags *rootFlags) *cobra.Command {
fmt.Fprintln(w, red("Not authenticated"))
fmt.Fprintln(w, "")
fmt.Fprintln(w, "Set your token:")
+{{- if .Auth.EnvVarSpecs}}
+{{- range .Auth.EnvVarSpecs}}
+{{- if isRequestAuthEnvVar .}}
+ fmt.Fprintln(w, " export {{.Name}}=\"your-token-here\"")
+{{- end}}
+{{- end}}
+{{- else}}
{{- range .Auth.EnvVars}}
fmt.Fprintln(w, " export {{.}}=\"your-token-here\"")
+{{- end}}
{{- end}}
fmt.Fprintf(w, " {{.Name}}-pp-cli auth set-token <token>\n")
return authErr(fmt.Errorf("no credentials configured"))
diff --git a/internal/generator/templates/client.go.tmpl b/internal/generator/templates/client.go.tmpl
index 80c5abb7..d134ab75 100644
--- a/internal/generator/templates/client.go.tmpl
+++ b/internal/generator/templates/client.go.tmpl
@@ -997,6 +997,10 @@ func (c *Client) authHeader() (string, error) {
return "", nil
}
{{- if eq .Auth.EffectiveOAuth2Grant "client_credentials"}}
+ if c.Config.AccessToken == "" && cliutil.IsVerifyEnv() {
+ c.Config.AccessToken = "mock-token-for-testing"
+ return c.Config.AuthHeader(), nil
+ }
// 60s window avoids in-flight requests racing the expiry boundary.
// Double-checked lock so only one goroutine mints under contention.
if needsClientCredentialsMint(c.Config) {
diff --git a/internal/generator/templates/command_endpoint.go.tmpl b/internal/generator/templates/command_endpoint.go.tmpl
index 3dce55e0..61ec96f6 100644
--- a/internal/generator/templates/command_endpoint.go.tmpl
+++ b/internal/generator/templates/command_endpoint.go.tmpl
@@ -328,12 +328,7 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
}
{{- end}}
{{- if endpointNeedsClientLimit .Endpoint}}
- // The API doesn't declare a paginator but accepts a limit
- // query param. Some APIs (Firebase, file-backed JSON dumps,
- // RSS feeds) silently ignore ?limit=N and return the full
- // collection — truncate client-side so --limit N is
- // honored regardless. Idempotent when the API already
- // returned <= N items.
+ // Honor --limit when the API accepts but ignores ?limit=N.
data = truncateJSONArray(data, flagLimit)
{{- end}}
diff --git a/internal/generator/templates/command_promoted.go.tmpl b/internal/generator/templates/command_promoted.go.tmpl
index 9cc6a463..d362fd3c 100644
--- a/internal/generator/templates/command_promoted.go.tmpl
+++ b/internal/generator/templates/command_promoted.go.tmpl
@@ -193,6 +193,10 @@ func new{{camel .PromotedName}}PromotedCmd(flags *rootFlags) *cobra.Command {
if err != nil {
return classifyAPIError(err, flags)
}
+{{- if endpointNeedsClientLimit .Endpoint}}
+ // Honor --limit when the API accepts but ignores ?limit=N.
+ data = truncateJSONArray(data, flagLimit)
+{{- end}}
{{- if .Endpoint.UsesHTMLResponse}}
if !flags.dryRun {
diff --git a/internal/generator/templates/config.go.tmpl b/internal/generator/templates/config.go.tmpl
index e990394b..8bc7f9ea 100644
--- a/internal/generator/templates/config.go.tmpl
+++ b/internal/generator/templates/config.go.tmpl
@@ -229,20 +229,6 @@ func (c *Config) AuthHeader() string {
return "Bearer " + c.AccessToken
{{- end}}
}
- {{- with $canonicalEnvVar}}
- if c.{{resolveEnvVarField .Name}} != "" {
- c.AuthSource = "env:{{.Name}}"
- {{- if $.Auth.Format}}
- return applyAuthFormat("{{$.Auth.Format}}", map[string]string{
- "{{envVarPlaceholder .Name}}": c.{{resolveEnvVarField .Name}},
- "{{.Name}}": c.{{resolveEnvVarField .Name}},
- "token": c.{{resolveEnvVarField .Name}},
- })
- {{- else}}
- return "Bearer " + c.{{resolveEnvVarField .Name}}
- {{- end}}
- }
- {{- end}}
return ""
{{- else}}
{{- if .BearerRefresh.Enabled}}
diff --git a/internal/generator/templates_test.go b/internal/generator/templates_test.go
index fbce37b3..b90e63b5 100644
--- a/internal/generator/templates_test.go
+++ b/internal/generator/templates_test.go
@@ -43,6 +43,42 @@ func TestDoctorTemplateRendersKindAwareAuthEnvPresence(t *testing.T) {
require.Contains(t, content, `report["env_vars"] = "INFO set one of: " + strings.Join(authEnvOptionalNames, " or ")`)
}
+func TestAuthStatusHintsOnlyRequestCredentialEnvVars(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("auth-status-rich-auth")
+ apiSpec.Auth = spec.AuthConfig{
+ Type: "api_key",
+ EnvVarSpecs: []spec.AuthEnvVar{
+ {Name: "STATUS_AUTH_TOKEN", Kind: spec.AuthEnvVarKindPerCall, Required: true, Sensitive: true},
+ {Name: "STATUS_AUTH_CLIENT_ID", Kind: spec.AuthEnvVarKindAuthFlowInput, Required: false, Sensitive: false},
+ {Name: "STATUS_AUTH_CLIENT_SECRET", Kind: spec.AuthEnvVarKindAuthFlowInput, Required: false, Sensitive: true},
+ {Name: "STATUS_AUTH_SESSION_COOKIE", Kind: spec.AuthEnvVarKindHarvested, Required: false, Sensitive: true},
+ {Name: "STATUS_AUTH_OPTIONAL_TOKEN", Kind: spec.AuthEnvVarKindPerCall, Required: false, Sensitive: true},
+ },
+ }
+
+ outputDir := filepath.Join(t.TempDir(), "auth-status-rich-auth-pp-cli")
+ require.NoError(t, New(apiSpec, outputDir).Generate())
+
+ authSrc, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "auth.go"))
+ require.NoError(t, err)
+ content := string(authSrc)
+
+ start := strings.Index(content, `fmt.Fprintln(w, "Set your token:")`)
+ require.NotEqual(t, -1, start, "auth status hint block should be emitted:\n%s", content)
+ hintBlock := content[start:]
+ end := strings.Index(hintBlock, `auth set-token <token>`)
+ require.NotEqual(t, -1, end, "auth set-token fallback should terminate status hint block:\n%s", hintBlock)
+ hintBlock = hintBlock[:end]
+
+ require.Contains(t, hintBlock, `export STATUS_AUTH_TOKEN=\"your-token-here\"`)
+ require.Contains(t, hintBlock, `export STATUS_AUTH_OPTIONAL_TOKEN=\"your-token-here\"`)
+ require.NotContains(t, hintBlock, `STATUS_AUTH_CLIENT_ID`)
+ require.NotContains(t, hintBlock, `STATUS_AUTH_CLIENT_SECRET`)
+ require.NotContains(t, hintBlock, `STATUS_AUTH_SESSION_COOKIE`)
+}
+
func TestMCPContextOmitsHarvestedAuthEnvVars(t *testing.T) {
t.Parallel()
diff --git a/internal/pipeline/runtime.go b/internal/pipeline/runtime.go
index 165606db..a4284982 100644
--- a/internal/pipeline/runtime.go
+++ b/internal/pipeline/runtime.go
@@ -200,6 +200,7 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
}
}
authEnvVars := authEnvVarSpecNames(authEnvVarSpecs)
+ requestAuthEnvVars := requestAuthEnvVarNames(authEnvVarSpecs)
authEnvVarStatuses := summarizeAuthEnvVars(authEnvVarSpecs, cfg.APIKey, cfg.EnvVar, report.Mode)
if shouldReportAuthEnvVarStatuses(authEnvVarStatuses) {
report.AuthEnvVars = authEnvVarStatuses
@@ -231,16 +232,17 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
env = append(env, ev+"="+val)
}
}
- // Also pass the explicit --api-key under ALL auth env var names so the
- // generated CLI finds it regardless of which env var it reads.
+ // Also pass the explicit --api-key under request credential env var
+ // names so the generated CLI finds it without rewriting setup-only
+ // OAuth inputs such as client IDs and client secrets.
if cfg.APIKey != "" {
- for _, ev := range authEnvVars {
+ for _, ev := range requestAuthEnvVars {
env = append(env, ev+"="+cfg.APIKey)
}
}
} else {
env = append(env, baseURLEnvVar+"="+baseURLOverride)
- for _, ev := range authEnvVars {
+ for _, ev := range requestAuthEnvVars {
env = append(env, ev+"=mock-token-for-testing")
}
// Templated URLs (e.g. /admin/api/{api_version}/graphql.json)
@@ -305,9 +307,24 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
}
func authEnvVarSpecNames(envVarSpecs []apispec.AuthEnvVar) []string {
+ return authEnvVarNamesMatching(envVarSpecs, func(apispec.AuthEnvVar) bool {
+ return true
+ })
+}
+
+func requestAuthEnvVarNames(envVarSpecs []apispec.AuthEnvVar) []string {
+ return authEnvVarNamesMatching(envVarSpecs, func(envVar apispec.AuthEnvVar) bool {
+ return envVar.IsRequestCredential()
+ })
+}
+
+func authEnvVarNamesMatching(envVarSpecs []apispec.AuthEnvVar, matches func(apispec.AuthEnvVar) bool) []string {
names := make([]string, 0, len(envVarSpecs))
seen := make(map[string]struct{}, len(envVarSpecs))
for _, envVar := range envVarSpecs {
+ if !matches(envVar) {
+ continue
+ }
name := strings.TrimSpace(envVar.Name)
if name == "" {
continue
@@ -326,15 +343,14 @@ func summarizeAuthEnvVars(envVarSpecs []apispec.AuthEnvVar, apiKey, apiKeyEnvVar
if apiKey != "" && apiKeyEnvVar == "" {
for _, envVar := range envVarSpecs {
name := strings.TrimSpace(envVar.Name)
- if name == "" {
+ if name == "" || !envVar.IsRequestCredential() {
continue
}
- apiKeyEnvVar = name
- kind := envVar.Kind
- if kind == "" {
- kind = apispec.AuthEnvVarKindPerCall
+ if apiKeyEnvVar == "" {
+ apiKeyEnvVar = name
}
- if kind == apispec.AuthEnvVarKindPerCall && envVar.Required {
+ if envVar.Required {
+ apiKeyEnvVar = name
break
}
}
@@ -344,10 +360,7 @@ func summarizeAuthEnvVars(envVarSpecs []apispec.AuthEnvVar, apiKey, apiKeyEnvVar
if name == "" {
continue
}
- kind := envVar.Kind
- if kind == "" {
- kind = apispec.AuthEnvVarKindPerCall
- }
+ kind := envVar.EffectiveKind()
status := AuthEnvVarStatus{
Name: name,
Kind: string(kind),
@@ -358,7 +371,7 @@ func summarizeAuthEnvVars(envVarSpecs []apispec.AuthEnvVar, apiKey, apiKeyEnvVar
statuses = append(statuses, status)
continue
}
- if kind == apispec.AuthEnvVarKindPerCall && envVar.Required {
+ if envVar.IsRequestCredential() && envVar.Required {
status.Status = AuthEnvVarStatusMissingRequired
status.Detail = "required per-call auth env var is not set"
} else {
diff --git a/internal/pipeline/runtime_test.go b/internal/pipeline/runtime_test.go
index 48a73870..ad87b4e0 100644
--- a/internal/pipeline/runtime_test.go
+++ b/internal/pipeline/runtime_test.go
@@ -952,6 +952,18 @@ func TestSummarizeAuthEnvVarsKindAware(t *testing.T) {
assert.Equal(t, AuthEnvVarStatusOK, mock[0].Status)
}
+func TestRequestAuthEnvVarNamesOnlyIncludesPerCallCredentials(t *testing.T) {
+ got := requestAuthEnvVarNames([]apispec.AuthEnvVar{
+ {Name: "REQUIRED_TOKEN", Kind: apispec.AuthEnvVarKindPerCall, Required: true},
+ {Name: "LEGACY_EMPTY_KIND"},
+ {Name: "SETUP_CLIENT_ID", Kind: apispec.AuthEnvVarKindAuthFlowInput},
+ {Name: "SESSION_COOKIE", Kind: apispec.AuthEnvVarKindHarvested},
+ {Name: "REQUIRED_TOKEN", Kind: apispec.AuthEnvVarKindPerCall, Required: true},
+ })
+
+ assert.Equal(t, []string{"REQUIRED_TOKEN", "LEGACY_EMPTY_KIND"}, got)
+}
+
func TestSummarizeAuthEnvVarsAPIKeyOnlySatisfiesConfiguredEnvVar(t *testing.T) {
t.Setenv("PRIMARY_TOKEN", "")
t.Setenv("SECONDARY_TOKEN", "")
@@ -971,11 +983,15 @@ func TestSummarizeAuthEnvVarsApiKeyWithoutCanonicalEnvVar(t *testing.T) {
t.Setenv("PRIMARY_TOKEN", "")
got := summarizeAuthEnvVars([]apispec.AuthEnvVar{
+ {Name: "SETUP_CLIENT_ID", Kind: apispec.AuthEnvVarKindAuthFlowInput},
{Name: "PRIMARY_TOKEN", Kind: apispec.AuthEnvVarKindPerCall, Required: true},
+ {Name: "SESSION_COOKIE", Kind: apispec.AuthEnvVarKindHarvested},
}, "secret", "", "live")
- require.Len(t, got, 1)
- assert.Equal(t, AuthEnvVarStatusOK, got[0].Status)
+ require.Len(t, got, 3)
+ assert.Equal(t, AuthEnvVarStatusMissingInfo, got[0].Status)
+ assert.Equal(t, AuthEnvVarStatusOK, got[1].Status)
+ assert.Equal(t, AuthEnvVarStatusMissingInfo, got[2].Status)
assert.Empty(t, missingRequiredAuthEnvVars(got))
}
diff --git a/internal/spec/spec.go b/internal/spec/spec.go
index f4440798..7990bd19 100644
--- a/internal/spec/spec.go
+++ b/internal/spec/spec.go
@@ -481,6 +481,19 @@ const (
AuthEnvVarKindHarvested AuthEnvVarKind = "harvested"
)
+// EffectiveKind treats legacy empty kinds as per-call credentials.
+func (v AuthEnvVar) EffectiveKind() AuthEnvVarKind {
+ if v.Kind == "" {
+ return AuthEnvVarKindPerCall
+ }
+ return v.Kind
+}
+
+// IsRequestCredential reports whether this env var can satisfy request auth.
+func (v AuthEnvVar) IsRequestCredential() bool {
+ return v.EffectiveKind() == AuthEnvVarKindPerCall
+}
+
func (k AuthEnvVarKind) SensitivePlaceholder() string {
switch k {
case AuthEnvVarKindPerCall:
@@ -511,7 +524,7 @@ func (c *AuthConfig) CanonicalEnvVar() *AuthEnvVar {
}
c.NormalizeEnvVarSpecs("")
for i := range c.EnvVarSpecs {
- if c.EnvVarSpecs[i].Kind == AuthEnvVarKindPerCall && c.EnvVarSpecs[i].Required {
+ if c.EnvVarSpecs[i].IsRequestCredential() && c.EnvVarSpecs[i].Required {
return &c.EnvVarSpecs[i]
}
}
@@ -530,7 +543,7 @@ func (c *AuthConfig) IsAuthEnvVarORCase() bool {
return false
}
for _, ev := range c.EnvVarSpecs {
- if ev.Kind != AuthEnvVarKindPerCall || ev.Required {
+ if !ev.IsRequestCredential() || ev.Required {
return false
}
}
@@ -1516,7 +1529,7 @@ func independentAuthORGroupsExample(envVarSpecs []AuthEnvVar) (string, bool) {
members := make([]AuthEnvVar, 0, len(envVarSpecs))
for _, envVar := range envVarSpecs {
name := strings.TrimSpace(envVar.Name)
- if name == "" || envVar.Kind != AuthEnvVarKindPerCall || envVar.Required || !strings.Contains(envVar.Description, " OR ") {
+ if name == "" || !envVar.IsRequestCredential() || envVar.Required || !strings.Contains(envVar.Description, " OR ") {
continue
}
referencesSibling := false
diff --git a/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go b/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
index 9483f84a..f3297a7f 100644
--- a/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
+++ b/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
@@ -339,6 +339,10 @@ func (c *Client) authHeader() (string, error) {
if c.Config == nil {
return "", nil
}
+ if c.Config.AccessToken == "" && cliutil.IsVerifyEnv() {
+ c.Config.AccessToken = "mock-token-for-testing"
+ return c.Config.AuthHeader(), nil
+ }
// 60s window avoids in-flight requests racing the expiry boundary.
// Double-checked lock so only one goroutine mints under contention.
if needsClientCredentialsMint(c.Config) {
diff --git a/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/config/config.go b/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/config/config.go
index 02432df0..9a5ddc07 100644
--- a/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/config/config.go
+++ b/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/config/config.go
@@ -78,10 +78,6 @@ func (c *Config) AuthHeader() string {
c.AuthSource = "oauth2"
return "Bearer " + c.AccessToken
}
- if c.PrintingPressOauth2ClientId != "" {
- c.AuthSource = "env:PRINTING_PRESS_OAUTH2_CLIENT_ID"
- return "Bearer " + c.PrintingPressOauth2ClientId
- }
return ""
}
diff --git a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/auth.go b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/auth.go
index 523863c7..209f4628 100644
--- a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/auth.go
+++ b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/auth.go
@@ -60,9 +60,6 @@ func newAuthStatusCmd(flags *rootFlags) *cobra.Command {
fmt.Fprintln(w, "")
fmt.Fprintln(w, "Set your token:")
fmt.Fprintln(w, " export RICH_AUTH_API_KEY=\"your-token-here\"")
- fmt.Fprintln(w, " export RICH_AUTH_CLIENT_ID=\"your-token-here\"")
- fmt.Fprintln(w, " export RICH_AUTH_CLIENT_SECRET=\"your-token-here\"")
- fmt.Fprintln(w, " export RICH_AUTH_SESSION_COOKIE=\"your-token-here\"")
fmt.Fprintln(w, " export RICH_AUTH_OPTIONAL_TOKEN=\"your-token-here\"")
fmt.Fprintln(w, " export RICH_AUTH_BOT_TOKEN=\"your-token-here\"")
fmt.Fprintln(w, " export RICH_AUTH_USER_TOKEN=\"your-token-here\"")
← 3bc1a23a feat(cli): widen auth env-var model with kind/required/sensi
·
back to Cli Printing Press
·
chore(main): release 3.10.0 (#616) f486e4d2 →