@getpara/core-sdk 3.4.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,7 +54,7 @@ import {
54
54
  import { waitForAuthStateChange } from "./utils/stateListener.js";
55
55
  import { warnOnce } from "./utils/deprecation.js";
56
56
  import { assertConfigAllowed } from "./utils/partnerConfigGating.js";
57
- import { TransactionReviewDenied, TransactionReviewTimeout } from "./errors.js";
57
+ import { SigningResultError, TransactionReviewDenied, TransactionReviewTimeout } from "./errors.js";
58
58
  import * as constants from "./constants.js";
59
59
  import { EnclaveClient } from "./shares/enclave.js";
60
60
  import { AuthService } from "./services/AuthService.js";
@@ -69,6 +69,24 @@ import { wrapWithSpan } from "./telemetry/tracer.js";
69
69
  import { setCurrentUserId } from "./telemetry/uxStateSpanProcessor.js";
70
70
  import { initTelemetry } from "./telemetry/init.js";
71
71
  import { recordActionOnSpan } from "./telemetry/uxAction.js";
72
+ const DEFAULT_SIGNING_RESULT_ERROR_MESSAGE = "Signing failed before a signature was produced.";
73
+ function getPendingTransactionId(signRes) {
74
+ return typeof signRes.pendingTransactionId === "string" && signRes.pendingTransactionId.length > 0 ? signRes.pendingTransactionId : void 0;
75
+ }
76
+ function requireSuccessfulSignatureResult(signRes) {
77
+ if (typeof signRes.signature === "string" && signRes.signature.length > 0) {
78
+ return { signature: signRes.signature };
79
+ }
80
+ throw new SigningResultError(
81
+ typeof signRes.message === "string" && signRes.message.length > 0 ? signRes.message : DEFAULT_SIGNING_RESULT_ERROR_MESSAGE,
82
+ {
83
+ code: signRes.code,
84
+ status: signRes.status,
85
+ responseURL: signRes.responseURL,
86
+ data: signRes.data
87
+ }
88
+ );
89
+ }
72
90
  const _ParaCore = class _ParaCore {
73
91
  constructor(envOrApiKey, apiKeyOrOpts, opts) {
74
92
  __privateAdd(this, _ParaCore_instances);
@@ -1770,6 +1788,37 @@ const _ParaCore = class _ParaCore {
1770
1788
  yield this.ctx.client.enable2FA(userId, verificationCode);
1771
1789
  });
1772
1790
  }
1791
+ /**
1792
+ * Login-time 2FA (ENG-6906): enroll a TOTP second factor for the current session.
1793
+ * Distinct from setup2fa/enable2fa, which configure the recovery-gating 2FA. This
1794
+ * is authenticated by the active session (no recovery config gate); the backend
1795
+ * 404s the surface when the login-MFA flag is off.
1796
+ *
1797
+ * @returns {Object} `{ uri, backupCodes }` — otpauth URI to render as a QR, plus
1798
+ * one-time backup codes to show the user exactly once.
1799
+ */
1800
+ enrollMfa() {
1801
+ return __async(this, null, function* () {
1802
+ yield __privateMethod(this, _ParaCore_instances, assertPartner_fn).call(this);
1803
+ return yield __privateGet(this, _authService).enrollMfa();
1804
+ });
1805
+ }
1806
+ /**
1807
+ * Login-time 2FA (ENG-6906): verify the second factor for the current login. On
1808
+ * success the backend stamps the session and this re-polls the login status so the
1809
+ * flow advances toward `done`; a wrong code resolves to `{ ok:false, attemptsRemaining }`
1810
+ * so the UI can re-prompt without treating it as a hard error.
1811
+ *
1812
+ * @param {Object} opts the options object.
1813
+ * @param {string} opts.code the TOTP or backup code entered by the user.
1814
+ * @returns {Object} `{ ok: true } | { ok: false; attemptsRemaining? }`
1815
+ */
1816
+ verifyMfa(_0) {
1817
+ return __async(this, arguments, function* ({ code }) {
1818
+ yield __privateMethod(this, _ParaCore_instances, assertPartner_fn).call(this);
1819
+ return yield __privateGet(this, _authService).verifyMfa({ code });
1820
+ });
1821
+ }
1773
1822
  /**
1774
1823
  * Resend a verification email for the current user.
1775
1824
  */
@@ -2331,6 +2380,14 @@ const _ParaCore = class _ParaCore {
2331
2380
  return result.url;
2332
2381
  });
2333
2382
  }
