You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat!: fail-closed authentication via AuthRequirement; populate PKCE identity (#17)
Replace the implicit eager-auth model with an explicit, fail-closed
per-command authentication policy, and populate credential identity from
PKCE tokens.
Authentication policy (`CommandSpec.auth: AuthRequirement`):
- Required (default): the engine resolves the credential before the handler
runs and renders an auth-error if it cannot — a gated command cannot execute
unauthenticated even if its handler never reads the credential, and its
audit/activity identity is always populated.
- Optional (`auth_optional()`): resolution is deferred; the handler calls
`resolve()`/`try_resolve()` only when a decision depends on the credential.
- None (`no_auth(true)`): never authenticates; default-env injection is
suppressed.
Credential resolution stays memoized behind a `CredentialResolver`, and
`--schema`/`--dry-run` short-circuit before any Required resolve, so they never
trigger an auth flow. A forgotten annotation now over-prompts rather than
letting a gated command run unauthenticated.
Auth-error outcomes are classified by the error a handler or authorizer
actually returns (`CliCoreError::is_auth()`, with provider failures wrapped in
`CliCoreError::AuthProvider`) instead of a sticky flag, so a swallowed-then-
unrelated error is no longer misclassified. Auth failures attribute the
activity backend to the auth provider.
`PkceAuthProvider` now fills `Credential.identity`/`sub` by decoding the
access-token JWT payload (display-only, no signature verification), with a
configurable claim priority via `with_identity_claims`.
BREAKING CHANGE: `CommandSpec.no_auth` (bool) is replaced by `CommandSpec.auth`
(`AuthRequirement`), and `MiddlewareRequest.no_auth` by `MiddlewareRequest.auth`.
`CommandContext.credential` is now a `CredentialResolver` instead of
`Option<Credential>`; `RuntimeCommandSpec::new` and `new_typed` handler closures
receive a `CredentialResolver`; and `Authorizer::authorize` receives
`&CredentialResolver` instead of `Option<&Credential>`. The `no_auth(true)`
builder still works and maps to `AuthRequirement::None`; `auth_optional()` and
`auth(AuthRequirement)` select the other policies.
Co-authored-by: Jay Gowdy <jgowdy@godaddy.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Set `.with_default_fields(...)` for list-style output.
220
220
- Set `.with_json_schema::<T>()` when the response shape is known.
221
221
- Add `clap::Arg` values with the exact user-facing flag names the CLI should expose.
222
-
- Use `.no_auth(true)` only for commands that genuinely do not need credentials.
222
+
- Authentication is fail-closed by default (`AuthRequirement::Required`): the engine resolves the
223
+
credential before the handler runs, so a command that should be gated cannot execute
224
+
unauthenticated even if its handler never reads the credential. Handlers receive a
225
+
`CredentialResolver`; for `Required` commands the credential is already resolved, so
226
+
`resolver.resolve().await?` (or `ctx.credential().await?`) is a memoized lookup. `--schema` and
227
+
`--dry-run` short-circuit before resolution, so they never trigger an auth flow.
228
+
- Use `.auth_optional()` for commands that must run while logged out and only enrich output when a
229
+
credential happens to be present; the engine does not resolve on their behalf, so the handler
230
+
decides via `resolver.try_resolve().await?`. Use `.no_auth(true)` for commands that never
231
+
authenticate (this also suppresses default-env injection). Forgetting these annotations only
232
+
over-prompts; it never lets a gated command run unauthenticated.
223
233
- Use `.with_tier(...)` or `.mutates(true)` for mutating commands so `--dry-run` can short-circuit them.
224
234
- Prefer returning structured JSON values from handlers; let cli-engine render JSON, human, and TOON formats.
225
235
- Prefer `CommandSpec::from_args::<T>()` + `RuntimeCommandSpec::new_typed` when the command has many flags, needs clap validation attributes, or when porting existing derive-based commands. Use the builder path for simple commands with one or two flags.
0 commit comments