generated from eblume/project-template
Auth errors: distinguish IdP rejection from unreachable + actionable re-auth recovery #14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/auth-error-clarity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes the spoke→hub auth-failure cluster (observed in the 2026-06-06
offline_accessincident): the daemon reported a rejected OAuth refresh as "identity provider unreachable" and gave no recovery guidance.Root cause: every failure in the OAuth path funneled into one
AuthError::Providerwhose Display was hardcoded"identity provider unreachable: {0}". A reachable IdP returning400 invalid_granton a refresh got labeled "unreachable", sending debugging toward the network. The real refresh cause was also swallowed —bearer()logged it and returnedNone, so sync health only ever showed the downstream 401 on/sync/pull.Wording fix (
auth.rs/oauth.rs)AuthErrorintoUnreachable(transport),Rejected(IdP returned an HTTP error — carries the RFC 6749 §5.2error/error_description), andOther(keyring / malformed response, previously mislabeled too).refresh()/discover()/start()/poll()classify transport vs status;refreshreads the OAuth error body on a non-2xx.Recovery UX (
server.rs/heph/heph-tui)bearer()now returnsResult; the sync paths record the real acquisition failure (with a re-login hint when it's a rejection) instead of a masked 401, solast_errorreflects the refresh cause (e.g.invalid_grant).last_errorcarries the exactheph auth login --hub-url … --issuer … --client-id …command, built daemon-side and keyed to the configured hub URL (per the per-URL token-keying gotcha).sync.statusalso returnsissuer/client_id+ the command.heph auth statusprints auth health and the re-login command.heph-tui's auth chip points at it:⚠ auth · heph auth status.Also corrects a now-stale gap note in
set-up-sync-hub.md(daemon config baking landed in #12).Testing
cargo test --workspacegreen;cargo clippy --workspace --all-targetsclean; fmt + prek hooks passtests/oauth.rs: a400 invalid_grantrefresh →Rejectedwith the OAuth body (not "unreachable"); a dead IdP →Unreachableauth.rsunit tests forAuthError::rejectedformatting /is_rejectionheph-tuiindicator test updated for the new chipheph auth statusagainst the live daemon (renders hub + auth state; degrades gracefully on the older daemon that doesn't yet return the new fields)Closes
01KTFSHMYK…+01KTFVNEQH…(duplicate "misleading identity provider unreachable")01KTFSHMYZ…(actionable re-auth guidance)🤖 Generated with Claude Code