Skip to content

feat(profile-sync): add MFA credential enrollment - #10266

Merged
mathieuartu merged 13 commits into
mainfrom
mfa/controller-enrollment
Sep 21, 2026
Merged

mathieuartu merged 13 commits into
mainfrom
mfa/controller-enrollment

Conversation

@mathieuartu

@mathieuartu mathieuartu commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Explanation

Exposes UI-driven MFA enrollment on AuthenticationController (no ApprovalController).

  • In-memory enrolledCredentials cache (optional on the state type so partial-state selectors stay assignable) with redacted emails in state logs.
  • refreshEnrolledCredentials, beginCredentialEnrollment, completeCredentialEnrollment.
  • AuthenticationController:credentialsChanged when the cache actually changes.
  • Automatic credential refresh after sign-in and unlock when MFA is enabled; refresh failure does not fail sign-in.
  • Cache cleared (and the change event published) on lock, sign-out, and wallet reset.
  • Regenerated messenger action types; README and changelog updates.

Enrollment is UI-orchestrated: the controller returns challenges; the client collects the passkey or OTP proof and calls complete.

References

Depends on #10265. Follow-up: #10267. Stack: stack #10268.
Related to: https://consensyssoftware.atlassian.net/browse/MUL-2262

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

High Risk
Changes authentication session lifecycle and MFA enrollment, including SRP token invalidation after email enroll and race handling around lock/sign-out—security-sensitive behavior that clients must wire correctly.

Overview
Adds UI-orchestrated MFA enrollment on AuthenticationController: refreshEnrolledCredentials, beginCredentialEnrollment, and completeCredentialEnrollment, wired to the existing JWT bearer MFA SDK and new messenger action types.

Enrollment state lives in a non-persisted enrolledCredentials cache (UI-visible; state logs allow-list type/status/enrolledAt only). An optional trace constructor hook (@metamask/controller-utils) wraps MFA network steps with operation/credential tags and outcomes.

Session and concurrency guards bump an auth-session epoch on lock, sign-out, and wallet reset so in-flight MFA work cannot apply after the session ends; credential refreshes are sequenced so slower responses cannot overwrite newer lists. Email OTP completion invalidates the primary SRP session after a post-enroll refresh (or even when the session ends mid-flow) so cached tokens are not reused without the new email claim; passkey enroll does not touch the session. Sign-out/clearState clear the credential cache.

Also adds MFA nock fixtures and mock response helpers, depends on @metamask/controller-utils, and tightens ElevatedTokenClaimsStruct as a single sensitive struct.

Reviewed by Cursor Bugbot for commit d70d671. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from ed6e074 to 66759be Compare September 16, 2026 14:08

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch 2 times, most recently from a8c6583 to 0d73110 Compare September 16, 2026 20:31

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from 9cec589 to e2ae81d Compare September 16, 2026 21:03

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from e2ae81d to 05d8bdd Compare September 17, 2026 06:48

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from 05d8bdd to 12ca387 Compare September 17, 2026 06:53
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch 2 times, most recently from 040560e to 081c55a Compare September 17, 2026 08:12
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from 081c55a to 9c22c31 Compare September 17, 2026 13:39
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from 663f3b0 to f43170c Compare September 18, 2026 07:23
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from f43170c to 678158c Compare September 18, 2026 10:14

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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 678158c. Configure here.

Base automatically changed from mfa/sdk-services to main September 18, 2026 12:13
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch 2 times, most recently from 7539a3d to a89612e Compare September 18, 2026 13:11
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Sep 18, 2026
## Explanation

Wires the MFA HTTP client into the SRP JWT auth SDK on top of the
validation foundation.

- `mfa/services` calls `/api/v2/mfa/*` (enroll, enroll complete, verify,
verify complete, credentials), validates request/response bodies, maps
server codes to `MfaError` subclasses, and handles OTP cooldown /
`Retry-After`.
- `SRPJwtBearerAuth` exposes begin/complete enrollment and verification,
credential listing, and assertion-to-token exchange.
- Public `JwtBearerAuth` forwards those methods and rejects non-SRP auth
types.
- Nock fixtures and unit tests cover happy paths and error mapping.

Clients still go through `AuthenticationController` in later PRs; this
layer is not UI-facing.

## References

Depends on MetaMask#10264. Follow-ups: MetaMask#10266, MetaMask#10267. Stack: [stack
#10268](https://github.com/MetaMask/core/pull/10268).
Related to: https://consensyssoftware.atlassian.net/browse/MUL-2261

## 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)
- [ ] 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]
> **High Risk**
> Touches authentication, step-up tokens, and passkey/OTP handling;
mistakes could weaken session elevation or mishandle credentials, though
coverage is extensive.
> 
> **Overview**
> Adds **passkey and email OTP MFA** to the profile-sync JWT auth SDK: a
new HTTP layer calls `/api/v2/mfa/*` (enroll, complete, verify,
credentials), validates payloads, maps server codes to `MfaError`
subclasses, and handles OTP cooldown plus `Retry-After`.
> 
> **`SRPJwtBearerAuth`** and public **`JwtBearerAuth`** expose
enrollment/step-up flows, credential listing, and
**`exchangeMfaAssertion`** (AAL2 JWT → elevated access token via
existing OIDC). MFA is **SRP-only**; email enrollment uses a separate
`email` option from `entropySourceId`.
> 
> Schemas mark challenges, OTP codes, passkey payloads, and tokens with
**`sensitive()`**. Tests, nock fixtures, changelog, and SDK exports for
MFA types are included.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
56b3671. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch 2 times, most recently from e81accc to e809f04 Compare September 18, 2026 21:10
mathieuartu and others added 13 commits September 21, 2026 16:04
Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh enrolled credentials before invalidating the SRP session after email enrollment, and drop in-flight refresh results if the wallet locks.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Drop the credentialsChanged event; stateChange already carries the list
- Move step-up state, config and event scaffolding to the step-up layer
- Derive the completion credential type from the proof; trace reason.operation
- Invalidate the SRP session once via finally after email enrollment
- Make the post-sign-in credential warm-up non-blocking; drop unlock refresh
- Inline the default trace callback

Co-authored-by: Cursor <cursoragent@cursor.com>
@mathieuartu
mathieuartu force-pushed the mfa/controller-enrollment branch from e809f04 to d70d671 Compare September 21, 2026 14:07

@ccharly ccharly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (went quickly over tests, relying on coverage threshold here)

@mathieuartu
mathieuartu added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit a87b1c9 Sep 21, 2026
337 checks passed
@mathieuartu
mathieuartu deleted the mfa/controller-enrollment branch September 21, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants