Skip to content

feat: add auth login/logout/status commands (PROT-7)#8

Merged
BlakeHastings merged 7 commits into
mainfrom
feat/PROT-7-cli-auth
Jun 2, 2026
Merged

feat: add auth login/logout/status commands (PROT-7)#8
BlakeHastings merged 7 commits into
mainfrom
feat/PROT-7-cli-auth

Conversation

@BlakeHastings
Copy link
Copy Markdown
Member

What

Adds the CLI side of PROT-7: protostar auth login, logout, and status. This completes the authenticate-to-the-registry loop (the registry side shipped in protostar-registry).

  • auth login — OAuth Authorization Code + PKCE over a loopback redirect (RFC 8252). Opens the browser to the registry, which federates sign-in to GitHub; catches the code on an ephemeral 127.0.0.1 port, exchanges it for tokens, and stores the session in the OS credential store (Devlooped.CredentialManager, the same dependency the skills use). --no-browser prints the URL for headless use; --timeout bounds the wait.
  • auth status — reports the stored session and verifies it against the registry (refreshing an expired access token when possible). Works offline: no session → "Not logged in".
  • auth logout — removes the stored session.
  • Registry selection via --registry <url> or PROTOSTAR_REGISTRY_URL.
  • API-contract compatibility check on connect: the CLI declares the API major it speaks and refuses an unsupported registry (GET /v1/meta), matching the registry's versioning policy.

Design notes

  • New Auth/ helpers: PKCE, loopback listener, token store, registry HTTP client + DTOs, browser launcher, endpoint/compat resolution. Commands live under Commands/Auth/ and mirror the existing Spectre.Console.Cli command style (sync Execute wrapping the async flow).
  • No secret is persisted; PKCE handles the public-client proof.

Verification

  • dotnet test green — 9/9 acceptance scenarios (7 existing + 2 new in Auth.feature: auth --help lists the subcommands; auth status reports not-logged-in with no session).
  • Manual integration smoke against a running protostar-registry: the compat check passes, and the authorization request the CLI builds is accepted by OpenIddict and 302-redirects to GitHub (https://github.com/login/oauth/authorize) — confirming client_id, the loopback redirect_uri (port ignored as designed), PKCE, and scopes all line up. The GitHub consent + token-exchange leg requires a real GitHub OAuth app and an interactive browser, so it stays a manual smoke.

Completes PROT-7.

@BlakeHastings BlakeHastings marked this pull request as draft June 2, 2026 05:44
@BlakeHastings
Copy link
Copy Markdown
Member Author

Added --provider <name> (e.g. --provider github): appends an identity_provider hint to the authorize request so the registry skips its sign-in chooser and forwards straight to that provider. Pairs with the registry chooser in protostar-registry PR #2.

…he OS keychain

The Windows Credential Manager caps a credential blob at 2560 bytes, which our JWT
access token + refresh token exceeds, so saves failed. Switch to an owner-only file
store (0600 on Unix, profile ACL on Windows), overridable via PROTOSTAR_CONFIG_DIR.
Drops the Devlooped.CredentialManager dependency.
@BlakeHastings BlakeHastings marked this pull request as ready for review June 2, 2026 16:43
ljones491
ljones491 previously approved these changes Jun 2, 2026
Copy link
Copy Markdown
Member

@ljones491 ljones491 left a comment

Choose a reason for hiding this comment

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

Seeing the synchronous Executes for async commands thing in design notes and it works. I don't see why these can't be AsyncCommands, but it's not blocking.

Comment thread src/Protostar.Cli/Commands/Auth/LoginCommand.cs Outdated
Comment thread src/Protostar.Cli/Commands/Auth/StatusCommand.cs Outdated
Login and Status do genuinely async work; AsyncCommand<T> drops the
sync-over-async GetAwaiter().GetResult() hop and cancellation flows through
ExecuteAsync. Logout stays a sync Command<T> (file IO only).
@BlakeHastings
Copy link
Copy Markdown
Member Author

Good call — done in 4fa26f4. LoginCommand and StatusCommand now derive from AsyncCommand<T> and override ExecuteAsync, dropping the GetAwaiter().GetResult() hop so cancellation flows through cleanly. LogoutCommand stays a sync Command<T> since it only does file IO. Tests green (13/13).

@BlakeHastings BlakeHastings requested a review from ljones491 June 2, 2026 22:06
@BlakeHastings BlakeHastings merged commit 7fa225c into main Jun 2, 2026
2 checks passed
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