Skip to content

Add --device flag for headless OAuth login and fix session token handling - #71

Merged
bregydoc merged 1 commit into
mainfrom
sync/monorepo-8d5aca8
Sep 25, 2026
Merged

bregydoc merged 1 commit into
mainfrom
sync/monorepo-8d5aca8

Conversation

@bregydoc

Copy link
Copy Markdown
Contributor

Summary

Add OAuth device flow as the preferred authentication path for remote and headless hosts, and fix two session-handling bugs: pasted 60-second Clerk session JWTs were rejected immediately due to incorrect refresh skew, and first-party Dev/staging API URLs incorrectly required explicit OAuth configuration in config.toml. Bumps the am CLI workspace to 0.3.0.

Changes

  • Add am auth login --device for OAuth device flow that stores a refreshable Cloud session
  • Route --no-browser through device flow instead of browser OAuth with a loopback callback
  • Add --issuer override to the device login path, persisted so later commands can refresh the session
  • Fail closed when the browser cannot open: exit immediately with device-flow guidance rather than waiting on a dead loopback server
  • Fix token freshness (token_fresh): skip the 60-second refresh skew for paste-login sessions that have no refresh token — fixes am init with freshly pasted short-lived Clerk JWTs (ATO-2308)
  • Introduce is_first_party_cloud_api_url and use it in place of is_production_api_url in auth, doctor, and OAuth-pair resolution — Dev and staging API URLs now use the shipped OAuth pair without requiring config.toml entries (ATO-2321)
  • Validate device login OAuth overrides before the flow starts; refuse any override that later commands would ignore (first-party origins ignore configured pairs, ATOMICMEMORY_OAUTH_ISSUER outranks config)
  • Add reject_blank_oauth_overrides guard applied to both browser and device login paths
  • Fail closed when the device token response omits refresh_token; validate that the returned issuer matches the resolved issuer for the origin
  • Add DeviceLoginOptions.issuer and skip_project_select fields; remove LoginOptions.no_browser (routing handled by select_auth_login_method)
  • Export headless_login_next_steps for consistent error-path copy across auth commands
  • Update README: document --device as the preferred remote/headless path; clarify --token as a short-lived paste session
  • Bump the Rust CLI workspace version from 0.2.2 to 0.3.0 (Cargo.toml, Cargo.lock) for the cli-v0.3.0 release

Why

Remote and VPS users previously had no refreshable login path — paste-login (--token) stores a short-lived JWT with no refresh token, requiring manual re-authentication. The device flow stores a proper OAuth session that CLI commands can refresh without user intervention.

The token freshness fix (ATO-2308) addressed Clerk dashboard JWTs being rejected the moment they were pasted: their 60-second lifetime was shorter than the refresh skew applied unconditionally to all sessions, making am init unusable with a freshly issued token.

The first-party Dev URL fix (ATO-2321) ensures am auth doctor and am whoami work against api.dev.atomicstrata.ai out of the box; the old is_production_api_url check excluded Dev and staging, forcing contributors and QA to add OAuth entries to config.toml that the prod path never needed.

Validation

  • New integration test file crates/cli/tests/device_login_oauth.rs covers the full device poll loop against a local Axum mock, including the fail-closed case for an id-token-only response
  • Unit tests added for oauth_tokens_from_device_response, device_oauth_pair, token_fresh, unrefreshable_session_error, select_auth_login_method, headless_login_next_steps, and first-party Dev URL OAuth resolution
  • pnpm run ci:rust (fmt check, clippy -D warnings, full test suite, release am --help smoke)

…ndling

## Summary

Add OAuth device flow as the preferred authentication path for remote and headless hosts, and fix two session-handling bugs: pasted 60-second Clerk session JWTs were rejected immediately due to incorrect refresh skew, and first-party Dev/staging API URLs incorrectly required explicit OAuth configuration in `config.toml`. Bumps the `am` CLI workspace to 0.3.0.

## Changes

