@turnkey/core 1.10.0 → 1.11.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.
Files changed (67) hide show
  1. package/dist/__clients__/core.d.ts +2 -1
  2. package/dist/__clients__/core.d.ts.map +1 -1
  3. package/dist/__clients__/core.js +144 -125
  4. package/dist/__clients__/core.js.map +1 -1
  5. package/dist/__clients__/core.mjs +144 -125
  6. package/dist/__clients__/core.mjs.map +1 -1
  7. package/dist/__generated__/sdk-client-base.d.ts +29 -0
  8. package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
  9. package/dist/__generated__/sdk-client-base.js +105 -38
  10. package/dist/__generated__/sdk-client-base.js.map +1 -1
  11. package/dist/__generated__/sdk-client-base.mjs +105 -38
  12. package/dist/__generated__/sdk-client-base.mjs.map +1 -1
  13. package/dist/__generated__/version.d.ts +1 -1
  14. package/dist/__generated__/version.js +1 -1
  15. package/dist/__generated__/version.mjs +1 -1
  16. package/dist/__stampers__/api/base.d.ts +2 -0
  17. package/dist/__stampers__/api/base.d.ts.map +1 -1
  18. package/dist/__stampers__/api/base.js +19 -0
  19. package/dist/__stampers__/api/base.js.map +1 -1
  20. package/dist/__stampers__/api/base.mjs +19 -0
  21. package/dist/__stampers__/api/base.mjs.map +1 -1
  22. package/dist/__stampers__/api/mobile/stamper.d.ts +2 -0
  23. package/dist/__stampers__/api/mobile/stamper.d.ts.map +1 -1
  24. package/dist/__stampers__/api/mobile/stamper.js +19 -0
  25. package/dist/__stampers__/api/mobile/stamper.js.map +1 -1
  26. package/dist/__stampers__/api/mobile/stamper.mjs +20 -1
  27. package/dist/__stampers__/api/mobile/stamper.mjs.map +1 -1
  28. package/dist/__stampers__/api/web/stamper.d.ts +2 -1
  29. package/dist/__stampers__/api/web/stamper.d.ts.map +1 -1
  30. package/dist/__stampers__/api/web/stamper.js +9 -4
  31. package/dist/__stampers__/api/web/stamper.js.map +1 -1
  32. package/dist/__stampers__/api/web/stamper.mjs +9 -4
  33. package/dist/__stampers__/api/web/stamper.mjs.map +1 -1
  34. package/dist/__storage__/mobile/storage.js +1 -1
  35. package/dist/__storage__/mobile/storage.js.map +1 -1
  36. package/dist/__storage__/mobile/storage.mjs +1 -1
  37. package/dist/__storage__/mobile/storage.mjs.map +1 -1
  38. package/dist/__types__/auth.d.ts +2 -0
  39. package/dist/__types__/auth.d.ts.map +1 -1
  40. package/dist/__types__/auth.js.map +1 -1
  41. package/dist/__types__/auth.mjs.map +1 -1
  42. package/dist/__types__/external-wallets.d.ts +9 -1
  43. package/dist/__types__/external-wallets.d.ts.map +1 -1
  44. package/dist/__types__/method-types/shared.d.ts +2 -0
  45. package/dist/__types__/method-types/shared.d.ts.map +1 -1
  46. package/dist/__wallet__/web/native/ethereum.d.ts.map +1 -1
  47. package/dist/__wallet__/web/native/ethereum.js +13 -5
  48. package/dist/__wallet__/web/native/ethereum.js.map +1 -1
  49. package/dist/__wallet__/web/native/ethereum.mjs +13 -5
  50. package/dist/__wallet__/web/native/ethereum.mjs.map +1 -1
  51. package/dist/__wallet__/web/native/solana.d.ts.map +1 -1
  52. package/dist/__wallet__/web/native/solana.js +17 -9
  53. package/dist/__wallet__/web/native/solana.js.map +1 -1
  54. package/dist/__wallet__/web/native/solana.mjs +17 -9
  55. package/dist/__wallet__/web/native/solana.mjs.map +1 -1
  56. package/dist/index.d.ts +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +3 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/index.mjs +1 -1
  61. package/dist/utils.d.ts +34 -2
  62. package/dist/utils.d.ts.map +1 -1
  63. package/dist/utils.js +103 -9
  64. package/dist/utils.js.map +1 -1
  65. package/dist/utils.mjs +100 -10
  66. package/dist/utils.mjs.map +1 -1
  67. package/package.json +8 -8
