Repository navigation
feat(profile-controller): backend-owned X OAuth connect and disconnect - #10668
Conversation
- Add startXConnect to fetch the X authorization URL; profile identity is resolved server-side from the bearer token, with an optional linked_address for profile auto-creation - connectX now persists the linked X profile to controller state and returns profile_created when the backend auto-creates a profile - Add disconnectX to clear the linked X profile - Expose XConnectSession type and regenerate method action types
|
@metamaskbot publish-previews |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
…ssenger Use `ControllerStateChangedEvent` instead of the deprecated `ControllerStateChangeEvent` so the messenger event type matches the `ProfileController:stateChanged` event that BaseController emits.
| params, | ||
| ); | ||
| return this.#mapXResponseToXProfile(response); | ||
| async connectX(params: { |
There was a problem hiding this comment.
Are we creating the profile in one step here as well? We had to keep the xprofile and profile row creations step separate because the connect to x and the point that the profile is created is separate in the UI.
There was a problem hiding this comment.
It can create as well as link an x profile to a profile.
There was a problem hiding this comment.
Sorry, I was saying that we shouldn't create a profile at this point (commented about this here as well: https://github.com/MetaMask/temp-profile-api/pull/2/changes#r4232515587). The UX is multistep so the profile shouldn't be created until createProfile is actually called. We also don't have anything in the designs that would allow a user to call this entrypoint after creating a profile so linking an x profile to an existing profile isn't a real scenario.
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/profile-controller/CHANGELOG.md
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 838633a. Configure here.
…AndUpdateProfile` (MetaMask#10793) ## Explanation - Recently merged PR MetaMask#10668 added a `profileId` check in state for x methods that would throw since at the point that those methods are called, a profile should not exist in state. Removed that check. - Added a `fetchAndUpdateProfile` method - Removed `nullable` from `avatar_url` in the profile api response type ## References N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > X OAuth can run without local profile state and `connectedToX` is no longer updated on connect; `avatar_url` is now required in API responses, which may fail validation if the API still returns null. > > **Overview** > **Adds `fetchAndUpdateProfile`** on `ProfileController`, wired through the messenger to `ProfileService:getProfile`, mapping the API response into `state.profile` without touching `xProfile`. > > **Relaxes X OAuth entry points** so `getXAuthorizationUrl` and `connectX` no longer require a profile in local state (removed `#getProfileIdOrThrow()`). **`connectX` only persists `xProfile`** on success; it no longer flips `profile.connectedToX` in state. > > **Tightens profile API typing** by making `avatar_url` non-nullable in `ProfileService` validation and mapping it directly (drops null-to-empty handling and related tests). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2a6e9ee. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Explanation
@metamask/profile-controllercould start an X (Twitter) link, but the client had to supply the profile ID and hold the OAuth details. The Profile API now owns the whole X OAuth flow: it keeps the client secret, the PKCE verifier and the X tokens, and it creates the MetaMask profile during the connect if one doesn't exist yet (using the X handle as the username). The client only opens the authorize URL and relays the returnedcodeandstate.This PR updates the controller and service to match:
ProfileController.startXConnect({ linkedAddress? })(new) returns{ authorizationUrl, state }. Nothing is stored in controller state. The profile comes from the verified bearer token on the backend.linkedAddress(CAIP-10) is forwarded aslinked_addressand is required when the profile doesn't exist yet.ProfileController.connectX({ code, state, profileId })(BREAKING) now returns{ profile, xProfile, profileCreated }instead of just the X profile.xProfileto state as soon as the connect succeeds, then fetches the profile, because the backend may have just created it.ProfileController.disconnectX(profileId)(new) clearsxProfileand setsconnectedToXtofalse.ProfileService.disconnectX(profileId)(new) sendsDELETE profiles/{profileId}/x. A404is treated as success, meaning the account is already disconnected; otherwise a client whose saved state has drifted from the backend could never disconnect.ProfileService.getXAuthUrl(linkedAddress?)sends the optionallinked_addressquery parameter.ProfileService.connectXaccepts the optionalprofile_createdboolean in the response. It's optional so older backends still validate.startXConnectanddisconnectXare exposed on the messenger, and the method action types are regenerated.References
@metamask/profile-controller).Checklist
Note
Medium Risk
Breaking changes to X connect/createProfile state handling and OAuth entry requirements will affect mobile and other consumers; OAuth unlink uses authenticated DELETE with intentional 404-as-success semantics.
Overview
Adds X OAuth start and unlink APIs on
ProfileController/ProfileService, and tightens when X link state is written to controller state.getXAuthorizationUrlstarts the flow: it requires a profile already in state, calls the backend for the authorize URL andstate, and returns ephemeralConnectXSessiondata without persisting OAuth details in the controller.disconnectX(profileId)callsDELETE profiles/{profileId}/x(404 treated as success) and clearsxProfile/connectedToXwhen the ID matches state.Breaking behavior changes:
connectXnow requires a profile in state and, on success, immediately setsxProfileandconnectedToX(it no longer relies oncreateProfileto carry over anx_profilefrom the create response).getXAuthUrlis bearer-token–scoped on the server with nolinkedAddressparameter.Also fixes the messenger event typing to
ProfileController:stateChanged, renamesXConnectResponse→ConnectXResponse, and invalidates cachedgetProfileafter connect/disconnect mutations inProfileService.Reviewed by Cursor Bugbot for commit 7ec47a5. Bugbot is set up for automated code reviews on this repo. Configure here.