- Add `am auth login --device` for OAuth device flow that stores a refreshable Cloud session
- Route `--no-browser` through device flow instead of browser OAuth with a loopback callback
- Add `--issuer` override to the device login path, persisted so later commands can refresh the session
- Fail closed when the browser cannot open: exit immediately with device-flow guidance rather than waiting on a dead loopback server
- Fix token freshness (`token_fresh`): skip the 60-second refresh skew for paste-login sessions that have no refresh token — fixes `am init` with freshly pasted short-lived Clerk JWTs (ATO-2308)
- Introduce `is_first_party_cloud_api_url` and use it in place of `is_production_api_url` in auth, doctor, and OAuth-pair resolution — Dev and staging API URLs now use the shipped OAuth pair without requiring `config.toml` entries (ATO-2321)
- Validate device login OAuth overrides before the flow starts; refuse any override that later commands would ignore (first-party origins ignore configured pairs, `ATOMICMEMORY_OAUTH_ISSUER` outranks config)
- Add `reject_blank_oauth_overrides` guard applied to both browser and device login paths
- Fail closed when the device token response omits `refresh_token`; validate that the returned issuer matches the resolved issuer for the origin
- Add `DeviceLoginOptions.issuer` and `skip_project_select` fields; remove `LoginOptions.no_browser` (routing handled by `select_auth_login_method`)
- Export `headless_login_next_steps` for consistent error-path copy across auth commands
- Update README: document `--device` as the preferred remote/headless path; clarify `--token` as a short-lived paste session
- Bump the Rust CLI workspace version from 0.2.2 to 0.3.0 (`Cargo.toml`, `Cargo.lock`) for the `cli-v0.3.0` release

## Why

Remote and VPS users previously had no refreshable login path — paste-login (`--token`) stores a short-lived JWT with no refresh token, requiring manual re-authentication. The device flow stores a proper OAuth session that CLI commands can refresh without user intervention.

The token freshness fix (ATO-2308) addressed Clerk dashboard JWTs being rejected the moment they were pasted: their 60-second lifetime was shorter than the refresh skew applied unconditionally to all sessions, making `am init` unusable with a freshly issued token.

The first-party Dev URL fix (ATO-2321) ensures `am auth doctor` and `am whoami` work against `api.dev.atomicstrata.ai` out of the box; the old `is_production_api_url` check excluded Dev and staging, forcing contributors and QA to add OAuth entries to `config.toml` that the prod path never needed.

## Validation

- New integration test file `crates/cli/tests/device_login_oauth.rs` covers the full device poll loop against a local Axum mock, including the fail-closed case for an id-token-only response
- Unit tests added for `oauth_tokens_from_device_response`, `device_oauth_pair`, `token_fresh`, `unrefreshable_session_error`, `select_auth_login_method`, `headless_login_next_steps`, and first-party Dev URL OAuth resolution
- `pnpm run ci:rust` (fmt check, clippy `-D warnings`, full test suite, release `am --help` smoke)
@bregydoc
bregydoc requested a review from ethanj September 25, 2026 20:26

@ethanj ethanj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed PR #71 at 507e2ed. No outstanding blocking findings.

Confirmed the CLI implementation and regression tests match the previously reviewed source. The release metadata consistently bumps the Rust workspace, path dependencies, and lockfile package versions to 0.3.0.

The device flow validates the usable OAuth pair before requesting authorization, persists explicit overrides, rejects incompatible issuers and missing refresh tokens, and applies the same flag conflicts to --device and --no-browser. Both browser and device login reject blank OAuth overrides before configuration writes.

Verification: reused the seven focused tests previously run against identical implementation and test files, including byte-for-byte credential/config preservation and subsequent whoami checks. All 17 checks on this public PR head are green, including macOS, Windows, and fmt-clippy-test.

Disclosure: I contributed the initial blank-override guard and device regression test; this approval is not independent review of that contribution. The consolidated implementation was also reviewed by another reviewer.

Approved for merge. This does not merge the PR or publish the release.

@bregydoc
bregydoc merged commit 9b493a8 into main Sep 25, 2026
28 of 29 checks passed
@bregydoc
bregydoc deleted the sync/monorepo-8d5aca8 branch September 25, 2026 21:17
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.

2 participants