Conversation
The five `/status` routes report whether a provider is configured server-side, never whether this account's stored token still works, so an expired Bluesky or Mastodon credential looked healthy until a cross-post silently failed. `POST /api/user/identities/verify` is the only route that loads the credential and exercises it upstream. Wire it to a manual "Check connection" action per row — manual because each call makes the backend hit the third-party provider, so an on-appear sweep would bill five remote round-trips to a screen opened to read. The route's answers are kept apart from its failures: 400 (credential rejected / no token stored) and 404 (identity row gone) return `needsReconnect` and light up the existing reconnect affordance, while 401, 5xx and transport errors throw and render as "couldn't run the check — your connection is unchanged". Without the new `postCamel(_:body:mappingStatuses:)`, `checkResponse` flattens a 400 and a 500 into the same `.server(…)`, making a dead token indistinguishable from a dead check. Also drops `providerId` from `verifyIdentity`: the route reads only `body.provider` and never looks at it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bss5MgZa7Jvj2m9zdaUd1
This was referenced Sep 16, 2026
Member
Author
|
Superseded by a new PR — see below. |
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.
Summary
Closes #92.
LinkedIdentitiesViewreported each provider's/status, and none of those five routes looks at theaccount's stored token — they answer either "the server holds OAuth app credentials for this
provider" (LinkedIn/Twitter/GitHub) or "a row exists in this user's identity table"
(Bluesky/Mastodon). Both stay cheerfully
configured: trueafter a token is revoked upstream, so auser with an expired Bluesky or Mastodon credential saw a healthy row and found out only when a
cross-post silently failed.
POST /api/user/identities/verifyis the only route that loads the credential and exercises it —restoring the DPoP-bound AT Protocol session for Bluesky, calling the provider's "who am I" endpoint
otherwise.
APIClient.verifyIdentityexisted for it and had no caller; this wires it up.What's included
backend hit the third-party provider, so verifying all five on a screen the user opened to read is
the wrong default.
200 {"success":true}IdentityHealth.verified, distinct from the weaker status-route.connected)400— provider rejected the credential, or no token stored404— identity row gone401/5xx/ no networkpostCamel(_:body:mappingStatuses:)inAPIClientTransport, the write-side twin of theexisting
get(_:mappingStatuses:). Without it,checkResponseflattens the route's 400 and a 500into the same
.server(…)and "your token expired" becomes indistinguishable from "the checknever ran".
providerIddropped fromverifyIdentity— the route reads onlybody.providerand neverlooks at the id. Existing tests updated; a new one asserts the key is absent from the body.
authState.handleUnauthorized()(re-validation), neverlogout().APIClient+Identitiesheaderno longer claims a revoked token is invisible to the app — it now points at the route that sees it.
No new
.swiftfiles, soproject.pbxprojis untouched (and can't conflict with the other in-flightPRs).
Testing
xcodebuild teston simulator6DD28061-659E-4E65-937A-D01DE0E8AECF, serialized, E2E skipped:1166 tests, 0 failures. Clean build, no new warnings.
15 tests added or rewritten:
APIClientIdentitiesTests— path/method, camelCase body,providerIdabsent,200 → verified,400 → needsReconnect(.credentialRejected),404 → needsReconnect(.identityMissing),401 → APIError.status(401),500 → throws(not needs-reconnect), and a transport failure via athrowing
URLSessionProtocolstub → throws (not needs-reconnect).IdentityHealthTests— verify-outcome → row-health mapping:verifiedis notconnected, bothfailures are stale and name the provider, the two failure reasons differ from each other, and
uncheckableisunknownand never stale.Mutation-checked: swapping the 400/404 mapping, mapping 500 to a rejection, re-adding
providerIdto the body, returning
.connectedfor a verified credential, and makinguncheckableaneedsReconnectproduced 8 failures across the 9 new assertions' tests; restoring theimplementation returned the suite to green.
Not verified: the live end-to-end path against a genuinely revoked provider token — that needs a real
expired OAuth credential. The status→outcome mapping is taken from the route source
(
app/api/user/identities/verify/route.ts), not inferred.🤖 Generated with Claude Code