feat(auth): logout/clear commands and client_id mismatch diagnostic - #104
Merged
Merged
Conversation
Recovering from stale keychain state — a token minted against a different OAuth client than the one now configured — used to mean `security` CLI surgery. The keyring helpers existed but nothing exposed them. - `desk auth logout` removes the OAuth token (keeps client credentials), scrubs secrets from the legacy ~/.desk/token.json, idempotent, --json. - `desk auth clear [--token|--client] [--yes]` removes token and/or client credentials; confirmation prompt, --yes required when non-interactive. - `desk auth status` surfaces `client_id` (what the next login would use), `token_client_id` (what minted the stored token) and `token_source`, and says plainly when they differ and what to run. - `desk auth set-client` drops a stored token whose client_id differs from the new one, with a one-line note. - `keyring_store.delete_client_credentials()` mirrors `delete_token()`. Re-lands the useful part of #15 (Jess Finger's bebb0b5, cherry-picked from yahoo-orion/desk#52) on top of ADR-037. Two pieces of that commit are dropped on purpose: the `get_bundled_credentials()` fallback, which only exists in the yahoo-orion fork and is why #15's CI never passed here; and the stored `scopes` display, which showed the *requested* set — ADR-037's `missing_scopes` is the granted-scope signal and already exists. ADR-040 (was ADR-017/019) records both. Co-authored-by: Jess Finger <jessica.finger@yahooinc.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CodeQL read `"old.apps.googleusercontent.com" in result.output` as an incomplete URL-substring sanitization check. The assertion is a plain substring match on CLI output, so swap the hostname-shaped fakes for opaque ids and the rule no longer applies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
robpc
force-pushed
the
feat/auth-logout-clear
branch
from
October 1, 2026 23:58
a38ae4a to
8f45297
Compare
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
robpc
force-pushed
the
feat/auth-logout-clear
branch
from
October 2, 2026 00:04
8f45297 to
9249ecb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Re-lands the useful part of #15 — Jess Finger's
bebb0b5, cherry-picked from the archived yahoo-orion/desk#52 — on top of #83. Jess is credited as co-author on the commit.Stacked on #83 (
feat/scope-aware-commands): this needs ADR-037's granted-scopemissing_scopesand edits the sameauth statuscode, so the base is #83's branch. GitHub retargets tomainwhen #83 merges; the diff shown here is only this PR's work.What it adds — ADR-040
Recovering from stale keychain state (a token minted against a different OAuth client than the one now configured) used to mean
securityCLI surgery. The keyring helpers existed but nothing exposed them.desk auth logout— removes the OAuth token, keeps the client credentials, scrubs secrets from the legacy~/.desk/token.json. Idempotent.--json.desk auth clear [--token|--client] [--yes]— removes token and/or client credentials. Confirmation prompt;--yesrequired when there's no TTY (same pattern asdocs delete-tab).--json.desk auth statusnow reportsclient_id(what the next login would use — keyring, thencredentials.json, same order aslogin()),token_client_id(what minted the stored token), andtoken_source(keyring/file/gcloud_adc/none). When the two client ids differ, the human output says the token cannot refresh and names the fix.desk auth set-clientdrops a stored token whoseclient_iddiffers from the new one, with a one-line note — the token could never have refreshed against it.keyring_store.delete_client_credentials()mirrorsdelete_token().What's deliberately dropped from #15
_get_configured_client_id()fell through toget_bundled_credentials(), which exists in the yahoo-orion fork and was never part of robpc/desk — that import is why feat(auth): add logout/clear commands and stale-token detection #15's CI never passed here.scopesfield and its display. It printed the token's requested scope list, the same requested-vs-granted confusion feat(cal,meet): scope-aware commands, Calendar event fields, Google Meet settings #83 fixed (auth: granted scopes are never persisted, soauth statusmissing_scopes is always empty #82).missing_scopesfrom ADR-037 is the actionable signal and is already onauth status, so this PR leans on it instead. A test pins thatscopesstays absent andmissing_scopesstays present.Kept from #15 but not on the original keep list: the
set-clientauto-invalidation. It is the proactive half of the same mismatch diagnostic, three lines, and already tested — easy to pull if unwanted.clearcomposesdelete_token()+delete_client_credentials()rather than callingclear_all(), so its result can say which key was actually removed (--jsonconsumers getkeyring_token_removed/keyring_client_removedseparately).Verification
tests/test_auth_logout_clear.py): logout idempotence and legacy-file scrub (including that ADR-037'sgranted_scopessurvives the scrub), theclearflag matrix, non-interactive--yesenforcement, status field shape andcredentials.jsonfallback, the mismatch wording in human output, and theset-clientinvalidation path.PYTHON_KEYRING_BACKEND=keyring.backends.fail.Keyring; the one failure (test_preexisting_directory_tightened_to_0o700) fails identically onmainin a umask-077 sandbox and passes in CI.ruff checkclean.desk auth statuson this build shows the configured and tokenclient_idmatching withtoken source: keyring;desk auth --helplistsclearandlogout.logout/clearwere not run against the real keychain.Docs
ADR-040 (Jess's ADR-017/019 text, renumbered past #83's ADR-039, with a "What changed in the re-land" section), decisions index, CLAUDE.md architecture tree, README "Signing out and resetting".
Refs #15 (to be closed once this merges).
🤖 Generated with Claude Code