@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.
- package/dist/__clients__/core.d.ts +2 -1
- package/dist/__clients__/core.d.ts.map +1 -1
- package/dist/__clients__/core.js +144 -125
- package/dist/__clients__/core.js.map +1 -1
- package/dist/__clients__/core.mjs +144 -125
- package/dist/__clients__/core.mjs.map +1 -1
- package/dist/__generated__/sdk-client-base.d.ts +29 -0
- package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
- package/dist/__generated__/sdk-client-base.js +105 -38
- package/dist/__generated__/sdk-client-base.js.map +1 -1
- package/dist/__generated__/sdk-client-base.mjs +105 -38
- package/dist/__generated__/sdk-client-base.mjs.map +1 -1
- package/dist/__generated__/version.d.ts +1 -1
- package/dist/__generated__/version.js +1 -1
- package/dist/__generated__/version.mjs +1 -1
- package/dist/__stampers__/api/base.d.ts +2 -0
- package/dist/__stampers__/api/base.d.ts.map +1 -1
- package/dist/__stampers__/api/base.js +19 -0
- package/dist/__stampers__/api/base.js.map +1 -1
- package/dist/__stampers__/api/base.mjs +19 -0
- package/dist/__stampers__/api/base.mjs.map +1 -1
- package/dist/__stampers__/api/mobile/stamper.d.ts +2 -0
- package/dist/__stampers__/api/mobile/stamper.d.ts.map +1 -1
- package/dist/__stampers__/api/mobile/stamper.js +19 -0
- package/dist/__stampers__/api/mobile/stamper.js.map +1 -1
- package/dist/__stampers__/api/mobile/stamper.mjs +20 -1
- package/dist/__stampers__/api/mobile/stamper.mjs.map +1 -1
- package/dist/__stampers__/api/web/stamper.d.ts +2 -1
- package/dist/__stampers__/api/web/stamper.d.ts.map +1 -1
- package/dist/__stampers__/api/web/stamper.js +9 -4
- package/dist/__stampers__/api/web/stamper.js.map +1 -1
- package/dist/__stampers__/api/web/stamper.mjs +9 -4
- package/dist/__stampers__/api/web/stamper.mjs.map +1 -1
- package/dist/__storage__/mobile/storage.js +1 -1
- package/dist/__storage__/mobile/storage.js.map +1 -1
- package/dist/__storage__/mobile/storage.mjs +1 -1
- package/dist/__storage__/mobile/storage.mjs.map +1 -1
- package/dist/__types__/auth.d.ts +2 -0
- package/dist/__types__/auth.d.ts.map +1 -1
- package/dist/__types__/auth.js.map +1 -1
- package/dist/__types__/auth.mjs.map +1 -1
- package/dist/__types__/external-wallets.d.ts +9 -1
- package/dist/__types__/external-wallets.d.ts.map +1 -1
- package/dist/__types__/method-types/shared.d.ts +2 -0
- package/dist/__types__/method-types/shared.d.ts.map +1 -1
- package/dist/__wallet__/web/native/ethereum.d.ts.map +1 -1
- package/dist/__wallet__/web/native/ethereum.js +13 -5
- package/dist/__wallet__/web/native/ethereum.js.map +1 -1
- package/dist/__wallet__/web/native/ethereum.mjs +13 -5
- package/dist/__wallet__/web/native/ethereum.mjs.map +1 -1
- package/dist/__wallet__/web/native/solana.d.ts.map +1 -1
- package/dist/__wallet__/web/native/solana.js +17 -9
- package/dist/__wallet__/web/native/solana.js.map +1 -1
- package/dist/__wallet__/web/native/solana.mjs +17 -9
- package/dist/__wallet__/web/native/solana.mjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/utils.d.ts +34 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +103 -9
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +100 -10
- package/dist/utils.mjs.map +1 -1
- 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,
|
|
5
|
-
import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, getPublicKeyFromStampHeader, addressFromPublicKey, getCurveTypeFromProvider,
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
|
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
|
-
|
|
533
|
+
const { walletProvider, sessionKey = SessionKey.DefaultSessionkey } = params;
|
|
549
534
|
return withTurnkeyErrorHandling(async () => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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,
|
|
614
|
-
let generatedPublicKey = undefined;
|
|
574
|
+
const { walletProvider, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, } = params;
|
|
615
575
|
return withTurnkeyErrorHandling(async () => {
|
|
616
|
-
|
|
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
|
-
|
|
648
|
-
this.
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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
|
|
938
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
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
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
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({
|