@@ -1,8 +1,8 @@
1
1
  import { TurnkeySDKClientBase } from '../__generated__/sdk-client-base.mjs';
2
2
  import { TurnkeyErrorCodes, TurnkeyError, AuthAction } from '@turnkey/sdk-types';
3
3
  import { DEFAULT_SESSION_EXPIRATION_IN_SECONDS } from '../__types__/auth.mjs';
4
- import { SessionKey, StamperType, WalletSource, Chain, FilterType, OtpTypeToFilterTypeMap, OtpType, Curve, SignIntent } from '../__types__/enums.mjs';
5
- import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, getPublicKeyFromStampHeader, addressFromPublicKey, getCurveTypeFromProvider, sendSignedRequest, getAuthenticatorAddresses, fetchAllWalletAccountsWithCursor, mapAccountsToWallet, toExternalTimestamp, isEthereumProvider, isSolanaProvider, getActiveSessionOrThrowIfRequired, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, getPolicySignature, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
4
+ import { SessionKey, StamperType, WalletSource, FilterType, OtpTypeToFilterTypeMap, OtpType, Curve, Chain, SignIntent } from '../__types__/enums.mjs';
5
+ import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, getPublicKeyFromStampHeader, addressFromPublicKey, getCurveTypeFromProvider, getClientSignatureMessageForLogin, getClientSignatureMessageForSignup, getAuthenticatorAddresses, fetchAllWalletAccountsWithCursor, mapAccountsToWallet, toExternalTimestamp, isEthereumProvider, isSolanaProvider, getActiveSessionOrThrowIfRequired, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, getPolicySignature, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
6
6
  import { createStorageManager } from '../__storage__/base.mjs';
7
7
  import { CrossPlatformApiKeyStamper } from '../__stampers__/api/base.mjs';
8
8
  import { CrossPlatformPasskeyStamper } from '../__stampers__/passkey/base.mjs';
@@ -11,6 +11,7 @@ import { jwtDecode } from 'jwt-decode';
11
11
  import { createWalletManager } from '../__wallet__/base.mjs';
12
12
  import { toUtf8Bytes } from 'ethers';
13
13
  import { verify } from '@turnkey/crypto';
14
+ import { SignatureFormat } from '@turnkey/api-key-stamper';
14
15
 
