Skip to content

feat(auth): non-interactive flags for login/refresh (#211)#243

Merged
Zious11 merged 4 commits intodevelopfrom
feat/auth-non-interactive-flags-211
Apr 21, 2026
Merged

feat(auth): non-interactive flags for login/refresh (#211)#243
Zious11 merged 4 commits intodevelopfrom
feat/auth-non-interactive-flags-211

Conversation

@Zious11
Copy link
Copy Markdown
Owner

@Zious11 Zious11 commented Apr 21, 2026

Summary

Adds --email, --token, --client-id, --client-secret to jr auth login and jr auth refresh. Each credential resolves via flag → env var → TTY prompt. Under --no-input, missing credentials produce JrError::UserError (exit 64) with a message naming the specific flag and env var so agents and CI can recover.

Value resolution

Credential Flag Env var
Jira email --email JR_EMAIL
API token --token JR_API_TOKEN
OAuth Client ID --client-id JR_OAUTH_CLIENT_ID
OAuth Client Secret --client-secret JR_OAUTH_CLIENT_SECRET

Prefer env vars over flags for secrets — bare CLI args leak via ps/audit logs. This is documented in clap help text.

Error-path UX for agents

$ jr --no-input auth login --oauth
Error: OAuth Client ID is required. Provide --client-id or set $JR_OAUTH_CLIENT_ID. Create one at https://developer.atlassian.com/console/myapps/.

The dev-console URL is appended only to OAuth errors so first-time agents learn where to obtain an app, not just how to pass credentials. Addresses a silent-failure-hunter finding from local review.

Backward compat

  • jr init still works (inherently interactive — no flag threading).
  • Existing TTY flow is unchanged when no flags/env are set.
  • jr auth refresh preserves its clear-then-login ordering; if login fails, the user still sees "Credentials were cleared. Run `jr auth login` to restore access."

Findings from local review (all addressed)

# Source Finding Resolution
1 silent-failure-hunter OAuth --no-input error omitted dev-console URL Added optional hint param to resolve_credential; passed OAUTH_APP_HINT for both OAuth creds
2 silent-failure-hunter unsafe { set_var } in tests had no panic-safe cleanup Added EnvGuard RAII struct; all 4 env-setting tests now use it
3 silent-failure-hunter Integration test didn't pin "Credentials were cleared" recovery line Added stderr assertion

Test plan

  • cargo fmt --all -- --check clean
  • cargo clippy --all-targets -- -D warnings clean
  • cargo test — 813 passed, 0 failed (baseline 805 + 8 new)
  • 5 new resolve_credential_* unit tests (flag/env/empty/no_input/OAuth hint)
  • Tightened auth_refresh_no_input_fails_with_clear_message integration test
  • Local review by code-reviewer + silent-failure-hunter: no unresolved findings

Scope notes

  • No keychain-write success test. Keychain writes require a real backend (fragile across CI platforms) and leave stale entries. The 5 unit tests + tightened integration test cover the resolution logic end-to-end; the keychain-write path is the same as before.
  • refresh_credentials now has 7 params. Consider a RefreshArgs struct if it grows further — not needed now, and clippy is silent on it.

Closes #211

Add --email, --token, --client-id, --client-secret to both `jr auth login`
and `jr auth refresh`. Each value resolves via flag > env var > TTY
prompt. Under --no-input, missing credentials produce JrError::UserError
(exit 64) naming the specific flag + env var so agents and CI scripts
can recover.

Env vars:
  JR_EMAIL              (api_token flow)
  JR_API_TOKEN          (api_token flow; prefer over --token to avoid
                         process-list leakage)
  JR_OAUTH_CLIENT_ID    (OAuth flow)
  JR_OAUTH_CLIENT_SECRET (OAuth flow; same preference)

OAuth errors include a dev-console URL hint so first-time agents learn
they must create an app at developer.atlassian.com before passing
credentials — not just *how* to pass them.

refresh_credentials keeps its clear-then-login ordering; the existing
"Credentials were cleared" recovery line is now pinned by an integration
test assertion so a future refactor can't drop the hint silently.

Tests:
  - 5 new unit tests on resolve_credential (flag/env/empty/no_input/hint)
  - RAII EnvGuard for test env vars so a panic can't leak state across
    parallel tests in the same process
  - Tightened tests/auth_refresh.rs to assert exit 64, specific flag/env
    message, and recovery hint (replaces pre-#211 "fails without panic")

Closes #211
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds non-interactive credential flags to jr auth login / jr auth refresh, resolving credentials via flag → env var → TTY prompt, and producing a JrError::UserError (exit 64) with actionable messaging under --no-input.

Changes:

  • Introduces resolve_credential helper + JR_* env var constants to implement consistent credential resolution and --no-input error behavior (with OAuth hint URL).
  • Plumbs new optional credential args through CLI parsing and dispatch (main.rs, cli/mod.rs, cli/init.rs).
  • Tightens auth_refresh integration test to assert exit code 64 and presence of a recovery message.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/cli/auth.rs Adds credential resolution helper (flag/env/prompt), OAuth hinting, and threads new args through login/refresh flows; adds unit tests and env RAII guard.
src/cli/mod.rs Adds --email/--token/--client-id/--client-secret options to auth login and auth refresh clap structs.
src/main.rs Passes new CLI options and --no-input into auth login/refresh functions.
src/cli/init.rs Updates init’s auth calls to new function signatures (interactive-only path uses no_input=false).
tests/auth_refresh.rs Replaces prior “no panic” assertion with --no-input contract checks (exit 64 + actionable stderr).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/auth_refresh.rs
Document the new flag/env-var surface in three places:
  - Quick Start code block: shell examples for both api_token and OAuth
    flows under --no-input
  - macOS upgrade section: CI/headless recovery path
  - Scripting & AI Agents section: bullet referencing all 4 flags + env
    vars
  - Commands table: expand auth login entry, add auth refresh entry

Paired with the feature commit (9442f4c) on this branch.
Config::load() merges JR_* env vars via figment's Env::prefixed at
src/config.rs:65. A developer with JR_INSTANCE_AUTH_METHOD=oauth
exported in their shell would take the OAuth path under `auth refresh`,
and the test's email/JR_EMAIL stderr assertions would fail.

Explicitly env_remove("JR_INSTANCE_AUTH_METHOD") so the test is
hermetic to parent-env configuration.

Flagged by Copilot on PR #243.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/auth.rs Outdated
Per Copilot review on PR #243: `EnvGuard` did RAII cleanup but didn't
serialize env-var mutation across parallel tests. Since
`std::env::set_var` / `remove_var` are unsafe in Rust 2024 (concurrent
env access is UB — C's getenv/setenv aren't thread-safe), tests could
still race even with distinct per-test keys.

Add `static ENV_LOCK: Mutex<()>` and hold the lock for EnvGuard's full
lifetime. Also lock reader-only tests that call `resolve_credential`
(which reads env via `std::env::var`), since reads must also be
serialized against concurrent writes.

Matches the established pattern in src/config.rs::ENV_MUTEX and
src/cache.rs::ENV_MUTEX.
@Zious11 Zious11 requested a review from Copilot April 21, 2026 13:27
@Zious11 Zious11 merged commit 86b3091 into develop Apr 21, 2026
9 checks passed
@Zious11 Zious11 deleted the feat/auth-non-interactive-flags-211 branch April 21, 2026 13:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/auth_refresh.rs
// stderr assertions would fail. Explicitly clear it to pin the
// api_token flow for this test.
.env_remove("JR_INSTANCE_AUTH_METHOD")
.args(["--no-input", "auth", "refresh"])
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This integration test no longer sets stdin for the subprocess (it used to call write_stdin("")). If --no-input handling regresses and the command falls back to an interactive prompt, the test can hang waiting for user input (especially when cargo test is run with an attached TTY). Consider explicitly piping empty stdin (or otherwise closing stdin) so a regression fails fast instead of blocking the test suite.

Suggested change
.args(["--no-input", "auth", "refresh"])
.args(["--no-input", "auth", "refresh"])
.write_stdin("")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

auth login / refresh: add non-interactive flag equivalents (--email, --token, --client-id, --client-secret)

2 participants