2383
+ getDeniedSignatureResultWithUrl(pendingTransactionId) {
2384
+ return __async(this, null, function* () {
2385
+ return {
2386
+ pendingTransactionId,
2387
+ transactionReviewUrl: yield this.getTransactionReviewUrl(pendingTransactionId)
2388
+ };
2389
+ });
2390
+ }
2334
2391
  getOnRampTransactionUrl(_0) {
2335
2392
  return __async(this, arguments, function* ({
2336
2393
  purchaseId
@@ -2393,11 +2450,9 @@ const _ParaCore = class _ParaCore {
2393
2450
  );
2394
2451
  let timeStart = Date.now();
2395
2452
  const effectiveTimeoutMs = Math.max(timeoutMs, constants.TRANSACTION_REVIEW_TIMEOUT_MS);
2396
- if (signRes.pendingTransactionId) {
2397
- const reviewUrl = yield this.getTransactionReviewUrl(
2398
- signRes.pendingTransactionId,
2399
- effectiveTimeoutMs
2400
- );
2453
+ let pendingTransactionId = getPendingTransactionId(signRes);
2454
+ if (pendingTransactionId) {
2455
+ const reviewUrl = yield this.getTransactionReviewUrl(pendingTransactionId, effectiveTimeoutMs);
2401
2456
  if (onTransactionReviewUrl) {
2402
2457
  onTransactionReviewUrl(reviewUrl);
2403
2458
  } else {
@@ -2406,8 +2461,9 @@ const _ParaCore = class _ParaCore {
2406
2461
  });
2407
2462
  }
2408
2463
  } else {
2409
- dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, signRes);
2410
- return signRes;
2464
+ const successfulSignRes2 = requireSuccessfulSignatureResult(signRes);
2465
+ dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, successfulSignRes2);
2466
+ return successfulSignRes2;
2411
2467
  }
2412
2468
  while (true) {
2413
2469
  if (isCanceled() || Date.now() - timeStart > effectiveTimeoutMs) {
@@ -2421,15 +2477,19 @@ const _ParaCore = class _ParaCore {
2421
2477
  "tx.review.poll",
2422
2478
  (span) => __async(this, null, function* () {
2423
2479
  var _a;
2424
- const res = (_a = (yield this.ctx.client.getPendingTransaction(this.userId, signRes.pendingTransactionId)).data) == null ? void 0 : _a.pendingTransaction;
2480
+ const res = (_a = (yield this.ctx.client.getPendingTransaction(this.userId, pendingTransactionId)).data) == null ? void 0 : _a.pendingTransaction;
2425
2481
  span.setAttribute("polling.status", (res == null ? void 0 : res.approvedAt) ? "approved" : "pending");
2426
2482
  return res;
2427
2483
  }),
2428
- { "pending_transaction.id": signRes.pendingTransactionId }
2484
+ { "pending_transaction.id": pendingTransactionId }
2429
2485
  );
2430
2486
  } catch (e) {
2431
2487
  const error = new TransactionReviewDenied();
2432
- dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, signRes, error.message);
2488
+ dispatchEvent(
2489
+ ParaEvent.SIGN_MESSAGE_EVENT,
2490
+ yield this.getDeniedSignatureResultWithUrl(pendingTransactionId),
2491
+ error.message
2492
+ );
2433
2493
  throw error;
2434
2494
  }
2435
2495
  if (!(pendingTransaction == null ? void 0 : pendingTransaction.approvedAt)) {
@@ -2444,16 +2504,16 @@ const _ParaCore = class _ParaCore {
2444
2504
  break;
2445
2505
  }
2446
2506
  }
2447
- if (signRes.pendingTransactionId) {
2448
- const error = new TransactionReviewTimeout(
2449
- yield this.getTransactionReviewUrl(signRes.pendingTransactionId),
2450
- signRes.pendingTransactionId
2451
- );
2452
- dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, signRes, error.message);
2507
+ pendingTransactionId = getPendingTransactionId(signRes);
2508
+ if (pendingTransactionId) {
2509
+ const deniedSignRes = yield this.getDeniedSignatureResultWithUrl(pendingTransactionId);
2510
+ const error = new TransactionReviewTimeout(deniedSignRes.transactionReviewUrl, pendingTransactionId);
2511
+ dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, deniedSignRes, error.message);
2453
2512
  throw error;
2454
2513
  }
2455
- dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, signRes);
2456
- return signRes;
2514
+ const successfulSignRes = requireSuccessfulSignatureResult(signRes);
2515
+ dispatchEvent(ParaEvent.SIGN_MESSAGE_EVENT, successfulSignRes);
2516
+ return successfulSignRes;
2457
2517
  });