15
16
  class TurnkeyClient {
16
17
  constructor(config,
@@ -487,28 +488,12 @@ class TurnkeyClient {
487
488
  if (!signedRequest) {
488
489
  throw new TurnkeyError("Failed to create stamped request for wallet login", TurnkeyErrorCodes.BAD_RESPONSE);
489
490
  }
490
- let publicKey;
491
- switch (walletProvider.chainInfo.namespace) {
492
- case Chain.Ethereum: {
493
- // for Ethereum, there is no way to get the public key from the wallet address
494
- // so we derive it from the signed request
495
- publicKey = getPublicKeyFromStampHeader(signedRequest.stamp.stampHeaderValue);
496
- break;
497
- }
498
- case Chain.Solana: {
499
- // for Solana, we can get the public key from the wallet address
500
- // since the wallet address is the public key
501
- // this doesn't require any action from the user as long as the wallet is connected
502
- // which it has to be since they just called stampStampLogin()
503
- publicKey = await this.walletManager.stamper.getPublicKey(walletProvider.interfaceType, walletProvider);
504
- break;
505
- }
506
- default:
507
- throw new TurnkeyError(`Unsupported interface type: ${walletProvider.interfaceType}`, TurnkeyErrorCodes.INVALID_REQUEST);
508
- }
491
+ // the wallet's public key is embedded in the stamp header by the wallet stamper
492
+ // so we extract it from there
493
+ const publicKey = getPublicKeyFromStampHeader(signedRequest.stamp.stampHeaderValue);
509
494
  return {
510
495
  signedRequest,
511
- publicKey: publicKey,
496
+ publicKey,
512
497
  };
513
498
  }, {
514
499
  errorCode: TurnkeyErrorCodes.WALLET_BUILD_LOGIN_REQUEST_ERROR,
@@ -545,49 +530,25 @@ class TurnkeyClient {
545
530
  * @throws {TurnkeyError} If the wallet stamper is uninitialized, a public key cannot be found or generated, or login fails.
546
531
  */
547
532
  this.loginWithWallet = async (params) => {
548
- let generatedPublicKey = params.publicKey || (await this.apiKeyStamper?.createKeyPair());
533
+ const { walletProvider, sessionKey = SessionKey.DefaultSessionkey } = params;
549
534
  return withTurnkeyErrorHandling(async () => {
550
- if (!this.walletManager?.stamper) {
551
- throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
552
- }
553
- const sessionKey = params.sessionKey || SessionKey.DefaultSessionkey;
554
- const walletProvider = params.walletProvider;
555
- const expirationSeconds = params?.expirationSeconds || DEFAULT_SESSION_EXPIRATION_IN_SECONDS;
556
- if (!generatedPublicKey) {
557
- throw new TurnkeyError("A publickey could not be found or generated.", TurnkeyErrorCodes.INTERNAL_ERROR);
535
+ const { signedRequest, publicKey } = await this.buildWalletLoginRequest(params);
536
+ const sessionResponse = await this.httpClient.sendSignedRequest(signedRequest);
537
+ const sessionToken = sessionResponse.session;
538
+ if (!sessionToken) {
539
+ throw new TurnkeyError("Session token not found in the response", TurnkeyErrorCodes.BAD_RESPONSE);
558
540
  }
559
- this.walletManager.stamper.setProvider(walletProvider.interfaceType, walletProvider);
560
- const sessionResponse = await this.httpClient.stampLogin({
561
- publicKey: generatedPublicKey,
562
- organizationId: params?.organizationId ?? this.config.organizationId,
563
- expirationSeconds,
564
- }, StamperType.Wallet);
565
541
  await this.storeSession({
566
542
  sessionToken: sessionResponse.session,
567
543
  sessionKey,
568
544
  });
569
- // TODO (Moe): What happens if a user connects to MetaMask on Ethereum,
570
- // then switches to a Solana account within MetaMask? Will this flow break?
571
- const address = addressFromPublicKey(walletProvider.chainInfo.namespace, generatedPublicKey);
572
- generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
573
545
  return {
574
546
  sessionToken: sessionResponse.session,
575
- address,
547
+ address: addressFromPublicKey(walletProvider.chainInfo.namespace, publicKey),
576
548
  };
577
549
  }, {
578
550
  errorMessage: "Unable to log in with the provided wallet",
579
551
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_AUTH_ERROR,
580
- }, {
581
- finallyFn: async () => {
582
- if (generatedPublicKey) {
583
- try {
584
- await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
585
- }
586
- catch (cleanupError) {
587
- throw new TurnkeyError("Failed to clean up generated key pair", TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
588
- }
589
- }
590
- },
591
552
  });
592
553
  };
593
554
  /**
@@ -610,18 +571,9 @@ class TurnkeyClient {
610
571
  * @throws {TurnkeyError} If there is an error during wallet authentication, sub-organization creation, session storage, or cleanup.
611
572
  */
612
573
  this.signUpWithWallet = async (params) => {
613
- const { walletProvider, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
614
- let generatedPublicKey = undefined;
574
+ const { walletProvider, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, } = params;
615
575
  return withTurnkeyErrorHandling(async () => {
616
- if (!this.walletManager?.stamper) {
617
- throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
618
- }
619
- generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
620
- this.walletManager.stamper.setProvider(walletProvider.interfaceType, walletProvider);
621
- const publicKey = await this.walletManager.stamper.getPublicKey(walletProvider.interfaceType, walletProvider);
622
- if (!publicKey) {
623
- throw new TurnkeyError("Failed to get public key from wallet", TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
624
- }
576
+ const { signedRequest, publicKey } = await this.buildWalletLoginRequest(params);
625
577
  const signUpBody = buildSignUpBody({
626
578
  createSubOrgParams: {
627
579
  ...createSubOrgParams,
@@ -631,12 +583,6 @@ class TurnkeyClient {
631
583
  publicKey: publicKey,
632
584
  curveType: getCurveTypeFromProvider(walletProvider),
633
585
  },
634
- {
635
- apiKeyName: `wallet-auth-${generatedPublicKey}`,
636
- publicKey: generatedPublicKey,
637
- curveType: "API_KEY_CURVE_P256",
638
- expirationSeconds: "60",
639
- },
640
586
  ],
641
587
  },
642
588
  });
@@ -644,43 +590,24 @@ class TurnkeyClient {
644
590
  if (!res) {
645
591
  throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
646
592
  }
647
- const newGeneratedKeyPair = await this.apiKeyStamper?.createKeyPair();
648
- this.apiKeyStamper?.setTemporaryPublicKey(generatedPublicKey);
649
- const sessionResponse = await this.httpClient.stampLogin({
650
- publicKey: newGeneratedKeyPair,
651
- organizationId: this.config.organizationId,
652
- expirationSeconds,
593
+ // now we can send the stamped request to Turnkey
594
+ const sessionResponse = await this.httpClient.sendSignedRequest(signedRequest);
595
+ const sessionToken = sessionResponse.session;
596
+ if (!sessionToken) {
597
+ throw new TurnkeyError("Session token not found in the response", TurnkeyErrorCodes.BAD_RESPONSE);
598
+ }
599
+ await this.storeSession({
600
+ sessionToken: sessionToken,
601
+ sessionKey,
653
602
  });
654
- await Promise.all([
655
- this.apiKeyStamper?.deleteKeyPair(generatedPublicKey),
656
- this.storeSession({
657
- sessionToken: sessionResponse.session,
658
- sessionKey,
659
- }),
660
- ]);
661
- generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
662
- // TODO (Moe): What happens if a user connects to MetaMask on Ethereum,
663
- // then switches to a Solana account within MetaMask? Will this flow break?
664
603
  return {
665
- sessionToken: sessionResponse.session,
604
+ sessionToken: sessionToken,
666
605
  appProofs: res.appProofs,
667
606
  address: addressFromPublicKey(walletProvider.chainInfo.namespace, publicKey),
668
607
  };
669
608
  }, {
670
609
  errorMessage: "Failed to sign up with wallet",
671
610
  errorCode: TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR,
672
- }, {
673
- finallyFn: async () => {
674
- this.apiKeyStamper?.clearTemporaryPublicKey();
675
- if (generatedPublicKey) {
676
- try {
677
- await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
678
- }
679
- catch (cleanupError) {
680
- throw new TurnkeyError("Failed to clean up generated key pair", TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
681
- }
682
- }
683
- },
684
611
  });
685
612
  };
686
613
  /**
@@ -706,9 +633,7 @@ class TurnkeyClient {
706
633
  * @throws {TurnkeyError} If there is an error during wallet authentication, sub-organization creation, or session storage.
707
634
  */
708
635
  this.loginOrSignupWithWallet = async (params) => {
709
- const createSubOrgParams = params.createSubOrgParams;
710
- const sessionKey = params.sessionKey || SessionKey.DefaultSessionkey;
711
- const walletProvider = params.walletProvider;
636
+ const { walletProvider, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, } = params;
712
637
  return withTurnkeyErrorHandling(async () => {
713
638
  const { signedRequest, publicKey } = await this.buildWalletLoginRequest(params);
714
639
  // here we check if the subOrg exists and create one
@@ -742,8 +667,8 @@ class TurnkeyClient {
742
667
  }
743
668
  }
744
669
  // now we can send the stamped request to Turnkey
745
- const sessionResponse = await sendSignedRequest(signedRequest);
746
- const sessionToken = sessionResponse.activity.result.stampLoginResult?.session;
670
+ const sessionResponse = await this.httpClient.sendSignedRequest(signedRequest);
671
+ const sessionToken = sessionResponse.session;
747
672
  if (!sessionToken) {
748
673
  throw new TurnkeyError("Session token not found in the response", TurnkeyErrorCodes.BAD_RESPONSE);
749
674
  }
@@ -761,8 +686,6 @@ class TurnkeyClient {
761
686
  }, {
762
687
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_OR_SIGNUP_ERROR,
763
688
  errorMessage: "Failed to log in or sign up with wallet",
764
- catchFn: async () => {
765
- },
766
689
  });
767
690
  };
768
691
  /**
@@ -808,6 +731,7 @@ class TurnkeyClient {
808
731
  * @param params.otpCode - OTP code entered by the user.
809
732
  * @param params.contact - contact information for the user (e.g., email address or phone number).
810
733
  * @param params.otpType - type of OTP being verified (OtpType.Email or OtpType.Sms).
734
+ * @param params.publicKey - public key the verification token is bound to for ownership verification (client signature verification during login/signup). This public key is optional; if not provided, a new key pair will be generated.
811
735
  * @returns A promise that resolves to an object containing:
812
736
  * - subOrganizationId: sub-organization ID if the contact is already associated with a sub-organization, or an empty string if not.
813
737
  * - verificationToken: verification token to be used for login or sign-up.
@@ -815,10 +739,12 @@ class TurnkeyClient {
815
739
  */
816
740
  this.verifyOtp = async (params) => {
817
741
  const { otpId, otpCode, contact, otpType } = params;
742
+ const resolvedPublicKey = params.publicKey ?? (await this.apiKeyStamper?.createKeyPair());
818
743
  return withTurnkeyErrorHandling(async () => {
819
744
  const verifyOtpRes = await this.httpClient.proxyVerifyOtp({
820
745
  otpId: otpId,
821
746
  otpCode: otpCode,
747
+ publicKey: resolvedPublicKey,
822
748
  });
823
749
  if (!verifyOtpRes) {
824
750
  throw new TurnkeyError(`OTP verification failed`, TurnkeyErrorCodes.INTERNAL_ERROR);
@@ -826,6 +752,7 @@ class TurnkeyClient {
826
752
  const accountRes = await this.httpClient.proxyGetAccount({
827
753
  filterType: OtpTypeToFilterTypeMap[otpType],
828
754
  filterValue: contact,
755
+ verificationToken: verifyOtpRes.verificationToken,
829
756
  });
830
757
  if (!accountRes) {
831
758
  throw new TurnkeyError(`Account fetch failed`, TurnkeyErrorCodes.ACCOUNT_FETCH_ERROR);
@@ -867,10 +794,26 @@ class TurnkeyClient {
867
794
  this.loginWithOtp = async (params) => {
868
795
  const { verificationToken, invalidateExisting = false, publicKey = await this.apiKeyStamper?.createKeyPair(), organizationId, sessionKey = SessionKey.DefaultSessionkey, } = params;
869
796
  return withTurnkeyErrorHandling(async () => {
797
+ const { message, publicKey: clientSignaturePublicKey } = getClientSignatureMessageForLogin({
798
+ verificationToken,
799
+ sessionPublicKey: publicKey,
800
+ });
801
+ this.apiKeyStamper?.setTemporaryPublicKey(publicKey);
802
+ const signature = await this.apiKeyStamper?.sign(message, SignatureFormat.Raw);
803
+ if (!signature) {
804
+ throw new TurnkeyError(`Failed to sign client signature for OTP login`, TurnkeyErrorCodes.INTERNAL_ERROR);
805
+ }
806
+ const clientSignature = {
807
+ message: message,
808
+ publicKey: clientSignaturePublicKey,
809
+ scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
810
+ signature: signature,
811
+ };
870
812
  const res = await this.httpClient.proxyOtpLogin({
871
813
  verificationToken,
872
814
  publicKey: publicKey,
873
815
  invalidateExisting,
816
+ clientSignature,
874
817
  ...(organizationId && { organizationId }),
875
818
  });
876
819
  if (!res) {
@@ -901,6 +844,10 @@ class TurnkeyClient {
901
844
  }
902
845
  }
903
846
  },
847
+ }, {
848
+ finallyFn: async () => {
849
+ this.apiKeyStamper?.clearTemporaryPublicKey();
850
+ },
904
851
  });
905
852
  };
906
853
  /**
@@ -923,7 +870,8 @@ class TurnkeyClient {
923
870
  * @throws {TurnkeyError} If there is an error during the OTP sign-up process or session storage.
924
871
  */
925
872
  this.signUpWithOtp = async (params) => {
926
- const { verificationToken, contact, otpType, createSubOrgParams, invalidateExisting, sessionKey, } = params;
873
+ const { verificationToken, contact, otpType, createSubOrgParams, invalidateExisting, sessionKey, publicKey = await this.apiKeyStamper?.createKeyPair(), } = params;
874
+ // build sign up body without client signature first
927
875
  const signUpBody = buildSignUpBody({
928
876
  createSubOrgParams: {
929
877
  ...createSubOrgParams,
@@ -934,14 +882,37 @@ class TurnkeyClient {
934
882
  },
935
883
  });
936
884
  return withTurnkeyErrorHandling(async () => {
937
- const generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
938
- const signupRes = await this.httpClient.proxySignup(signUpBody);
885
+ const { message, publicKey: clientSignaturePublicKey } = getClientSignatureMessageForSignup({
886
+ verificationToken,
887
+ ...(signUpBody.userEmail && { email: signUpBody.userEmail }),
888
+ ...(signUpBody.userPhoneNumber && {
889
+ phoneNumber: signUpBody.userPhoneNumber,
890
+ }),
891
+ apiKeys: signUpBody.apiKeys,
892
+ authenticators: signUpBody.authenticators,
893
+ oauthProviders: signUpBody.oauthProviders,
894
+ });
895
+ this.apiKeyStamper?.setTemporaryPublicKey(publicKey);
896
+ const signature = await this.apiKeyStamper?.sign(message, SignatureFormat.Raw);
897
+ if (!signature) {
898
+ throw new TurnkeyError(`Failed to sign client signature for OTP sign up`, TurnkeyErrorCodes.INTERNAL_ERROR);
899
+ }
900
+ const clientSignature = {
901
+ message: message,
902
+ publicKey: clientSignaturePublicKey,
903
+ scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
904
+ signature: signature,
905
+ };
906
+ const signupRes = await this.httpClient.proxySignup({
907
+ ...signUpBody,
908
+ clientSignature,
909
+ });
939
910
  if (!signupRes) {
940
911
  throw new TurnkeyError(`Auth proxy OTP sign up failed`, TurnkeyErrorCodes.OTP_SIGNUP_ERROR);
941
912
  }
942
913
  const otpRes = await this.loginWithOtp({
943
914
  verificationToken,
944
- publicKey: generatedPublicKey,
915
+ publicKey: publicKey,
945
916
  ...(invalidateExisting && { invalidateExisting }),
946
917
  ...(sessionKey && { sessionKey }),
947
918
  });
@@ -950,8 +921,23 @@ class TurnkeyClient {
950
921
  appProofs: signupRes.appProofs,
951
922
  };
952
923
  }, {
924
+ catchFn: async () => {
925
+ // Clean up the generated key pair if it wasn't successfully used
926
+ if (publicKey) {
927
+ try {
928
+ await this.apiKeyStamper?.deleteKeyPair(publicKey);
929
+ }
930
+ catch (cleanupError) {
931
+ throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
932
+ }
933
+ }
934
+ },
953
935
  errorCode: TurnkeyErrorCodes.OTP_SIGNUP_ERROR,
954
936
  errorMessage: "Failed to sign up with OTP",
937
+ }, {
938
+ finallyFn: async () => {
939
+ this.apiKeyStamper?.clearTemporaryPublicKey();
940
+ },
955
941
  });
956
942
  };
957
943
  /**
@@ -978,13 +964,14 @@ class TurnkeyClient {
978
964
  * @throws {TurnkeyError} If there is an error during OTP verification, sign-up, or login.
979
965
  */
980
966
  this.completeOtp = async (params) => {
981
- const { otpId, otpCode, contact, otpType, publicKey, invalidateExisting = false, sessionKey, createSubOrgParams, } = params;
967
+ const { otpId, otpCode, contact, otpType, publicKey = await this.apiKeyStamper?.createKeyPair(), invalidateExisting = false, sessionKey, createSubOrgParams, } = params;
982
968
  return withTurnkeyErrorHandling(async () => {
983
969
  const { subOrganizationId, verificationToken } = await this.verifyOtp({
984
970
  otpId: otpId,
985
971
  otpCode: otpCode,
986
972
  contact: contact,
987
973
  otpType: otpType,
974
+ publicKey: publicKey,
988
975
  });
989
976
  if (!verificationToken) {
990
977
  throw new TurnkeyError("No verification token returned from OTP verification", TurnkeyErrorCodes.VERIFY_OTP_ERROR);
@@ -997,6 +984,7 @@ class TurnkeyClient {
997
984
  ...(createSubOrgParams && { createSubOrgParams }),
998
985
  ...(invalidateExisting && { invalidateExisting }),
999
986
  ...(sessionKey && { sessionKey }),
987
+ publicKey: publicKey,
1000
988
  });
1001
989
  return {
1002
990
  ...signUpRes,
@@ -1007,7 +995,7 @@ class TurnkeyClient {
1007
995
  else {
1008
996
  const loginRes = await this.loginWithOtp({
1009
997
  verificationToken,
1010
- ...(publicKey && { publicKey }),
998
+ publicKey: publicKey,
1011
999
  ...(invalidateExisting && { invalidateExisting }),
1012
1000
  ...(sessionKey && { sessionKey }),
1013
1001
  });
@@ -1245,7 +1233,9 @@ class TurnkeyClient {
1245
1233
  if (!signedUserRequest) {
1246
1234
  throw new TurnkeyError("Failed to stamp user request", TurnkeyErrorCodes.INVALID_REQUEST);
1247
1235
  }
1248
- userPromise = sendSignedRequest(signedUserRequest).then((response) => getAuthenticatorAddresses(response.user));
1236
+ userPromise = this.httpClient
1237
+ .sendSignedRequest(signedUserRequest)
1238
+ .then((response) => getAuthenticatorAddresses(response.user));
1249
1239
  }
1250
1240
  // if connectedOnly is true, we skip fetching embedded wallets
1251
1241
  if (!connectedOnly) {
@@ -1265,7 +1255,7 @@ class TurnkeyClient {
1265
1255
  }
1266
1256
  const [accounts, walletsRes] = await Promise.all([
1267
1257
  fetchAllWalletAccountsWithCursor(this.httpClient, organizationId, stampWith),
1268
- sendSignedRequest(signedWalletsRequest),
1258
+ this.httpClient.sendSignedRequest(signedWalletsRequest),
1269
1259
  ]);
1270
1260
  // create a map of walletId to EmbeddedWallet for easy lookup
1271
1261
  const walletMap = new Map(walletsRes.wallets.map((wallet) => [
@@ -2060,12 +2050,15 @@ class TurnkeyClient {
2060
2050
  throw new TurnkeyError("User ID must be provided to update user email", TurnkeyErrorCodes.INVALID_REQUEST);
2061
2051
  }
2062
2052
  return withTurnkeyErrorHandling(async () => {
2063
- const existingUser = await this.httpClient.proxyGetAccount({
2064
- filterType: FilterType.Email,
2065
- filterValue: email,
2066
- });
2067
- if (existingUser.organizationId) {
2068
- throw new TurnkeyError(`Email ${email} is already associated with another user.`, TurnkeyErrorCodes.ACCOUNT_ALREADY_EXISTS);
2053
+ if (verificationToken) {
2054
+ const existingUser = await this.httpClient.proxyGetAccount({
2055
+ filterType: FilterType.Email,
2056
+ filterValue: email,
2057
+ verificationToken,
2058
+ });
2059
+ if (existingUser.organizationId) {
2060
+ throw new TurnkeyError(`Email ${email} is already associated with another user.`, TurnkeyErrorCodes.ACCOUNT_ALREADY_EXISTS);
2061
+ }
2069
2062
  }
2070
2063
  const res = await this.httpClient.updateUserEmail({
2071
2064
  userId: userId,
@@ -2143,6 +2136,16 @@ class TurnkeyClient {
2143
2136
  throw new TurnkeyError("User ID must be provided to update user phone number", TurnkeyErrorCodes.INVALID_REQUEST);
2144
2137
  }
2145
2138
  return withTurnkeyErrorHandling(async () => {
2139
+ if (verificationToken) {
2140
+ const existingUser = await this.httpClient.proxyGetAccount({
2141
+ filterType: FilterType.Sms,
2142
+ filterValue: phoneNumber,
2143
+ verificationToken,
2144
+ });
2145
+ if (existingUser.organizationId) {
2146
+ throw new TurnkeyError(`Phone number ${phoneNumber} is already associated with another user.`, TurnkeyErrorCodes.ACCOUNT_ALREADY_EXISTS);
2147
+ }
2148
+ }
2146
2149
  const res = await this.httpClient.updateUserPhoneNumber({
2147
2150
  userId,
2148
2151
  userPhoneNumber: phoneNumber,
@@ -2261,7 +2264,8 @@ class TurnkeyClient {
2261
2264
  if (!accountRes) {
2262
2265
  throw new TurnkeyError(`Account fetch failed`, TurnkeyErrorCodes.ACCOUNT_FETCH_ERROR);
2263
2266
  }
2264
- if (accountRes.organizationId) {
2267
+ if (accountRes.organizationId &&
2268
+ accountRes.organizationId !== session?.organizationId) {
2265
2269
  throw new TurnkeyError("Account already exists with this OIDC token", TurnkeyErrorCodes.ACCOUNT_ALREADY_EXISTS);
2266
2270
  }
2267
2271
  const userId = params?.userId || session?.userId;
@@ -2278,18 +2282,33 @@ class TurnkeyClient {
2278
2282
  const verifiedSuborg = await this.httpClient.proxyGetAccount({
2279
2283
  filterType: "EMAIL",
2280
2284
  filterValue: oidcEmail,
2285
+ oidcToken: oidcToken,
2281
2286
  });
2282
2287
  const isVerified = verifiedSuborg.organizationId === organizationId;
2283
2288
  const user = await this.fetchUser({
2284
2289
  userId,
2285
2290
  stampWith,
2286
2291
  });
2292
+ // this block's pupose is for social linking, it's important that we update the email BEFORE we call createOauthProviders
2293
+ // since for social linking createOauthProviders will mark the email as verified as long as it's the same one that lives in the
2294
+ // OIDC token.
2287
2295
  if (!user?.userEmail && !isVerified) {
2288
- await this.updateUserEmail({
2289
- email: oidcEmail,
2290
- userId,
2291
- stampWith,
2296
+ // we cannot use our sugared updateUserEmail here since we need to pass in the oidcToken in case Require Verification Token On Account Lookup is enabled
2297
+ // in the dashboard
2298
+ const existingUser = await this.httpClient.proxyGetAccount({
2299
+ filterType: FilterType.Email,
2300
+ filterValue: oidcEmail,
2301
+ oidcToken: oidcToken,
2292
2302
  });
2303
+ if (existingUser.organizationId) {
2304
+ throw new TurnkeyError(`Email ${oidcEmail} is already associated with another user.`, TurnkeyErrorCodes.ACCOUNT_ALREADY_EXISTS);
2305
+ }
2306
+ // update and verify their email since we got it from a verified OIDC token
2307
+ await this.httpClient.updateUserEmail({
2308
+ userId,
2309
+ userEmail: oidcEmail,
2310
+ organizationId,
2311
+ }, stampWith);
2293
2312
  }
2294
2313
  }
2295
2314
  const createProviderRes = await this.httpClient.createOauthProviders({