fix(identities): send unlink provider as a query param - #93
Merged
Merged
Conversation
Settings -> Connected accounts -> Disconnect always failed with
400 {"error":"provider is required"}. `unlinkIdentity` sent the provider in a
camelCase JSON body via `deleteCamel`, but DELETE /api/user/identities reads it
from the query string (`searchParams.get('provider')`) and never parses a body,
so the value was always null.
Switch to the bodyless `delete(_:)` with `?provider=<encoded>`. The value is
percent-encoded with `.urlQueryAllowed` minus `+&=?#/`: that set permits the
sub-delimiters, and the backend reads params through `URLSearchParams`, which
decodes a literal `+` as a space.
Drop `providerId` — the route never reads it and deletes by
`{userId, provider}`, removing every identity stored under that provider value.
That is recorded on the method, not worked around here.
Tests cover the query parameter, an empty body, the verb and path, the
mastodon:instance provider form, delimiter encoding, and the 401/403/404 paths.
The four new assertions fail against the previous body-based call.
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
One conflict, in APIClient.swift: this branch adds the `queryValueAllowed` character set used by `unlinkIdentity`, and #90 (the dead-endpoint sweep, now on main) added a doc comment above `verifyIdentity` at the same point. Both kept, in that order, so the doc comment stays attached to the function it documents — verified after resolving. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bss5MgZa7Jvj2m9zdaUd1
This was referenced Sep 17, 2026
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 #77. Settings -> Connected accounts -> Disconnect always failed with
400 {"error":"provider is required"}.APIClient.unlinkIdentitysent the provider in a camelCase JSON body viadeleteCamel, butDELETE /api/user/identitiesreads it from the query string (searchParams.get('provider')) and never parses a body, so the value was alwaysnull. The call now uses the bodylessdelete(_:)against/api/user/identities?provider=<encoded>.providerIdis gone from the signature: the route never reads it and deletes by{ userId, provider }viadeleteMany, so it removes every identity row stored under that provider value. That is recorded in a doc comment on the method rather than worked around here. In practice Mastodon rows are stored per instance (mastodon:techhub.social), so distinct instances are unaffected; the existing confirmation copy ("You'll no longer be able to sign in with X") is already provider-scoped.What's included
unlinkIdentity(provider:)sends the provider as a query parameter and an empty body..urlQueryAllowedminus+&=?#/. That set permits the sub-delimiters, and the backend reads params throughURLSearchParams, which decodes a literal+as a space.LinkedIdentitiesViewupdated; its 401 path still goes throughauthState.handleUnauthorized(), unchanged.deleteCamel's doc comment no longer lists identity unlink as a body-scoped route.unlinkIdentitytests inAPIClientIdentitiesTests.No new files, so no
project.pbxprojchange. Out of scope by design: no token-health / "check connection" work (that is #92), andverifyIdentityis untouched.Testing
-destination id=D9B21F4D-933E-4871-98AE-B3E65427BCF5, dedicated-derivedDataPath,-parallel-testing-enabled NO,-skip-testing:InterlinedListTests/E2EReadOnlyTests). No compiler warnings.sendsProviderAsQueryParameter,sendsEmptyBody,mastodonProvider_keepsInstanceSuffix,providerWithQueryDelimiters_percentEncodesThem), so the tests bite.nil, verbDELETE+ path/api/user/identities,mastodon:instancesurvives encoding,+ & =are percent-encoded and round-trip throughURLComponents, plus the 401 / 403 / 404 error paths (404 surfaces the route'sIdentity not found).🤖 Generated with Claude Code