2458
2518
  }
2459
2519
  signMessageInner(_0) {
@@ -2532,19 +2592,18 @@ const _ParaCore = class _ParaCore {
2532
2592
  );
2533
2593
  let timeStart = Date.now();
2534
2594
  const effectiveTimeoutMs = Math.max(timeoutMs, constants.TRANSACTION_REVIEW_TIMEOUT_MS);
2535
- if (signRes.pendingTransactionId) {
2536
- const reviewUrl = yield this.getTransactionReviewUrl(
2537
- signRes.pendingTransactionId,
2538
- effectiveTimeoutMs
2539
- );
2595
+ let pendingTransactionId = getPendingTransactionId(signRes);
2596
+ if (pendingTransactionId) {
2597
+ const reviewUrl = yield this.getTransactionReviewUrl(pendingTransactionId, effectiveTimeoutMs);
2540
2598
  if (onTransactionReviewUrl) {
2541
2599
  onTransactionReviewUrl(reviewUrl);
2542
2600
  } else {
2543
2601
  yield this.platformUtils.openPopup(reviewUrl, { type: PopupType.SIGN_TRANSACTION_REVIEW });
2544
2602
  }
2545
2603
  } else {
2546
- dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, signRes);
2547
- return signRes;
2604
+ const successfulSignRes2 = requireSuccessfulSignatureResult(signRes);
2605
+ dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, successfulSignRes2);
2606
+ return successfulSignRes2;
2548
2607
  }
2549
2608
  while (true) {
2550
2609
  if (isCanceled() || Date.now() - timeStart > effectiveTimeoutMs) {
@@ -2552,7 +2611,7 @@ const _ParaCore = class _ParaCore {
2552
2611
  break;
2553
2612
  }
2554
2613
  yield new Promise((resolve) => setTimeout(resolve, constants.POLLING_INTERVAL_MS));
2555
- const pendingTxId = signRes.pendingTransactionId;
2614
+ const pendingTxId = pendingTransactionId;
2556
2615
  let pendingTransaction;
2557
2616
  try {
2558
2617
  pendingTransaction = yield wrapWithSpan(
@@ -2567,7 +2626,11 @@ const _ParaCore = class _ParaCore {
2567
2626
  );
2568
2627
  } catch (e) {
2569
2628
  const error = new TransactionReviewDenied();
2570
- dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, signRes, error.message);
2629
+ dispatchEvent(
2630
+ ParaEvent.SIGN_TRANSACTION_EVENT,
2631
+ yield this.getDeniedSignatureResultWithUrl(pendingTxId),
2632
+ error.message
2633
+ );
2571
2634
  throw error;
2572
2635
  }
2573
2636
  if (!(pendingTransaction == null ? void 0 : pendingTransaction.approvedAt)) {
@@ -2591,16 +2654,16 @@ const _ParaCore = class _ParaCore {
2591
2654
  break;
2592
2655
  }
2593
2656
  }
2594
- if (signRes.pendingTransactionId) {
2595
- const error = new TransactionReviewTimeout(
2596
- yield this.getTransactionReviewUrl(signRes.pendingTransactionId),
2597
- signRes.pendingTransactionId
2598
- );
2599
- dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, signRes, error.message);
2657
+ pendingTransactionId = getPendingTransactionId(signRes);
2658
+ if (pendingTransactionId) {
2659
+ const deniedSignRes = yield this.getDeniedSignatureResultWithUrl(pendingTransactionId);
2660
+ const error = new TransactionReviewTimeout(deniedSignRes.transactionReviewUrl, pendingTransactionId);
2661
+ dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, deniedSignRes, error.message);
2600
2662
  throw error;
2601
2663
  }
2602
- dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, signRes);
2603
- return signRes;
2664
+ const successfulSignRes = requireSuccessfulSignatureResult(signRes);
2665
+ dispatchEvent(ParaEvent.SIGN_TRANSACTION_EVENT, successfulSignRes);
2666
+ return successfulSignRes;
2604
2667
  });
2605
2668
  }
2606
2669
  isProviderModalDisabled() {
@@ -1,5 +1,5 @@
1
1
  import "./chunk-7B52C2XE.js";
2
- const PARA_CORE_VERSION = "3.4.0";
2
+ const PARA_CORE_VERSION = "3.5.1";
3
3
  const PREFIX = "@CAPSULE/";
4
4
  const PARA_PREFIX = "@PARA/";
5
5
  const LOCAL_STORAGE_AUTH_INFO = `${PREFIX}authInfo`;
@@ -20,6 +20,16 @@ class TransactionReviewTimeout extends Error {
20
20
  this.pendingTransactionId = pendingTransactionId;
21
21
  }
22
22
  }
23
+ class SigningResultError extends Error {
24
+ constructor(message, options = {}) {
25
+ super(message);
26
+ this.name = "SigningResultError";
27
+ this.code = options.code;
28
+ this.status = options.status;
29
+ this.responseURL = options.responseURL;
30
+ this.data = options.data;
31
+ }
32
+ }
23
33
  class PartnerConfigError extends Error {
24
34
  constructor(code, message, detail) {
25
35
  super(message);
@@ -30,6 +40,7 @@ class PartnerConfigError extends Error {
30
40
  }
31
41
  export {
32
42
  PartnerConfigError,
43
+ SigningResultError,
33
44
  TransactionReviewDenied,
34
45
  TransactionReviewError,
35
46
  TransactionReviewTimeout
@@ -169,6 +169,11 @@ class AuthService {
169
169
  JSON.stringify(isSLOPossible)
170
170
  );
171
171
  return serverAuthState;
172
+ // No preparation needed
173
+ case "mfa":
174
+ return serverAuthState;
175
+ default:
176
+ return serverAuthState;
172
177
  }
173
178
  });
174
179
  this.prepareLoginState = (_0, _1) => __async(this, [_0, _1], function* (loginState, { useShortUrls: shorten = false, portalTheme, sessionLookupId }) {
@@ -420,10 +425,46 @@ class AuthService {
420
425
  return yield waitForAuthStateChange({
421
426
  stateManager: __privateGet(this, _stateManager),
422
427
  resolvePhases: [
423
- { phase: "awaiting_session_start", onPhase: (state) => state.authStateResult }
428
+ { phase: "awaiting_session_start", onPhase: (state) => state.authStateResult },
429
+ // Login-time 2FA (ENG-6906): the OAuth poll surfaced an mfa challenge instead
430
+ // of completing. Resolve here so the caller stops blocking; the challenge is
431
+ // read from the consumer auth state (AuthStateInfo.mfa) and satisfied via the
432
+ // enroll/verify hooks, which fire RESOLVE_2FA to continue the flow.
433
+ { phase: "awaiting_2fa", onPhase: (state) => state.authStateResult },
434
+ { phase: "awaiting_2fa_enrollment", onPhase: (state) => state.authStateResult }
424
435
  ]
425
436
  });
426
437
  });
438
+ /**
439
+ * Login-time 2FA (ENG-6906): after the second factor has been satisfied/enrolled
440
+ * server-side, re-poll the login status to advance the flow. Fires RESOLVE_2FA,
441
+ * which re-enters verify-oauth polling (now returning stage:'done') and routes
442
+ * onward to awaiting_session_start / authenticated.
443
+ */
444
+ this.resolveMfa = (pollingCallbacks) => __async(this, null, function* () {
445
+ __privateGet(this, _stateManager).send({ type: "RESOLVE_2FA", data: pollingCallbacks });
446
+ return yield waitForAuthStateChange({
447
+ stateManager: __privateGet(this, _stateManager),
448
+ resolvePhases: [
449
+ { phase: "awaiting_session_start", onPhase: (state) => state.authStateResult },
450
+ { phase: "authenticated", onPhase: (state) => state.authStateResult }
451
+ ],
452
+ // If re-polling after the second factor lands in the auth error phase, reject fast
453
+ // with a clear message instead of stalling until the listener's default timeout. The
454
+ // caller stays parked at awaiting_2fa and can re-prompt/retry. (ENG-6906)
455
+ rejectPhases: [{ phase: "error", onPhase: () => new Error("Login 2FA resolution failed") }]
456
+ });
457
+ });
458
+ this.enrollMfa = () => __async(this, null, function* () {
459
+ return yield __privateGet(this, _paraCoreInterface).ctx.client.enrollMfa();
460
+ });
461
+ this.verifyMfa = (_0) => __async(this, [_0], function* ({ code }) {
462
+ const result = yield __privateGet(this, _paraCoreInterface).ctx.client.verifyMfa(code);
463
+ if (result.ok) {
464
+ yield this.resolveMfa();
465
+ }
466
+ return result;
467
+ });
427
468
  this.authenticateWithOAuth = (params) => __async(this, null, function* () {
428
469
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
429
470
  switch (params.method) {
@@ -80,7 +80,7 @@ const _CoreStateManager = class _CoreStateManager {
80
80
  * Extracts all data needed by UI consumers from authStateResult.
81
81
  */
82
82
  computeAuthStateInfo(authContext, walletContext) {
83
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L;
83
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q;
84
84
  const defaultAuthStateInfo = {
85
85
  userId: null,
86
86
  isPasskeySupported: false,
@@ -102,6 +102,7 @@ const _CoreStateManager = class _CoreStateManager {
102
102
  fallbackUsed: false,
103
103
  fallbackChannel: null,
104
104
  externalWalletVerification: null,
105
+ mfa: null,
105
106
  recoverySecret: null,
106
107
  isNewUser: false
107
108
  };
@@ -143,6 +144,7 @@ const _CoreStateManager = class _CoreStateManager {
143
144
  let deliveryChannel = null;
144
145
  let fallbackUsed = false;
145
146
  let fallbackChannel = null;
147
+ let mfa = null;
146
148
  if (stage === "login") {
147
149
  const loginState = authStateResult;
148
150
  const authMethods = (_j = loginState.loginAuthMethods) != null ? _j : [];
@@ -179,6 +181,17 @@ const _CoreStateManager = class _CoreStateManager {
179
181
  deliveryChannel = (_E = verifyState.deliveryChannel) != null ? _E : null;
180
182
  fallbackUsed = (_F = verifyState.fallbackUsed) != null ? _F : false;
181
183
  fallbackChannel = (_G = verifyState.fallbackChannel) != null ? _G : null;
184
+ } else if (stage === "mfa") {
185
+ const { mfa: challenge } = authStateResult;
186
+ mfa = {
187
+ mode: challenge.mode,
188
+ step: challenge.step,
189
+ methods: (_H = challenge.methods) != null ? _H : null,
190
+ attemptsRemaining: (_I = challenge.attemptsRemaining) != null ? _I : null,
191
+ lockedUntil: (_J = challenge.lockedUntil) != null ? _J : null,
192
+ secretUri: (_K = challenge.secretUri) != null ? _K : null,
193
+ backupCodes: (_L = challenge.backupCodes) != null ? _L : null
194
+ };
182
195
  }
183
196
  return {
184
197
  userId,
@@ -201,10 +214,11 @@ const _CoreStateManager = class _CoreStateManager {
201
214
  fallbackUsed,
202
215
  fallbackChannel,
203
216
  externalWalletVerification: externalWalletVerification ? {
204
- signatureVerificationMessage: (_H = externalWalletVerification.signatureVerificationMessage) != null ? _H : "",
205
- walletAddress: (_J = (_I = externalWalletVerification.externalWallet) == null ? void 0 : _I.address) != null ? _J : "",
206
- walletType: (_L = (_K = externalWalletVerification.externalWallet) == null ? void 0 : _K.type) != null ? _L : ""
217
+ signatureVerificationMessage: (_M = externalWalletVerification.signatureVerificationMessage) != null ? _M : "",
218
+ walletAddress: (_O = (_N = externalWalletVerification.externalWallet) == null ? void 0 : _N.address) != null ? _O : "",
219
+ walletType: (_Q = (_P = externalWalletVerification.externalWallet) == null ? void 0 : _P.type) != null ? _Q : ""
207
220
  } : null,
221
+ mfa,
208
222
  recoverySecret,
209
223
  isNewUser
210
224
  };
@@ -265,10 +279,19 @@ const _CoreStateManager = class _CoreStateManager {
265
279
  }
266
280
  authStateInfoEqual(a, b) {
267
281
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
268
- return a.userId === b.userId && a.isPasskeySupported === b.isPasskeySupported && a.hasPasskey === b.hasPasskey && a.hasPassword === b.hasPassword && a.hasPin === b.hasPin && a.passkeyUrl === b.passkeyUrl && a.passkeyFullUrl === b.passkeyFullUrl && a.passkeyKnownDeviceUrl === b.passkeyKnownDeviceUrl && a.passwordUrl === b.passwordUrl && a.passwordFullUrl === b.passwordFullUrl && a.pinUrl === b.pinUrl && a.pinFullUrl === b.pinFullUrl && a.passkeyId === b.passkeyId && a.verificationUrl === b.verificationUrl && a.verificationFullUrl === b.verificationFullUrl && a.deliveryChannel === b.deliveryChannel && a.fallbackUsed === b.fallbackUsed && a.fallbackChannel === b.fallbackChannel && a.recoverySecret === b.recoverySecret && a.isNewUser === b.isNewUser && ((_a = a.externalWalletVerification) == null ? void 0 : _a.signatureVerificationMessage) === ((_b = b.externalWalletVerification) == null ? void 0 : _b.signatureVerificationMessage) && ((_c = a.externalWalletVerification) == null ? void 0 : _c.walletAddress) === ((_d = b.externalWalletVerification) == null ? void 0 : _d.walletAddress) && ((_e = a.externalWalletVerification) == null ? void 0 : _e.walletType) === ((_f = b.externalWalletVerification) == null ? void 0 : _f.walletType) && ((_g = a.passkeyHints) == null ? void 0 : _g.length) === ((_h = b.passkeyHints) == null ? void 0 : _h.length) && ((_i = a.passkeyHints) != null ? _i : []).every(
282
+ return a.userId === b.userId && a.isPasskeySupported === b.isPasskeySupported && a.hasPasskey === b.hasPasskey && a.hasPassword === b.hasPassword && a.hasPin === b.hasPin && a.passkeyUrl === b.passkeyUrl && a.passkeyFullUrl === b.passkeyFullUrl && a.passkeyKnownDeviceUrl === b.passkeyKnownDeviceUrl && a.passwordUrl === b.passwordUrl && a.passwordFullUrl === b.passwordFullUrl && a.pinUrl === b.pinUrl && a.pinFullUrl === b.pinFullUrl && a.passkeyId === b.passkeyId && a.verificationUrl === b.verificationUrl && a.verificationFullUrl === b.verificationFullUrl && a.deliveryChannel === b.deliveryChannel && a.fallbackUsed === b.fallbackUsed && a.fallbackChannel === b.fallbackChannel && a.recoverySecret === b.recoverySecret && a.isNewUser === b.isNewUser && ((_a = a.externalWalletVerification) == null ? void 0 : _a.signatureVerificationMessage) === ((_b = b.externalWalletVerification) == null ? void 0 : _b.signatureVerificationMessage) && ((_c = a.externalWalletVerification) == null ? void 0 : _c.walletAddress) === ((_d = b.externalWalletVerification) == null ? void 0 : _d.walletAddress) && ((_e = a.externalWalletVerification) == null ? void 0 : _e.walletType) === ((_f = b.externalWalletVerification) == null ? void 0 : _f.walletType) && this.mfaEqual(a.mfa, b.mfa) && ((_g = a.passkeyHints) == null ? void 0 : _g.length) === ((_h = b.passkeyHints) == null ? void 0 : _h.length) && ((_i = a.passkeyHints) != null ? _i : []).every(
269
283
  (hint, i) => hint.useragent === b.passkeyHints[i].useragent && hint.aaguid === b.passkeyHints[i].aaguid
270
284
  );
271
285
  }
286
+ mfaEqual(a, b) {
287
+ if (a === b) return true;
288
+ if (!a || !b) return false;
289
+ const sameStringArray = (x, y) => {
290
+ var _a, _b;
291
+ return ((_a = x == null ? void 0 : x.length) != null ? _a : -1) === ((_b = y == null ? void 0 : y.length) != null ? _b : -1) && (x != null ? x : []).every((v, i) => v === y[i]);
292
+ };
293
+ return a.mode === b.mode && a.step === b.step && a.attemptsRemaining === b.attemptsRemaining && a.lockedUntil === b.lockedUntil && a.secretUri === b.secretUri && sameStringArray(a.methods, b.methods) && sameStringArray(a.backupCodes, b.backupCodes);
294
+ }
272
295
  };
273
296
  _CoreStateManager.CORE_LOADING_STATES = /* @__PURE__ */ new Set([
274
297
  "setting_up",
@@ -282,7 +305,11 @@ _CoreStateManager.AUTH_LOADING_STATES = /* @__PURE__ */ new Set([
282
305
  "authenticating_farcaster",
283
306
  "processing_authentication",
284
307
  "authenticating_telegram",
285
- "checking_state"
308
+ "checking_state",
309
+ // Re-polling the login status after the second factor is satisfied (ENG-6906).
310
+ // awaiting_2fa / awaiting_2fa_enrollment are deliberately NOT loading — they are
311
+ // user-input holds where the UI shows the MFA prompt.
312
+ "resolving_2fa"
286
313
  ]);
287
314
  _CoreStateManager.WALLET_LOADING_STATES = /* @__PURE__ */ new Set([
288
315
  "checking_wallet_state",
@@ -712,18 +712,18 @@ function createAuthStateMachine(paraCoreInterface) {
712
712
  guard: and([
713
713
  ({ event }) => {
714
714
  const output = event.output;
715
- return !!output.externalWallet;
715
+ return !!(output == null ? void 0 : output.externalWallet);
716
716
  },
717
717
  or([
718
718
  ({ event }) => {
719
719
  var _a;
720
720
  const output = event.output;
721
- return output.stage === "done" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
721
+ return (output == null ? void 0 : output.stage) === "done" && !((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
722
722
  },
723
723
  ({ event }) => {
724
724
  var _a, _b;
725
725
  const output = event.output;
726
- return output.stage === "verify" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth) && !((_b = output.externalWallet) == null ? void 0 : _b.withVerification);
726
+ return (output == null ? void 0 : output.stage) === "verify" && !((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth) && !((_b = output == null ? void 0 : output.externalWallet) == null ? void 0 : _b.withVerification);
727
727
  }
728
728
  ])
729
729
  ])
@@ -740,12 +740,12 @@ function createAuthStateMachine(paraCoreInterface) {
740
740
  guard: and([
741
741
  ({ event }) => {
742
742
  const output = event.output;
743
- return !!output.externalWallet;
743
+ return !!(output == null ? void 0 : output.externalWallet);
744
744
  },
745
745
  ({ event }) => {
746
746
  var _a, _b;
747
747
  const output = event.output;
748
- return output.stage === "verify" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth) && ((_b = output.externalWallet) == null ? void 0 : _b.withVerification);
748
+ return (output == null ? void 0 : output.stage) === "verify" && !((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth) && ((_b = output == null ? void 0 : output.externalWallet) == null ? void 0 : _b.withVerification);
749
749
  }
750
750
  ])
751
751
  },
@@ -761,12 +761,12 @@ function createAuthStateMachine(paraCoreInterface) {
761
761
  guard: and([
762
762
  ({ event }) => {
763
763
  const output = event.output;
764
- return !!output.externalWallet;
764
+ return !!(output == null ? void 0 : output.externalWallet);
765
765
  },
766
766
  ({ event }) => {
767
767
  var _a;
768
768
  const output = event.output;
769
- return output.stage === "verify" && !!((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
769
+ return (output == null ? void 0 : output.stage) === "verify" && !!((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
770
770
  }
771
771
  ])
772
772
  },
@@ -780,11 +780,37 @@ function createAuthStateMachine(paraCoreInterface) {
780
780
  ({ event }) => {
781
781
  var _a;
782
782
  const output = event.output;
783
- return output.stage === "verify" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
783
+ return (output == null ? void 0 : output.stage) === "verify" && !((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
784
784
  },
785
785
  () => !paraCoreInterface.authService.isEnclaveUser
786
786
  ])
787
787
  },
788
+ // Login-time 2FA (ENG-6906): the server gated the login behind a second
789
+ // factor. Enrollment (no factor yet) and verification (existing factor)
790
+ // are surfaced as distinct phases so a custom UI can show the right step.
791
+ {
792
+ target: "awaiting_2fa_enrollment",
793
+ actions: [
794
+ "storeAuthStateResult",
795
+ { type: "logTransition", params: { to: "awaiting_2fa_enrollment", detail: "mfa enroll" } }
796
+ ],
797
+ guard: ({ event }) => {
798
+ var _a;
799
+ const output = event.output;
800
+ return (output == null ? void 0 : output.stage) === "mfa" && ((_a = output == null ? void 0 : output.mfa) == null ? void 0 : _a.mode) === "enroll";
801
+ }
802
+ },
803
+ {
804
+ target: "awaiting_2fa",
805
+ actions: [
806
+ "storeAuthStateResult",
807
+ { type: "logTransition", params: { to: "awaiting_2fa", detail: "mfa verify" } }
808
+ ],
809
+ guard: ({ event }) => {
810
+ const output = event.output;
811
+ return (output == null ? void 0 : output.stage) === "mfa";
812
+ }
813
+ },
788
814
  {
789
815
  target: "awaiting_session_start",
790
816
  actions: [
@@ -827,6 +853,77 @@ function createAuthStateMachine(paraCoreInterface) {
827
853
  CANCEL: { target: "unauthenticated", actions: ["resetState"] }
828
854
  }
829
855
  },
856
+ // Login-time 2FA hold states (ENG-6906). The SDK satisfies the second factor
857
+ // out-of-band (enrollMfa/verifyMfa against /internal/v1/auth/2fa/*); once the
858
+ // backend has stamped the session, a RESOLVE_2FA event re-polls the login
859
+ // status, which now returns stage:'done'.
860
+ awaiting_2fa: {
861
+ on: {
862
+ RESOLVE_2FA: { target: "resolving_2fa", actions: ["setPollingCallbacks", "resetError"] },
863
+ CANCEL: { target: "unauthenticated", actions: ["resetState"] }
864
+ }
865
+ },
866
+ awaiting_2fa_enrollment: {
867
+ on: {
868
+ RESOLVE_2FA: { target: "resolving_2fa", actions: ["setPollingCallbacks", "resetError"] },
869
+ CANCEL: { target: "unauthenticated", actions: ["resetState"] }
870
+ }
871
+ },
872
+ resolving_2fa: {
873
+ entry: [{ type: "logTransition", params: { to: "resolving_2fa" } }],
874
+ invoke: {
875
+ src: "polling",
876
+ input: ({ context }) => {
877
+ var _a;
878
+ let resolvedAuthState = null;
879
+ return {
880
+ checkCondition: () => __async(this, null, function* () {
881
+ try {
882
+ const result = yield paraCoreInterface.pollingService.waitForOAuth({
883
+ onSuccess: (serverAuthState) => {
884
+ resolvedAuthState = serverAuthState;
885
+ }
886
+ })();
887
+ return { finished: result.finished && !!resolvedAuthState, data: { authState: resolvedAuthState } };
888
+ } catch (error) {
889
+ throw new Error(`2FA resolution polling failed: ${extractErrorMessage(error, "Unknown error")}`);
890
+ }
891
+ }),
892
+ onPoll: () => {
893
+ var _a2, _b;
894
+ return (_b = (_a2 = context.pollingCallbacks) == null ? void 0 : _a2.onPoll) == null ? void 0 : _b.call(_a2);
895
+ },
896
+ onCancel: () => {
897
+ var _a2, _b;
898
+ return (_b = (_a2 = context.pollingCallbacks) == null ? void 0 : _a2.onCancel) == null ? void 0 : _b.call(_a2);
899
+ },
900
+ isCanceled: ((_a = context.pollingCallbacks) == null ? void 0 : _a.isCanceled) || (() => false),
901
+ id: "resolving2fa"
902
+ };
903
+ },
904
+ onError: {
905
+ target: "error",
906
+ actions: setAuthErrorAssign("2FA resolution failed")
907
+ }
908
+ },
909
+ on: makePollingOnHandlers("processing_authentication", "2FA resolution failed", [
910
+ assign({
911
+ serverAuthStateResult: ({ event }) => {
912
+ var _a;
913
+ const authState = ((_a = event.data) == null ? void 0 : _a.authState) || null;
914
+ return {
915
+ authState,
916
+ opts: DEFAULT_AUTH_OPTS
917
+ };
918
+ },
919
+ isNewUser: ({ event }) => {
920
+ var _a;
921
+ const authState = (_a = event.data) == null ? void 0 : _a.authState;
922
+ return authState ? computeIsNewUser(authState) : false;
923
+ }
924
+ })
925
+ ])
926
+ },
830
927
  awaiting_session_start: {
831
928
  on: {
832
929
  WAIT_FOR_SESSION: {
@@ -180,6 +180,14 @@ function createCoreStateMachine(paraCoreInterface) {
180
180
  "WAIT_FOR_WALLET_CREATION": {
181
181
  actions: ({ context, event }) => context.authMachineRef.send(event)
182
182
  },
183
+ // Login-time 2FA (ENG-6906): after the second factor is satisfied, verifyMfa
184
+ // fires RESOLVE_2FA so the auth machine re-polls verify-oauth and advances the
185
+ // login. Without forwarding it here the parent silently drops the event, the
186
+ // auth child stays parked at awaiting_2fa[_enrollment], and the user never gets
187
+ // a wallet.
188
+ "RESOLVE_2FA": {
189
+ actions: ({ context, event }) => context.authMachineRef.send(event)
190
+ },
183
191
  "INITIALIZE_GUEST_MODE": {
184
192
  actions: ({ context, event }) => context.authMachineRef.send(event)
185
193
  },
@@ -17,6 +17,8 @@ const PARA_CORE_METHODS = [
17
17
  "setup2fa",
18
18
  "enable2fa",
19
19
  "verify2fa",
20
+ "enrollMfa",
21
+ "verifyMfa",
20
22
  "logout",
21
23
  "clearStorage",
22
24
  "isSessionActive",