feat(profile-sync): add MFA authentication services - #10265
Conversation
906f800 to
4ff3dc2
Compare
7872ea7 to
47c2854
Compare
3b1793f to
ccdc7a1
Compare
ccdc7a1 to
ac84c3f
Compare
7625f86 to
55ed40f
Compare
## Explanation Adds the MFA validation layer in `@metamask/profile-sync-controller` that later PRs in this stack consume. - Superstruct schemas and inferred types for auth-service MFA responses, WebAuthn ceremony payloads, and controller-boundary requests (passkey and email OTP only). - `MfaError` family with a stable enumerable `mfaCode`, plus helpers that survive JSON-RPC serialization (`getMfaErrorCode`, `isMfaError`, `getMfaRetryAfterMs`). - Shared `decodeJwtPayload` (no signature verification) and slightly stricter login JWT `exp` handling. - Expanded `HTTP_STATUS_CODES` for later MFA error mapping. This PR does not call MFA HTTP endpoints or change controller behavior. ## References Stacked under [stack #10268](https://github.com/MetaMask/core/pull/10268). Follow-ups: MetaMask#10265, MetaMask#10266, MetaMask#10267. Related to: https://consensyssoftware.atlassian.net/browse/MUL-2260 ## 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] > **Medium Risk** > Touches authentication-adjacent validation, JWT claim handling, and structured MFA errors, though runtime sign-in/MFA flows are unchanged until follow-up PRs. > > **Overview** > Introduces the **MFA validation layer** in `@metamask/profile-sync-controller` for later stack PRs—no MFA HTTP calls or controller behavior changes in this diff. > > Adds Superstruct schemas and inferred types under `authentication-jwt-bearer/mfa` for passkey and email OTP flows: auth-service responses, WebAuthn ceremony payloads, enrollment/step-up requests, and **AAL2 elevated-token** claim parsing (including Hydra `ext` nesting). `assertValidMfaRequest` / `assertValidMfaResponse` map validation failures to `MfaError` with path details. > > Expands **`MfaError`** with typed subclasses, stable enumerable **`mfaCode`**, and helpers (`getMfaErrorCode`, `isMfaError`, `getMfaRetryAfterMs`) that work after JSON-RPC serialization. Adds shared **`decodeJwtPayload`** and refactors login JWT expiry checks to use it with stricter `exp` typing. **`HTTP_STATUS_CODES`** gains `401` and `502` for upcoming error mapping. > > Dependencies: **`@metamask/superstruct`** (runtime), **`@metamask/rpc-errors`** (tests). Changelog updated. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d6d147b. 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>
55ed40f to
32a03ff
Compare
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 32a03ff. Configure here.
ef981db to
9b772b3
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
- Recognise 401 before requiring a JSON error body - Map code-less 429s to rate_limited instead of otp_resend_cooldown - Stop parsing the unstable error message for a retry delay - Return only the assertion from verify/complete; drop profile mapping - Tolerate unknown credential statuses and missing email.verified - Export domain types only from the SDK entrypoint Co-authored-by: Cursor <cursoragent@cursor.com>
9b772b3 to
32dce23
Compare
ccharly
left a comment
There was a problem hiding this comment.
LGTM. I left 1 note about a potential missing sensitive (can be done in the next PR).
Also, as discussed internally, we could potentially enforce typing in some of the input type, so the compiler can enforce it, e.g.: using type: 'passkey' and enforce the field passkey_attestation to be required.
But this can come later too. Might be relevant if we add more credential type in the future!
| @@ -241,7 +247,7 @@ export const GetElevatedTokenRequestStruct = object({ | |||
| }); | |||
|
|
|||
| export const ElevatedTokenClaimsStruct = type({ | |||
There was a problem hiding this comment.
Maybe we could put the entire token as sensitive too? Just in case
(can be done in a follow-up too)

Explanation
Wires the MFA HTTP client into the SRP JWT auth SDK on top of the validation foundation.
mfa/servicescalls/api/v2/mfa/*(enroll, enroll complete, verify, verify complete, credentials), validates request/response bodies, maps server codes toMfaErrorsubclasses, and handles OTP cooldown /Retry-After.SRPJwtBearerAuthexposes begin/complete enrollment and verification, credential listing, and assertion-to-token exchange.JwtBearerAuthforwards those methods and rejects non-SRP auth types.Clients still go through
AuthenticationControllerin later PRs; this layer is not UI-facing.References
Depends on #10264. Follow-ups: #10266, #10267. Stack: stack #10268.
Related to: https://consensyssoftware.atlassian.net/browse/MUL-2261
Checklist
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 toMfaErrorsubclasses, and handles OTP cooldown plusRetry-After.SRPJwtBearerAuthand publicJwtBearerAuthexpose enrollment/step-up flows, credential listing, andexchangeMfaAssertion(AAL2 JWT → elevated access token via existing OIDC). MFA is SRP-only; email enrollment uses a separateemailoption fromentropySourceId.Schemas mark challenges, OTP codes, passkey payloads, and tokens with
sensitive(). Tests, nock fixtures, changelog, and SDK exports for MFA types are included.Reviewed by Cursor Bugbot for commit 56b3671. Bugbot is set up for automated code reviews on this repo. Configure here.