Skip to content

W2: Surface OAuth token health in Connected accounts (verifyIdentity) #92

Closed

Description

@Adron

Split out of #64 (see PR #90). APIClient.verifyIdentity(provider:providerId:) has no caller; #64
kept it because it is the only primitive that closes a real gap.

The gap

LinkedIdentitiesView shows each provider's /status (linkedinStatus, twitterStatus,
blueskyStatus, githubStatus, mastodonStatus), but those report whether the provider is
configured server-side
— {"configured":true,…} — not whether this account's stored token still
works
. So a user whose Bluesky or Mastodon token has expired sees a healthy-looking row and has no
way to find out until a cross-post silently fails.

POST /api/user/identities/verify is the route that actually checks: it loads the stored credential
and verifies it (restoring the DPoP-bound session for Bluesky, calling the provider otherwise).

Build this

  • A Check connection action per linked identity in LinkedIdentitiesView, calling
    verifyIdentity(provider:).
  • Show the outcome on the row: verified, or "needs reconnecting" with the existing reconnect
    affordance.
  • Optionally verify all identities on appear — but only if it is cheap; each call hits the
    provider, so a manual action may be the better default.
  • Remove the "no caller yet" comment on the function once wired.

Note

The route reads only provider; the providerId iOS passes is accepted and ignored. Drop the
parameter when wiring, unless #77 (which also touches this area) resolves it differently.

Acceptance criteria

  • An identity with a revoked/expired token reports as needing reconnection.
  • A healthy identity reports as verified.
  • A failure to reach the route is distinguishable from "token is bad".
  • Unit tests for both outcomes.

Files: Views/LinkedIdentitiesView.swift, Services/APIClient.swift:177.

Activity

  1. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    Implemented in #99 (stacked on #93, which must merge first). Closing here — further work tracks in
    the PR.

    Three corrections to this issue's description, from reading the route

    1. Success is 200 {"success":true}, not {"ok":true}. The pre-existing iOS Response struct
      decoded ok. Harmless — the field was optional and discarded — but the old tests were stubbing a
      shape the server never sends.

    2. There are two different 400s, not one. The issue describes 400 as "token is bad". There is
      also "No token to verify", returned when providerData.access_token is missing or not a string.
      Different cause, same status, and both are reconnect-class — feat(identities): add per-identity connection check #99 folds them together deliberately
      and documents why.

    3. A 200 does not always mean "verified". See below — this is the important one.

    Backend hole found while doing this — filed as #100

    For any provider the route does not recognise (anything that is not bluesky, github,
    mastodon:*, linkedin, twitter), every branch is skipped, nothing is checked, and the route
    still stamps lastVerifiedAt and answers 200 {"success":true}. A bare "mastodon" provider
    string — no :instance suffix, which linkedIdentities can return — lands on exactly that path and
    reports verified without verifying anything.

    The client cannot tell that 200 apart from a real one, so #99 does not try to compensate. Tracked in
    #100.

    One design call worth reviewing in the PR

    checkResponse maps both 400 and 500 to APIError.server(message), so "your token expired" and "the
    server fell over" were indistinguishable — which would have broken this issue's own acceptance
    criterion that the two read differently. #99 adds postCamel(_:body:mappingStatuses:) to
    APIClientTransport.swift, the write-side twin of the existing get(_:mappingStatuses:) that
    APIClient+Lists.swift already uses. That is the one file outside this issue's stated scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestws:moderation-privacyW2 — moderation, privacy, account settings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions