@getpara/core-sdk 3.4.0 → 3.5.0

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.
@@ -1825,6 +1825,37 @@ const _ParaCore = class _ParaCore {
1825
1825
  yield this.ctx.client.enable2FA(userId, verificationCode);
1826
1826
  });
1827
1827
  }
1828
+ /**
1829
+ * Login-time 2FA (ENG-6906): enroll a TOTP second factor for the current session.
1830
+ * Distinct from setup2fa/enable2fa, which configure the recovery-gating 2FA. This
1831
+ * is authenticated by the active session (no recovery config gate); the backend
1832
+ * 404s the surface when the login-MFA flag is off.
1833
+ *
1834
+ * @returns {Object} `{ uri, backupCodes }` — otpauth URI to render as a QR, plus
1835
+ * one-time backup codes to show the user exactly once.
1836
+ */
1837
+ enrollMfa() {
1838
+ return __async(this, null, function* () {
1839
+ yield __privateMethod(this, _ParaCore_instances, assertPartner_fn).call(this);
1840
+ return yield __privateGet(this, _authService).enrollMfa();
1841
+ });
1842
+ }
1843
+ /**
1844
+ * Login-time 2FA (ENG-6906): verify the second factor for the current login. On
1845
+ * success the backend stamps the session and this re-polls the login status so the
1846
+ * flow advances toward `done`; a wrong code resolves to `{ ok:false, attemptsRemaining }`
1847
+ * so the UI can re-prompt without treating it as a hard error.
1848
+ *
1849
+ * @param {Object} opts the options object.
1850
+ * @param {string} opts.code the TOTP or backup code entered by the user.
1851
+ * @returns {Object} `{ ok: true } | { ok: false; attemptsRemaining? }`
1852
+ */
1853
+ verifyMfa(_0) {
1854
+ return __async(this, arguments, function* ({ code }) {
1855
+ yield __privateMethod(this, _ParaCore_instances, assertPartner_fn).call(this);
1856
+ return yield __privateGet(this, _authService).verifyMfa({ code });
1857
+ });
1858
+ }
1828
1859
  /**
1829
1860
  * Resend a verification email for the current user.
1830
1861
  */
@@ -46,7 +46,7 @@ __export(constants_exports, {
46
46
  TRANSACTION_REVIEW_TIMEOUT_MS: () => TRANSACTION_REVIEW_TIMEOUT_MS
47
47
  });
48
48
  module.exports = __toCommonJS(constants_exports);
49
- const PARA_CORE_VERSION = "3.4.0";
49
+ const PARA_CORE_VERSION = "3.5.0";
50
50
  const PREFIX = "@CAPSULE/";
51
51
  const PARA_PREFIX = "@PARA/";
52
52
  const LOCAL_STORAGE_AUTH_INFO = `${PREFIX}authInfo`;
@@ -238,6 +238,11 @@ class AuthService {
238
238
  JSON.stringify(isSLOPossible)
239
239
  );
240
240
  return serverAuthState;
241
+ // No preparation needed
242
+ case "mfa":
243
+ return serverAuthState;
244
+ default:
245
+ return serverAuthState;
241
246
  }
242
247
  });
243
248
  this.prepareLoginState = (_0, _1) => __async(this, [_0, _1], function* (loginState, { useShortUrls: shorten = false, portalTheme, sessionLookupId }) {
@@ -489,10 +494,46 @@ class AuthService {
489
494
  return yield (0, import_stateListener.waitForAuthStateChange)({
490
495
  stateManager: __privateGet(this, _stateManager),
491
496
  resolvePhases: [
492
- { phase: "awaiting_session_start", onPhase: (state) => state.authStateResult }
497
+ { phase: "awaiting_session_start", onPhase: (state) => state.authStateResult },
498
+ // Login-time 2FA (ENG-6906): the OAuth poll surfaced an mfa challenge instead
499
+ // of completing. Resolve here so the caller stops blocking; the challenge is
500
+ // read from the consumer auth state (AuthStateInfo.mfa) and satisfied via the
501
+ // enroll/verify hooks, which fire RESOLVE_2FA to continue the flow.
502
+ { phase: "awaiting_2fa", onPhase: (state) => state.authStateResult },
503
+ { phase: "awaiting_2fa_enrollment", onPhase: (state) => state.authStateResult }
493
504
  ]
494
505
  });
495
506
  });
507
+ /**
508
+ * Login-time 2FA (ENG-6906): after the second factor has been satisfied/enrolled
509
+ * server-side, re-poll the login status to advance the flow. Fires RESOLVE_2FA,
510
+ * which re-enters verify-oauth polling (now returning stage:'done') and routes
511
+ * onward to awaiting_session_start / authenticated.
512
+ */
513
+ this.resolveMfa = (pollingCallbacks) => __async(this, null, function* () {
514
+ __privateGet(this, _stateManager).send({ type: "RESOLVE_2FA", data: pollingCallbacks });
515
+ return yield (0, import_stateListener.waitForAuthStateChange)({
516
+ stateManager: __privateGet(this, _stateManager),
517
+ resolvePhases: [
518
+ { phase: "awaiting_session_start", onPhase: (state) => state.authStateResult },
519
+ { phase: "authenticated", onPhase: (state) => state.authStateResult }
520
+ ],
521
+ // If re-polling after the second factor lands in the auth error phase, reject fast
522
+ // with a clear message instead of stalling until the listener's default timeout. The
523
+ // caller stays parked at awaiting_2fa and can re-prompt/retry. (ENG-6906)
524
+ rejectPhases: [{ phase: "error", onPhase: () => new Error("Login 2FA resolution failed") }]
525
+ });
526
+ });
527
+ this.enrollMfa = () => __async(this, null, function* () {
528
+ return yield __privateGet(this, _paraCoreInterface).ctx.client.enrollMfa();
529
+ });
530
+ this.verifyMfa = (_0) => __async(this, [_0], function* ({ code }) {
531
+ const result = yield __privateGet(this, _paraCoreInterface).ctx.client.verifyMfa(code);
532
+ if (result.ok) {
533
+ yield this.resolveMfa();
534
+ }
535
+ return result;
536
+ });
496
537
  this.authenticateWithOAuth = (params) => __async(this, null, function* () {
497
538
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
498
539
  switch (params.method) {
@@ -115,7 +115,7 @@ const _CoreStateManager = class _CoreStateManager {
115
115
  * Extracts all data needed by UI consumers from authStateResult.
116
116
  */
117
117
  computeAuthStateInfo(authContext, walletContext) {
118
- 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;
118
+ 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;
119
119
  const defaultAuthStateInfo = {
120
120
  userId: null,
121
121
  isPasskeySupported: false,
@@ -137,6 +137,7 @@ const _CoreStateManager = class _CoreStateManager {
137
137
  fallbackUsed: false,
138
138
  fallbackChannel: null,
139
139
  externalWalletVerification: null,
140
+ mfa: null,
140
141
  recoverySecret: null,
141
142
  isNewUser: false
142
143
  };
@@ -178,6 +179,7 @@ const _CoreStateManager = class _CoreStateManager {
178
179
  let deliveryChannel = null;
179
180
  let fallbackUsed = false;
180
181
  let fallbackChannel = null;
182
+ let mfa = null;
181
183
  if (stage === "login") {
182
184
  const loginState = authStateResult;
183
185
  const authMethods = (_j = loginState.loginAuthMethods) != null ? _j : [];
@@ -214,6 +216,17 @@ const _CoreStateManager = class _CoreStateManager {
214
216
  deliveryChannel = (_E = verifyState.deliveryChannel) != null ? _E : null;
215
217
  fallbackUsed = (_F = verifyState.fallbackUsed) != null ? _F : false;
216
218
  fallbackChannel = (_G = verifyState.fallbackChannel) != null ? _G : null;
219
+ } else if (stage === "mfa") {
220
+ const { mfa: challenge } = authStateResult;
221
+ mfa = {
222
+ mode: challenge.mode,
223
+ step: challenge.step,
224
+ methods: (_H = challenge.methods) != null ? _H : null,
225
+ attemptsRemaining: (_I = challenge.attemptsRemaining) != null ? _I : null,
226
+ lockedUntil: (_J = challenge.lockedUntil) != null ? _J : null,
227
+ secretUri: (_K = challenge.secretUri) != null ? _K : null,
228
+ backupCodes: (_L = challenge.backupCodes) != null ? _L : null
229
+ };
217
230
  }
218
231
  return {
219
232
  userId,
@@ -236,10 +249,11 @@ const _CoreStateManager = class _CoreStateManager {
236
249
  fallbackUsed,
237
250
  fallbackChannel,
238
251
  externalWalletVerification: externalWalletVerification ? {
239
- signatureVerificationMessage: (_H = externalWalletVerification.signatureVerificationMessage) != null ? _H : "",
240
- walletAddress: (_J = (_I = externalWalletVerification.externalWallet) == null ? void 0 : _I.address) != null ? _J : "",
241
- walletType: (_L = (_K = externalWalletVerification.externalWallet) == null ? void 0 : _K.type) != null ? _L : ""
252
+ signatureVerificationMessage: (_M = externalWalletVerification.signatureVerificationMessage) != null ? _M : "",
253
+ walletAddress: (_O = (_N = externalWalletVerification.externalWallet) == null ? void 0 : _N.address) != null ? _O : "",
254
+ walletType: (_Q = (_P = externalWalletVerification.externalWallet) == null ? void 0 : _P.type) != null ? _Q : ""
242
255
  } : null,
256
+ mfa,
243
257
  recoverySecret,
244
258
  isNewUser
245
259
  };
@@ -300,10 +314,19 @@ const _CoreStateManager = class _CoreStateManager {
300
314
  }
301
315
  authStateInfoEqual(a, b) {
302
316
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
303
- 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(
317
+ 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(
304
318
  (hint, i) => hint.useragent === b.passkeyHints[i].useragent && hint.aaguid === b.passkeyHints[i].aaguid
305
319
  );
306
320
  }
321
+ mfaEqual(a, b) {
322
+ if (a === b) return true;
323
+ if (!a || !b) return false;
324
+ const sameStringArray = (x, y) => {
325
+ var _a, _b;
326
+ 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]);
327
+ };
328
+ 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);
329
+ }
307
330
  };
308
331
  _CoreStateManager.CORE_LOADING_STATES = /* @__PURE__ */ new Set([
309
332
  "setting_up",
@@ -317,7 +340,11 @@ _CoreStateManager.AUTH_LOADING_STATES = /* @__PURE__ */ new Set([
317
340
  "authenticating_farcaster",
318
341
  "processing_authentication",
319
342
  "authenticating_telegram",
320
- "checking_state"
343
+ "checking_state",
344
+ // Re-polling the login status after the second factor is satisfied (ENG-6906).
345
+ // awaiting_2fa / awaiting_2fa_enrollment are deliberately NOT loading — they are
346
+ // user-input holds where the UI shows the MFA prompt.
347
+ "resolving_2fa"
321
348
  ]);
322
349
  _CoreStateManager.WALLET_LOADING_STATES = /* @__PURE__ */ new Set([
323
350
  "checking_wallet_state",
@@ -756,18 +756,18 @@ function createAuthStateMachine(paraCoreInterface) {
756
756
  guard: (0, import_xstate.and)([
757
757
  ({ event }) => {
758
758
  const output = event.output;
759
- return !!output.externalWallet;
759
+ return !!(output == null ? void 0 : output.externalWallet);
760
760
  },
761
761
  (0, import_xstate.or)([
762
762
  ({ event }) => {
763
763
  var _a;
764
764
  const output = event.output;
765
- return output.stage === "done" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
765
+ return (output == null ? void 0 : output.stage) === "done" && !((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
766
766
  },
767
767
  ({ event }) => {
768
768
  var _a, _b;
769
769
  const output = event.output;
770
- return output.stage === "verify" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth) && !((_b = output.externalWallet) == null ? void 0 : _b.withVerification);
770
+ 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);
771
771
  }
772
772
  ])
773
773
  ])
@@ -784,12 +784,12 @@ function createAuthStateMachine(paraCoreInterface) {
784
784
  guard: (0, import_xstate.and)([
785
785
  ({ event }) => {
786
786
  const output = event.output;
787
- return !!output.externalWallet;
787
+ return !!(output == null ? void 0 : output.externalWallet);
788
788
  },
789
789
  ({ event }) => {
790
790
  var _a, _b;
791
791
  const output = event.output;
792
- return output.stage === "verify" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth) && ((_b = output.externalWallet) == null ? void 0 : _b.withVerification);
792
+ 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);
793
793
  }
794
794
  ])
795
795
  },
@@ -805,12 +805,12 @@ function createAuthStateMachine(paraCoreInterface) {
805
805
  guard: (0, import_xstate.and)([
806
806
  ({ event }) => {
807
807
  const output = event.output;
808
- return !!output.externalWallet;
808
+ return !!(output == null ? void 0 : output.externalWallet);
809
809
  },
810
810
  ({ event }) => {
811
811
  var _a;
812
812
  const output = event.output;
813
- return output.stage === "verify" && !!((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
813
+ return (output == null ? void 0 : output.stage) === "verify" && !!((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
814
814
  }
815
815
  ])
816
816
  },
@@ -824,11 +824,37 @@ function createAuthStateMachine(paraCoreInterface) {
824
824
  ({ event }) => {
825
825
  var _a;
826
826
  const output = event.output;
827
- return output.stage === "verify" && !((_a = output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
827
+ return (output == null ? void 0 : output.stage) === "verify" && !((_a = output == null ? void 0 : output.externalWallet) == null ? void 0 : _a.withFullParaAuth);
828
828
  },
829
829
  () => !paraCoreInterface.authService.isEnclaveUser
830
830
  ])
831
831
  },
832
+ // Login-time 2FA (ENG-6906): the server gated the login behind a second
833
+ // factor. Enrollment (no factor yet) and verification (existing factor)
834
+ // are surfaced as distinct phases so a custom UI can show the right step.
835
+ {
836
+ target: "awaiting_2fa_enrollment",
837
+ actions: [
838
+ "storeAuthStateResult",
839
+ { type: "logTransition", params: { to: "awaiting_2fa_enrollment", detail: "mfa enroll" } }
840
+ ],
841
+ guard: ({ event }) => {
842
+ var _a;
843
+ const output = event.output;
844
+ return (output == null ? void 0 : output.stage) === "mfa" && ((_a = output == null ? void 0 : output.mfa) == null ? void 0 : _a.mode) === "enroll";
845
+ }
846
+ },
847
+ {
848
+ target: "awaiting_2fa",
849
+ actions: [
850
+ "storeAuthStateResult",
851
+ { type: "logTransition", params: { to: "awaiting_2fa", detail: "mfa verify" } }
852
+ ],
853
+ guard: ({ event }) => {
854
+ const output = event.output;
855
+ return (output == null ? void 0 : output.stage) === "mfa";
856
+ }
857
+ },
832
858
  {
833
859
  target: "awaiting_session_start",
834
860
  actions: [
@@ -871,6 +897,77 @@ function createAuthStateMachine(paraCoreInterface) {
871
897
  CANCEL: { target: "unauthenticated", actions: ["resetState"] }
872
898
  }
873
899
  },
900
+ // Login-time 2FA hold states (ENG-6906). The SDK satisfies the second factor
901
+ // out-of-band (enrollMfa/verifyMfa against /internal/v1/auth/2fa/*); once the
902
+ // backend has stamped the session, a RESOLVE_2FA event re-polls the login
903
+ // status, which now returns stage:'done'.
904
+ awaiting_2fa: {
905
+ on: {
906
+ RESOLVE_2FA: { target: "resolving_2fa", actions: ["setPollingCallbacks", "resetError"] },
907
+ CANCEL: { target: "unauthenticated", actions: ["resetState"] }
908
+ }
909
+ },
910
+ awaiting_2fa_enrollment: {
911
+ on: {
912
+ RESOLVE_2FA: { target: "resolving_2fa", actions: ["setPollingCallbacks", "resetError"] },
913
+ CANCEL: { target: "unauthenticated", actions: ["resetState"] }
914
+ }
915
+ },
916
+ resolving_2fa: {
917
+ entry: [{ type: "logTransition", params: { to: "resolving_2fa" } }],
918
+ invoke: {
919
+ src: "polling",
920
+ input: ({ context }) => {
921
+ var _a;
922
+ let resolvedAuthState = null;
923
+ return {
924
+ checkCondition: () => __async(this, null, function* () {
925
+ try {
926
+ const result = yield paraCoreInterface.pollingService.waitForOAuth({
927
+ onSuccess: (serverAuthState) => {
928
+ resolvedAuthState = serverAuthState;
929
+ }
930
+ })();
931
+ return { finished: result.finished && !!resolvedAuthState, data: { authState: resolvedAuthState } };
932
+ } catch (error) {
933
+ throw new Error(`2FA resolution polling failed: ${(0, import_stateErrorHelpers.extractErrorMessage)(error, "Unknown error")}`);
934
+ }
935
+ }),
936
+ onPoll: () => {
937
+ var _a2, _b;
938
+ return (_b = (_a2 = context.pollingCallbacks) == null ? void 0 : _a2.onPoll) == null ? void 0 : _b.call(_a2);
939
+ },
940
+ onCancel: () => {
941
+ var _a2, _b;
942
+ return (_b = (_a2 = context.pollingCallbacks) == null ? void 0 : _a2.onCancel) == null ? void 0 : _b.call(_a2);
943
+ },
944
+ isCanceled: ((_a = context.pollingCallbacks) == null ? void 0 : _a.isCanceled) || (() => false),
945
+ id: "resolving2fa"
946
+ };
947
+ },
948
+ onError: {
949
+ target: "error",
950
+ actions: (0, import_authStateMachine_helpers.setAuthErrorAssign)("2FA resolution failed")
951
+ }
952
+ },
953
+ on: (0, import_authStateMachine_helpers.makePollingOnHandlers)("processing_authentication", "2FA resolution failed", [
954
+ (0, import_xstate.assign)({
955
+ serverAuthStateResult: ({ event }) => {
956
+ var _a;
957
+ const authState = ((_a = event.data) == null ? void 0 : _a.authState) || null;
958
+ return {
959
+ authState,
960
+ opts: import_authStateMachine_helpers.DEFAULT_AUTH_OPTS
961
+ };
962
+ },
963
+ isNewUser: ({ event }) => {
964
+ var _a;
965
+ const authState = (_a = event.data) == null ? void 0 : _a.authState;
966
+ return authState ? (0, import_authStateMachine_helpers.computeIsNewUser)(authState) : false;
967
+ }
968
+ })
969
+ ])
970
+ },
874
971
  awaiting_session_start: {
875
972
  on: {
876
973
  WAIT_FOR_SESSION: {
@@ -219,6 +219,14 @@ function createCoreStateMachine(paraCoreInterface) {
219
219
  "WAIT_FOR_WALLET_CREATION": {
220
220
  actions: ({ context, event }) => context.authMachineRef.send(event)
221
221
  },
222
+ // Login-time 2FA (ENG-6906): after the second factor is satisfied, verifyMfa
223
+ // fires RESOLVE_2FA so the auth machine re-polls verify-oauth and advances the
224
+ // login. Without forwarding it here the parent silently drops the event, the
225
+ // auth child stays parked at awaiting_2fa[_enrollment], and the user never gets
226
+ // a wallet.
227
+ "RESOLVE_2FA": {
228
+ actions: ({ context, event }) => context.authMachineRef.send(event)
229
+ },
222
230
  "INITIALIZE_GUEST_MODE": {
223
231
  actions: ({ context, event }) => context.authMachineRef.send(event)
224
232
  },
@@ -39,6 +39,8 @@ const PARA_CORE_METHODS = [
39
39
  "setup2fa",
40
40
  "enable2fa",
41
41
  "verify2fa",
42
+ "enrollMfa",
43
+ "verifyMfa",
42
44
  "logout",
43
45
  "clearStorage",
44
46
  "isSessionActive",
@@ -1770,6 +1770,37 @@ const _ParaCore = class _ParaCore {
1770
1770
  yield this.ctx.client.enable2FA(userId, verificationCode);
1771
1771
  });
1772
1772
  }
1773
+ /**
1774
+ * Login-time 2FA (ENG-6906): enroll a TOTP second factor for the current session.
1775
+ * Distinct from setup2fa/enable2fa, which configure the recovery-gating 2FA. This
1776
+ * is authenticated by the active session (no recovery config gate); the backend
1777
+ * 404s the surface when the login-MFA flag is off.
1778
+ *
1779
+ * @returns {Object} `{ uri, backupCodes }` — otpauth URI to render as a QR, plus
1780
+ * one-time backup codes to show the user exactly once.
1781
+ */
1782
+ enrollMfa() {
1783
+ return __async(this, null, function* () {
1784
+ yield __privateMethod(this, _ParaCore_instances, assertPartner_fn).call(this);
1785
+ return yield __privateGet(this, _authService).enrollMfa();
1786
+ });
1787
+ }
1788
+ /**
1789
+ * Login-time 2FA (ENG-6906): verify the second factor for the current login. On
1790
+ * success the backend stamps the session and this re-polls the login status so the
1791
+ * flow advances toward `done`; a wrong code resolves to `{ ok:false, attemptsRemaining }`
1792
+ * so the UI can re-prompt without treating it as a hard error.
1793
+ *
1794
+ * @param {Object} opts the options object.
1795
+ * @param {string} opts.code the TOTP or backup code entered by the user.
1796
+ * @returns {Object} `{ ok: true } | { ok: false; attemptsRemaining? }`
1797
+ */
1798
+ verifyMfa(_0) {
1799
+ return __async(this, arguments, function* ({ code }) {
1800
+ yield __privateMethod(this, _ParaCore_instances, assertPartner_fn).call(this);
1801
+ return yield __privateGet(this, _authService).verifyMfa({ code });
1802
+ });
1803
+ }
1773
1804
  /**
1774
1805
  * Resend a verification email for the current user.
1775
1806
  */
@@ -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.0";
3
3
  const PREFIX = "@CAPSULE/";
4
4
  const PARA_PREFIX = "@PARA/";
5
5
  const LOCAL_STORAGE_AUTH_INFO = `${PREFIX}authInfo`;
@@ -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",