Skip to content

feat(profile-sync): add MFA validation foundation - #10264

Merged
mathieuartu merged 4 commits into
mainfrom
mfa/sdk-foundation
Sep 17, 2026
Merged

mathieuartu merged 4 commits into
mainfrom
mfa/sdk-foundation

Conversation

@mathieuartu

@mathieuartu mathieuartu commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

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. Follow-ups: #10265, #10266, #10267.
Related to: https://consensyssoftware.atlassian.net/browse/MUL-2260

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

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.

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

@mathieuartu
mathieuartu requested review from a team as code owners September 16, 2026 13:42
@mathieuartu
mathieuartu deployed to default-branch September 16, 2026 13:42 — with GitHub Actions Active
@mathieuartu
mathieuartu added this pull request to stack #10268 September 16, 2026 13:42
@mathieuartu mathieuartu self-assigned this Sep 16, 2026
@mathieuartu
mathieuartu force-pushed the mfa/sdk-foundation branch 2 times, most recently from ebd08b3 to 72c255a Compare September 16, 2026 20:13
Comment thread packages/profile-sync-controller/src/sdk/errors.test.ts Outdated
mathieuartu and others added 3 commits September 17, 2026 08:48
Co-authored-by: Cursor <cursoragent@cursor.com>
- Read aal/amr from top-level or Hydra ext claims
- Derive credential type from proof; add reason to completion requests
- Split mfa_identity_missing from flow-expired errors; add rate_limited
- Accept unknown credential statuses; drop unused description/BAD_REQUEST
- MfaUnavailableError no longer invents a 502 for transport failures

Co-authored-by: Cursor <cursoragent@cursor.com>
@mathieuartu
mathieuartu added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 42915dc Sep 17, 2026
136 checks passed
@mathieuartu
mathieuartu deleted the mfa/sdk-foundation branch September 17, 2026 13:39
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>
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