@turnkey/core 1.14.0 → 2.0.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.
- package/dist/__clients__/core.d.ts +87 -34
- package/dist/__clients__/core.d.ts.map +1 -1
- package/dist/__clients__/core.js +357 -199
- package/dist/__clients__/core.js.map +1 -1
- package/dist/__clients__/core.mjs +358 -200
- package/dist/__clients__/core.mjs.map +1 -1
- package/dist/__generated__/sdk-client-base.d.ts +8 -3
- package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
- package/dist/__generated__/sdk-client-base.js +125 -63
- package/dist/__generated__/sdk-client-base.js.map +1 -1
- package/dist/__generated__/sdk-client-base.mjs +120 -58
- package/dist/__generated__/sdk-client-base.mjs.map +1 -1
- package/dist/__generated__/version.d.ts +1 -1
- package/dist/__generated__/version.d.ts.map +1 -1
- package/dist/__generated__/version.js +1 -1
- package/dist/__generated__/version.mjs +1 -1
- package/dist/__stampers__/api/base.d.ts +2 -1
- package/dist/__stampers__/api/base.d.ts.map +1 -1
- package/dist/__stampers__/api/mobile/stamper.d.ts +2 -1
- package/dist/__stampers__/api/mobile/stamper.d.ts.map +1 -1
- package/dist/__stampers__/api/mobile/stamper.js.map +1 -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.map +1 -1
- package/dist/__stampers__/api/web/stamper.mjs.map +1 -1
- package/dist/__stampers__/passkey/base.d.ts +3 -1
- package/dist/__stampers__/passkey/base.d.ts.map +1 -1
- package/dist/__stampers__/passkey/base.js +19 -0
- package/dist/__stampers__/passkey/base.js.map +1 -1
- package/dist/__stampers__/passkey/base.mjs +20 -1
- package/dist/__stampers__/passkey/base.mjs.map +1 -1
- package/dist/__types__/auth.d.ts +2 -41
- 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__/config.d.ts +3 -2
- package/dist/__types__/config.d.ts.map +1 -1
- package/dist/__types__/enums.d.ts +1 -0
- package/dist/__types__/enums.d.ts.map +1 -1
- package/dist/__types__/enums.js +1 -0
- package/dist/__types__/enums.js.map +1 -1
- package/dist/__types__/enums.mjs +1 -0
- package/dist/__types__/enums.mjs.map +1 -1
- package/dist/__types__/http.d.ts +1 -14
- package/dist/__types__/http.d.ts.map +1 -1
- package/dist/__types__/index.d.ts +0 -1
- package/dist/__types__/index.d.ts.map +1 -1
- package/dist/__types__/method-types/shared.d.ts +28 -13
- package/dist/__types__/method-types/shared.d.ts.map +1 -1
- package/dist/__wallet__/stamper.d.ts +2 -1
- package/dist/__wallet__/stamper.d.ts.map +1 -1
- package/dist/__wallet__/stamper.js.map +1 -1
- package/dist/__wallet__/stamper.mjs.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -4
- package/dist/index.mjs.map +1 -1
- package/dist/turnkey-helpers.d.ts +4 -0
- package/dist/turnkey-helpers.d.ts.map +1 -1
- package/dist/turnkey-helpers.js +28 -0
- package/dist/turnkey-helpers.js.map +1 -1
- package/dist/turnkey-helpers.mjs +25 -1
- package/dist/turnkey-helpers.mjs.map +1 -1
- package/dist/utils.d.ts +41 -18
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +126 -57
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +100 -38
- package/dist/utils.mjs.map +1 -1
- package/package.json +6 -6
- package/dist/__types__/error.d.ts +0 -13
- package/dist/__types__/error.d.ts.map +0 -1
- package/dist/__types__/error.js +0 -18
- package/dist/__types__/error.js.map +0 -1
- package/dist/__types__/error.mjs +0 -16
- package/dist/__types__/error.mjs.map +0 -1
- package/dist/__types__/http.js +0 -11
- package/dist/__types__/http.js.map +0 -1
- package/dist/__types__/http.mjs +0 -9
- package/dist/__types__/http.mjs.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
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, FilterType,
|
|
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';
|
|
4
|
+
import { SessionKey, StamperType, WalletSource, FilterType, OtpType, OtpTypeToFilterTypeMap, 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, ERC20_TRANSFER_ABI, getPolicySignature, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
|
|
6
|
+
import { encryptOtpCodeToBundle, verify } from '@turnkey/crypto';
|
|
6
7
|
import { createStorageManager } from '../__storage__/base.mjs';
|
|
7
8
|
import { CrossPlatformApiKeyStamper } from '../__stampers__/api/base.mjs';
|
|
8
9
|
import { CrossPlatformPasskeyStamper } from '../__stampers__/passkey/base.mjs';
|
|
@@ -10,26 +11,68 @@ import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS } from '../turnkey-h
|
|
|
10
11
|
import { jwtDecode } from 'jwt-decode';
|
|
11
12
|
import { createWalletManager } from '../__wallet__/base.mjs';
|
|
12
13
|
import { toUtf8Bytes } from 'ethers';
|
|
13
|
-
import { verify } from '@turnkey/crypto';
|
|
14
14
|
import { SignatureFormat } from '@turnkey/api-key-stamper';
|
|
15
15
|
import { encodeFunctionData } from 'viem';
|
|
16
16
|
|
|
17
|
-
const ERC20_TRANSFER_ABI = [
|
|
18
|
-
{
|
|
19
|
-
type: "function",
|
|
20
|
-
name: "transfer",
|
|
21
|
-
stateMutability: "nonpayable",
|
|
22
|
-
inputs: [
|
|
23
|
-
{ name: "to", type: "address" },
|
|
24
|
-
{ name: "amount", type: "uint256" },
|
|
25
|
-
],
|
|
26
|
-
outputs: [{ name: "success", type: "bool" }],
|
|
27
|
-
},
|
|
28
|
-
];
|
|
29
17
|
class TurnkeyClient {
|
|
30
18
|
constructor(config,
|
|
31
19
|
// Users can pass in their own stampers, or we will create them. Should we remove this?
|
|
32
20
|
apiKeyStamper, passkeyStamper, walletManager) {
|
|
21
|
+
/**
|
|
22
|
+
* Overrides the API key stamper configuration.
|
|
23
|
+
*
|
|
24
|
+
* - Set `temporaryPublicKey` to a non-empty string to override the default API key.
|
|
25
|
+
* - Set `temporaryPublicKey` to an empty string ("") to clear the temporary key and restore default behavior.
|
|
26
|
+
* - Omit `temporaryPublicKey` to leave it unchanged.
|
|
27
|
+
* - Useful for dynamically changing the API key used for signing requests.
|
|
28
|
+
*
|
|
29
|
+
* @param params.temporaryPublicKey - temporary public key to set (non-empty string), clear (empty string), or leave unchanged (omit).
|
|
30
|
+
* @returns A promise that resolves when the stamper has been updated.
|
|
31
|
+
* @throws {TurnkeyError} If the API key stamper is not initialized or if there is an error updating it.
|
|
32
|
+
*/
|
|
33
|
+
this.overrideApiKeyStamper = async (params) => {
|
|
34
|
+
return withTurnkeyErrorHandling(async () => {
|
|
35
|
+
if (!this.apiKeyStamper) {
|
|
36
|
+
throw new TurnkeyError("API key stamper is not initialized", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
37
|
+
}
|
|
38
|
+
const { temporaryPublicKey } = params;
|
|
39
|
+
// Only process if temporaryPublicKey is explicitly passed
|
|
40
|
+
if (temporaryPublicKey !== undefined) {
|
|
41
|
+
if (temporaryPublicKey === "") {
|
|
42
|
+
this.apiKeyStamper.clearTemporaryPublicKey();
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.apiKeyStamper.setTemporaryPublicKey(temporaryPublicKey);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, {
|
|
49
|
+
errorMessage: "Failed to override API key stamper",
|
|
50
|
+
errorCode: TurnkeyErrorCodes.INITIALIZE_API_KEY_STAMPER_ERROR,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Overrides the passkey stamper with a new configuration.
|
|
55
|
+
*
|
|
56
|
+
* - This function updates the passkey stamper configuration without reinitializing the entire stamper.
|
|
57
|
+
* - Updates the stamper's configuration properties (e.g., allowCredentials, rpId, timeout, userVerification).
|
|
58
|
+
* - Useful for dynamically changing passkey configuration during runtime.
|
|
59
|
+
*
|
|
60
|
+
* @param params.config - new passkey stamper configuration to use.
|
|
61
|
+
* @returns A promise that resolves when the stamper configuration has been updated.
|
|
62
|
+
* @throws {TurnkeyError} If there is an error updating the stamper configuration or if the passkey stamper is not initialized.
|
|
63
|
+
*/
|
|
64
|
+
this.overridePasskeyStamper = async (params) => {
|
|
65
|
+
const { config } = params;
|
|
66
|
+
return withTurnkeyErrorHandling(async () => {
|
|
67
|
+
if (!this.passkeyStamper) {
|
|
68
|
+
throw new TurnkeyError("Passkey stamper is not initialized", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
69
|
+
}
|
|
70
|
+
this.passkeyStamper.updateConfig(config);
|
|
71
|
+
}, {
|
|
72
|
+
errorMessage: "Failed to override passkey stamper",
|
|
73
|
+
errorCode: TurnkeyErrorCodes.INITIALIZE_PASSKEY_STAMPER_ERROR,
|
|
74
|
+
});
|
|
75
|
+
};
|
|
33
76
|
/**
|
|
34
77
|
* Creates a new TurnkeySDKClientBase instance with the provided configuration.
|
|
35
78
|
* This method is used internally to create the HTTP client for making API requests,
|
|
@@ -147,14 +190,14 @@ class TurnkeyClient {
|
|
|
147
190
|
if (params?.sessionKey) {
|
|
148
191
|
const session = await this.storageManager.getSession(params.sessionKey);
|
|
149
192
|
this.storageManager.clearSession(params.sessionKey);
|
|
150
|
-
this.
|
|
193
|
+
await this.deleteApiKeyPair({ publicKey: session?.publicKey });
|
|
151
194
|
}
|
|
152
195
|
else {
|
|
153
196
|
const sessionKey = await this.storageManager.getActiveSessionKey();
|
|
154
197
|
const session = await this.storageManager.getActiveSession();
|
|
155
198
|
if (sessionKey) {
|
|
156
199
|
this.storageManager.clearSession(sessionKey);
|
|
157
|
-
this.
|
|
200
|
+
await this.deleteApiKeyPair({ publicKey: session?.publicKey });
|
|
158
201
|
}
|
|
159
202
|
else {
|
|
160
203
|
throw new TurnkeyError("No active session found to log out from.", TurnkeyErrorCodes.NO_SESSION_FOUND);
|
|
@@ -178,18 +221,30 @@ class TurnkeyClient {
|
|
|
178
221
|
* @param params.sessionKey - session key to use for session creation (defaults to the default session key).
|
|
179
222
|
* @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
|
|
180
223
|
* @param params.organizationId - organization ID to target (defaults to the session's organization ID or the parent organization ID).
|
|
224
|
+
* @param params.allowCredentials - optional list of allowed credentials for passkey authentication. This allows you to restrict which passkeys can be used for login.
|
|
181
225
|
* @returns A promise that resolves to a {@link PasskeyAuthResult}, which includes:
|
|
182
226
|
* - `sessionToken`: the signed JWT session token.
|
|
183
227
|
* - `credentialId`: an empty string.
|
|
184
228
|
* @throws {TurnkeyError} If there is an error during the passkey login process or if the user cancels the passkey prompt.
|
|
185
229
|
*/
|
|
186
230
|
this.loginWithPasskey = async (params) => {
|
|
187
|
-
|
|
231
|
+
const shouldOverrideConfig = params?.allowCredentials && this.passkeyStamper;
|
|
232
|
+
const currentConfig = this.config.passkeyConfig;
|
|
233
|
+
const generatedPublicKey = params?.publicKey || (await this.createApiKeyPair());
|
|
188
234
|
return await withTurnkeyErrorHandling(async () => {
|
|
189
|
-
|
|
190
|
-
|
|
235
|
+
if (!this.passkeyStamper) {
|
|
236
|
+
throw new TurnkeyError("Passkey stamper is not initialized", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
237
|
+
}
|
|
191
238
|
const sessionKey = params?.sessionKey || SessionKey.DefaultSessionkey;
|
|
192
239
|
const expirationSeconds = params?.expirationSeconds || DEFAULT_SESSION_EXPIRATION_IN_SECONDS;
|
|
240
|
+
if (shouldOverrideConfig) {
|
|
241
|
+
// Override passkey stamper config to include allowCredentials
|
|
242
|
+
const mergedConfig = {
|
|
243
|
+
...currentConfig,
|
|
244
|
+
allowCredentials: params?.allowCredentials, // Can safely assert non-null due to check above
|
|
245
|
+
};
|
|
246
|
+
await this.overridePasskeyStamper({ config: mergedConfig });
|
|
247
|
+
}
|
|
193
248
|
if (!generatedPublicKey) {
|
|
194
249
|
throw new TurnkeyError("A publickey could not be found or generated.", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
195
250
|
}
|
|
@@ -202,7 +257,6 @@ class TurnkeyClient {
|
|
|
202
257
|
sessionToken: sessionResponse.session,
|
|
203
258
|
sessionKey,
|
|
204
259
|
});
|
|
205
|
-
generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
|
|
206
260
|
return {
|
|
207
261
|
sessionToken: sessionResponse.session,
|
|
208
262
|
// TODO: can we return the credentialId here?
|
|
@@ -219,15 +273,23 @@ class TurnkeyClient {
|
|
|
219
273
|
code: TurnkeyErrorCodes.SELECT_PASSKEY_CANCELLED,
|
|
220
274
|
},
|
|
221
275
|
},
|
|
276
|
+
catchFn: async () => {
|
|
277
|
+
try {
|
|
278
|
+
await this.deleteApiKeyPair({ publicKey: generatedPublicKey });
|
|
279
|
+
}
|
|
280
|
+
catch (cleanupError) {
|
|
281
|
+
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
282
|
+
}
|
|
283
|
+
},
|
|
222
284
|
}, {
|
|
223
285
|
finallyFn: async () => {
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
286
|
+
if (shouldOverrideConfig) {
|
|
287
|
+
// we restore previous stamper after login attempt
|
|
288
|
+
await this.overridePasskeyStamper({
|
|
289
|
+
// we can assert this safely, since if this statement runs we are certian the passkey stamper
|
|
290
|
+
// is initialized
|
|
291
|
+
config: currentConfig,
|
|
292
|
+
});
|
|
231
293
|
}
|
|
232
294
|
},
|
|
233
295
|
});
|
|
@@ -246,17 +308,18 @@ class TurnkeyClient {
|
|
|
246
308
|
* @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
|
|
247
309
|
* @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
|
|
248
310
|
* @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
|
|
249
|
-
* @param params.organizationId - organization ID to target (defaults to the session's organization ID or the parent organization ID).
|
|
250
311
|
* @returns A promise that resolves to a {@link PasskeyAuthResult}, which includes:
|
|
251
312
|
* - `sessionToken`: the signed JWT session token.
|
|
252
313
|
* - `credentialId`: the credential ID associated with the passkey created.
|
|
253
314
|
* @throws {TurnkeyError} If there is an error during passkey creation, sub-organization creation, or session storage.
|
|
254
315
|
*/
|
|
255
316
|
this.signUpWithPasskey = async (params) => {
|
|
256
|
-
const { passkeyDisplayName, challenge, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey,
|
|
257
|
-
|
|
317
|
+
const { passkeyDisplayName, challenge, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, } = params || {};
|
|
318
|
+
const generatedPublicKey = await this.createApiKeyPair();
|
|
258
319
|
return withTurnkeyErrorHandling(async () => {
|
|
259
|
-
|
|
320
|
+
if (!this.passkeyStamper) {
|
|
321
|
+
throw new TurnkeyError("Passkey stamper is not initialized", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
322
|
+
}
|
|
260
323
|
const passkeyName = passkeyDisplayName || `passkey-${Date.now()}`;
|
|
261
324
|
// A passkey will be created automatically when you call this function. The name is passed in
|
|
262
325
|
const passkey = await this.createPasskey({
|
|
@@ -288,25 +351,23 @@ class TurnkeyClient {
|
|
|
288
351
|
],
|
|
289
352
|
},
|
|
290
353
|
});
|
|
291
|
-
const res = await this.httpClient.
|
|
354
|
+
const res = await this.httpClient.proxySignupV2(signUpBody);
|
|
292
355
|
if (!res) {
|
|
293
356
|
throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.PASSKEY_SIGNUP_AUTH_ERROR);
|
|
294
357
|
}
|
|
295
|
-
const newGeneratedKeyPair = await this.
|
|
296
|
-
this.
|
|
358
|
+
const newGeneratedKeyPair = await this.createApiKeyPair();
|
|
359
|
+
await this.overrideApiKeyStamper({
|
|
360
|
+
temporaryPublicKey: generatedPublicKey,
|
|
361
|
+
});
|
|
297
362
|
const sessionResponse = await this.httpClient.stampLogin({
|
|
298
363
|
publicKey: newGeneratedKeyPair,
|
|
299
|
-
organizationId:
|
|
364
|
+
organizationId: res.organizationId,
|
|
300
365
|
expirationSeconds,
|
|
301
366
|
});
|
|
302
|
-
await
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
sessionKey,
|
|
307
|
-
}),
|
|
308
|
-
]);
|
|
309
|
-
generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
|
|
367
|
+
await this.storeSession({
|
|
368
|
+
sessionToken: sessionResponse.session,
|
|
369
|
+
sessionKey,
|
|
370
|
+
});
|
|
310
371
|
return {
|
|
311
372
|
sessionToken: sessionResponse.session,
|
|
312
373
|
appProofs: res.appProofs,
|
|
@@ -317,14 +378,12 @@ class TurnkeyClient {
|
|
|
317
378
|
errorMessage: "Failed to sign up with passkey",
|
|
318
379
|
}, {
|
|
319
380
|
finallyFn: async () => {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
327
|
-
}
|
|
381
|
+
try {
|
|
382
|
+
await this.overrideApiKeyStamper({ temporaryPublicKey: "" });
|
|
383
|
+
await this.deleteApiKeyPair({ publicKey: generatedPublicKey });
|
|
384
|
+
}
|
|
385
|
+
catch (cleanupError) {
|
|
386
|
+
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
328
387
|
}
|
|
329
388
|
},
|
|
330
389
|
});
|
|
@@ -464,13 +523,11 @@ class TurnkeyClient {
|
|
|
464
523
|
this.buildWalletLoginRequest = async (params) => {
|
|
465
524
|
const { walletProvider, publicKey: providedPublicKey } = params;
|
|
466
525
|
const expirationSeconds = params.expirationSeconds || DEFAULT_SESSION_EXPIRATION_IN_SECONDS;
|
|
467
|
-
|
|
526
|
+
const futureSessionPublicKey = providedPublicKey ?? (await this.createApiKeyPair());
|
|
468
527
|
return withTurnkeyErrorHandling(async () => {
|
|
469
528
|
if (!this.walletManager?.stamper) {
|
|
470
529
|
throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
|
|
471
530
|
}
|
|
472
|
-
const futureSessionPublicKey = providedPublicKey ??
|
|
473
|
-
(generatedPublicKey = await this.apiKeyStamper?.createKeyPair());
|
|
474
531
|
if (!futureSessionPublicKey) {
|
|
475
532
|
throw new TurnkeyError("Failed to find or generate a public key for building the wallet login request", TurnkeyErrorCodes.WALLET_BUILD_LOGIN_REQUEST_ERROR);
|
|
476
533
|
}
|
|
@@ -512,9 +569,12 @@ class TurnkeyClient {
|
|
|
512
569
|
errorCode: TurnkeyErrorCodes.WALLET_BUILD_LOGIN_REQUEST_ERROR,
|
|
513
570
|
errorMessage: "Failed to build wallet login request",
|
|
514
571
|
catchFn: async () => {
|
|
515
|
-
if
|
|
572
|
+
// we only clean up the generated key pair if we created it here
|
|
573
|
+
if (!providedPublicKey) {
|
|
516
574
|
try {
|
|
517
|
-
await this.
|
|
575
|
+
await this.deleteApiKeyPair({
|
|
576
|
+
publicKey: futureSessionPublicKey,
|
|
577
|
+
});
|
|
518
578
|
}
|
|
519
579
|
catch (cleanupError) {
|
|
520
580
|
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
@@ -599,7 +659,7 @@ class TurnkeyClient {
|
|
|
599
659
|
],
|
|
600
660
|
},
|
|
601
661
|
});
|
|
602
|
-
const res = await this.httpClient.
|
|
662
|
+
const res = await this.httpClient.proxySignupV2(signUpBody);
|
|
603
663
|
if (!res) {
|
|
604
664
|
throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
|
|
605
665
|
}
|
|
@@ -674,7 +734,7 @@ class TurnkeyClient {
|
|
|
674
734
|
],
|
|
675
735
|
},
|
|
676
736
|
});
|
|
677
|
-
signupRes = await this.httpClient.
|
|
737
|
+
signupRes = await this.httpClient.proxySignupV2(signUpBody);
|
|
678
738
|
if (!signupRes) {
|
|
679
739
|
throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
|
|
680
740
|
}
|
|
@@ -706,21 +766,29 @@ class TurnkeyClient {
|
|
|
706
766
|
*
|
|
707
767
|
* - This function initiates the OTP flow by sending a one-time password (OTP) code to the user's contact information (email address or phone number) via the auth proxy.
|
|
708
768
|
* - Supports both email and SMS OTP types.
|
|
709
|
-
* - Returns an OTP ID
|
|
769
|
+
* - Returns an OTP ID and an encryption target bundle required for subsequent OTP verification.
|
|
770
|
+
* - The `otpEncryptionTargetBundle` must be used to encrypt the OTP code and a client-generated public key before calling `verifyOtp`.
|
|
710
771
|
*
|
|
711
772
|
* @param params.otpType - type of OTP to initialize (OtpType.Email or OtpType.Sms).
|
|
712
773
|
* @param params.contact - contact information for the user (e.g., email address or phone number).
|
|
713
774
|
* @param params.organizationId - optional organization ID to target (defaults to the session's organization ID or the parent organization ID).
|
|
714
|
-
* @returns A promise that resolves to
|
|
775
|
+
* @returns A promise that resolves to an {@link InitOtpResult}, which includes:
|
|
776
|
+
* - `otpId`: the ID of the initiated OTP.
|
|
777
|
+
* - `otpEncryptionTargetBundle`: the signed encryption target bundle for encrypting the OTP code.
|
|
715
778
|
* @throws {TurnkeyError} If there is an error during the OTP initialization process or if the maximum number of OTPs has been reached.
|
|
716
779
|
*/
|
|
717
780
|
this.initOtp = async (params) => {
|
|
718
781
|
return withTurnkeyErrorHandling(async () => {
|
|
719
|
-
const initOtpRes = await this.httpClient.
|
|
720
|
-
if (!initOtpRes ||
|
|
721
|
-
|
|
782
|
+
const initOtpRes = await this.httpClient.proxyInitOtpV2(params);
|
|
783
|
+
if (!initOtpRes ||
|
|
784
|
+
!initOtpRes.otpId ||
|
|
785
|
+
!initOtpRes.otpEncryptionTargetBundle) {
|
|
786
|
+
throw new TurnkeyError("Failed to initialize OTP: otpId or otpEncryptionTargetBundle is missing", TurnkeyErrorCodes.INIT_OTP_ERROR);
|
|
722
787
|
}
|
|
723
|
-
return
|
|
788
|
+
return {
|
|
789
|
+
otpId: initOtpRes.otpId,
|
|
790
|
+
otpEncryptionTargetBundle: initOtpRes.otpEncryptionTargetBundle,
|
|
791
|
+
};
|
|
724
792
|
}, {
|
|
725
793
|
errorMessage: "Failed to initialize OTP",
|
|
726
794
|
errorCode: TurnkeyErrorCodes.INIT_OTP_ERROR,
|
|
@@ -735,45 +803,38 @@ class TurnkeyClient {
|
|
|
735
803
|
/**
|
|
736
804
|
* Verifies the OTP code sent to the user.
|
|
737
805
|
*
|
|
738
|
-
* -
|
|
739
|
-
* - If verification is successful, it returns
|
|
806
|
+
* - Under the hood, the OTP code and an ephemeral client public key are encrypted to the enclave's target key (from `initOtp`) before being sent for verification.
|
|
807
|
+
* - If verification is successful, it returns a verification token bound to the public key.
|
|
740
808
|
* - The verification token can be used for subsequent login or sign-up flows.
|
|
741
|
-
* - Handles both email and SMS OTP types.
|
|
742
809
|
*
|
|
743
810
|
* @param params.otpId - ID of the OTP to verify (returned from `initOtp`).
|
|
744
|
-
* @param params.otpCode - OTP code entered by the user.
|
|
745
|
-
* @param params.
|
|
746
|
-
* @param params.
|
|
747
|
-
* @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
|
+
* @param params.otpCode - the OTP code entered by the user.
|
|
812
|
+
* @param params.otpEncryptionTargetBundle - the encryption target bundle returned from `initOtp`.
|
|
813
|
+
* @param params.publicKey - optional public key to bind to the verification token. If not provided, a new key pair will be generated via the configured `apiKeyStamper`.
|
|
748
814
|
* @returns A promise that resolves to an object containing:
|
|
749
|
-
* - subOrganizationId: sub-organization ID if the contact is already associated with a sub-organization, or an empty string if not.
|
|
750
815
|
* - verificationToken: verification token to be used for login or sign-up.
|
|
816
|
+
* - publicKey: the public key bound to the verification token (either the one provided or the auto-generated one).
|
|
751
817
|
* @throws {TurnkeyError} If there is an error during the OTP verification process, such as an invalid code or network failure.
|
|
752
818
|
*/
|
|
753
819
|
this.verifyOtp = async (params) => {
|
|
754
|
-
const { otpId, otpCode,
|
|
755
|
-
|
|
820
|
+
const { otpId, otpCode, otpEncryptionTargetBundle } = params;
|
|
821
|
+
// we track auto-generated keys so we can clean them up on errors
|
|
822
|
+
const publicKey = params.publicKey ?? (await this.createApiKeyPair());
|
|
823
|
+
if (!publicKey) {
|
|
824
|
+
throw new TurnkeyError("No public key available. Either pass a publicKey or ensure apiKeyStamper is configured.", TurnkeyErrorCodes.INVALID_REQUEST);
|
|
825
|
+
}
|
|
756
826
|
return withTurnkeyErrorHandling(async () => {
|
|
757
|
-
const
|
|
827
|
+
const encryptedOtpBundle = await encryptOtpCodeToBundle(otpCode, otpEncryptionTargetBundle, publicKey);
|
|
828
|
+
const verifyOtpRes = await this.httpClient.proxyVerifyOtpV2({
|
|
758
829
|
otpId: otpId,
|
|
759
|
-
|
|
760
|
-
publicKey: resolvedPublicKey,
|
|
830
|
+
encryptedOtpBundle: encryptedOtpBundle,
|
|
761
831
|
});
|
|
762
832
|
if (!verifyOtpRes) {
|
|
763
833
|
throw new TurnkeyError(`OTP verification failed`, TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
764
834
|
}
|
|
765
|
-
const accountRes = await this.httpClient.proxyGetAccount({
|
|
766
|
-
filterType: OtpTypeToFilterTypeMap[otpType],
|
|
767
|
-
filterValue: contact,
|
|
768
|
-
verificationToken: verifyOtpRes.verificationToken,
|
|
769
|
-
});
|
|
770
|
-
if (!accountRes) {
|
|
771
|
-
throw new TurnkeyError(`Account fetch failed`, TurnkeyErrorCodes.ACCOUNT_FETCH_ERROR);
|
|
772
|
-
}
|
|
773
|
-
const subOrganizationId = accountRes.organizationId;
|
|
774
835
|
return {
|
|
775
|
-
subOrganizationId: subOrganizationId,
|
|
776
836
|
verificationToken: verifyOtpRes.verificationToken,
|
|
837
|
+
publicKey,
|
|
777
838
|
};
|
|
778
839
|
}, {
|
|
779
840
|
errorMessage: "Failed to verify OTP",
|
|
@@ -784,19 +845,29 @@ class TurnkeyClient {
|
|
|
784
845
|
code: TurnkeyErrorCodes.INVALID_OTP_CODE,
|
|
785
846
|
},
|
|
786
847
|
},
|
|
848
|
+
catchFn: async () => {
|
|
849
|
+
// we only clean up the generated key pair if we created it here
|
|
850
|
+
if (!params.publicKey) {
|
|
851
|
+
try {
|
|
852
|
+
await this.deleteApiKeyPair({ publicKey });
|
|
853
|
+
}
|
|
854
|
+
catch (cleanupError) {
|
|
855
|
+
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
},
|
|
787
859
|
});
|
|
788
860
|
};
|
|
789
861
|
/**
|
|
790
862
|
* Logs in a user using an OTP verification token.
|
|
791
863
|
*
|
|
792
864
|
* - This function logs in a user using the verification token received after OTP verification (from email or SMS).
|
|
793
|
-
* -
|
|
865
|
+
* - The client signature is always produced using the private key bound to the verification token during `verifyOtp`.
|
|
866
|
+
* - The verification token's embedded key becomes the session public key.
|
|
794
867
|
* - Optionally invalidates any existing sessions for the user if `invalidateExisting` is set to true.
|
|
795
868
|
* - Stores the resulting session token under the specified session key, or the default session key if not provided.
|
|
796
|
-
* - Handles cleanup of unused key pairs if login fails.
|
|
797
869
|
*
|
|
798
870
|
* @param params.verificationToken - verification token received after OTP verification.
|
|
799
|
-
* @param params.publicKey - public key to use for authentication. If not provided, a new key pair will be generated.
|
|
800
871
|
* @param params.organizationId - optional organization ID to target (defaults to the verified subOrg ID linked to the verification token contact).
|
|
801
872
|
* @param params.invalidateExisting - flag to invalidate existing session for the user.
|
|
802
873
|
* @param params.sessionKey - session key to use for session creation (defaults to the default session key).
|
|
@@ -805,34 +876,35 @@ class TurnkeyClient {
|
|
|
805
876
|
* @throws {TurnkeyError} If there is an error during the OTP login process or if key pair cleanup fails.
|
|
806
877
|
*/
|
|
807
878
|
this.loginWithOtp = async (params) => {
|
|
808
|
-
const { verificationToken, invalidateExisting = false,
|
|
879
|
+
const { verificationToken, invalidateExisting = false, organizationId, sessionKey = SessionKey.DefaultSessionkey, } = params;
|
|
809
880
|
return withTurnkeyErrorHandling(async () => {
|
|
810
|
-
const { message, publicKey:
|
|
811
|
-
|
|
812
|
-
|
|
881
|
+
const { message, publicKey: verificationPublicKey } = getClientSignatureMessageForLogin({ verificationToken });
|
|
882
|
+
// we sign with the verification token key. This is the key bound during
|
|
883
|
+
// verifyOtp() and is what Turnkey expects to sign the client signature for login
|
|
884
|
+
const signature = await this.signWithApiKey({
|
|
885
|
+
message,
|
|
886
|
+
publicKey: verificationPublicKey,
|
|
813
887
|
});
|
|
814
|
-
this.apiKeyStamper?.setTemporaryPublicKey(publicKey);
|
|
815
|
-
const signature = await this.apiKeyStamper?.sign(message, SignatureFormat.Raw);
|
|
816
888
|
if (!signature) {
|
|
817
889
|
throw new TurnkeyError(`Failed to sign client signature for OTP login`, TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
818
890
|
}
|
|
819
891
|
const clientSignature = {
|
|
820
892
|
message: message,
|
|
821
|
-
publicKey:
|
|
893
|
+
publicKey: verificationPublicKey,
|
|
822
894
|
scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
|
|
823
895
|
signature: signature,
|
|
824
896
|
};
|
|
825
|
-
|
|
897
|
+
// the verification token's public key is also used as the session public key for login
|
|
898
|
+
const loginRes = await this.httpClient.proxyOtpLoginV2({
|
|
826
899
|
verificationToken,
|
|
827
|
-
publicKey:
|
|
828
|
-
invalidateExisting,
|
|
900
|
+
publicKey: verificationPublicKey,
|
|
829
901
|
clientSignature,
|
|
902
|
+
invalidateExisting,
|
|
830
903
|
...(organizationId && { organizationId }),
|
|
831
904
|
});
|
|
832
|
-
if (!
|
|
905
|
+
if (!loginRes) {
|
|
833
906
|
throw new TurnkeyError(`Auth proxy OTP login failed`, TurnkeyErrorCodes.OTP_LOGIN_ERROR);
|
|
834
907
|
}
|
|
835
|
-
const loginRes = await res;
|
|
836
908
|
if (!loginRes.session) {
|
|
837
909
|
throw new TurnkeyError("No session returned from OTP login", TurnkeyErrorCodes.OTP_LOGIN_ERROR);
|
|
838
910
|
}
|
|
@@ -846,21 +918,6 @@ class TurnkeyClient {
|
|
|
846
918
|
}, {
|
|
847
919
|
errorMessage: "Failed to log in with OTP",
|
|
848
920
|
errorCode: TurnkeyErrorCodes.OTP_LOGIN_ERROR,
|
|
849
|
-
catchFn: async () => {
|
|
850
|
-
// Clean up the generated key pair if it wasn't successfully used
|
|
851
|
-
if (publicKey) {
|
|
852
|
-
try {
|
|
853
|
-
await this.apiKeyStamper?.deleteKeyPair(publicKey);
|
|
854
|
-
}
|
|
855
|
-
catch (cleanupError) {
|
|
856
|
-
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
},
|
|
860
|
-
}, {
|
|
861
|
-
finallyFn: async () => {
|
|
862
|
-
this.apiKeyStamper?.clearTemporaryPublicKey();
|
|
863
|
-
},
|
|
864
921
|
});
|
|
865
922
|
};
|
|
866
923
|
/**
|
|
@@ -868,7 +925,7 @@ class TurnkeyClient {
|
|
|
868
925
|
*
|
|
869
926
|
* - This function signs up a user using the verification token received after OTP verification (from email or SMS).
|
|
870
927
|
* - Creates a new sub-organization for the user with the provided parameters and associates the contact (email or phone) with the sub-organization.
|
|
871
|
-
* -
|
|
928
|
+
* - The verification token's embedded key becomes the session public key.
|
|
872
929
|
* - Stores the resulting session token under the specified session key, or the default session key if not provided.
|
|
873
930
|
* - Handles both email and SMS OTP types, and supports additional sub-organization creation parameters.
|
|
874
931
|
*
|
|
@@ -883,7 +940,7 @@ class TurnkeyClient {
|
|
|
883
940
|
* @throws {TurnkeyError} If there is an error during the OTP sign-up process or session storage.
|
|
884
941
|
*/
|
|
885
942
|
this.signUpWithOtp = async (params) => {
|
|
886
|
-
const { verificationToken, contact, otpType, createSubOrgParams, invalidateExisting, sessionKey,
|
|
943
|
+
const { verificationToken, contact, otpType, createSubOrgParams, invalidateExisting, sessionKey, } = params;
|
|
887
944
|
// build sign up body without client signature first
|
|
888
945
|
const signUpBody = buildSignUpBody({
|
|
889
946
|
createSubOrgParams: {
|
|
@@ -895,7 +952,7 @@ class TurnkeyClient {
|
|
|
895
952
|
},
|
|
896
953
|
});
|
|
897
954
|
return withTurnkeyErrorHandling(async () => {
|
|
898
|
-
const { message, publicKey:
|
|
955
|
+
const { message, publicKey: verificationPublicKey } = getClientSignatureMessageForSignup({
|
|
899
956
|
verificationToken,
|
|
900
957
|
...(signUpBody.userEmail && { email: signUpBody.userEmail }),
|
|
901
958
|
...(signUpBody.userPhoneNumber && {
|
|
@@ -905,18 +962,22 @@ class TurnkeyClient {
|
|
|
905
962
|
authenticators: signUpBody.authenticators,
|
|
906
963
|
oauthProviders: signUpBody.oauthProviders,
|
|
907
964
|
});
|
|
908
|
-
|
|
909
|
-
|
|
965
|
+
// we sign with the verification token key. This is the key bound during
|
|
966
|
+
// verifyOtp() and is what Turnkey expects to sign the client signature for signup
|
|
967
|
+
const signature = await this.signWithApiKey({
|
|
968
|
+
message,
|
|
969
|
+
publicKey: verificationPublicKey,
|
|
970
|
+
});
|
|
910
971
|
if (!signature) {
|
|
911
972
|
throw new TurnkeyError(`Failed to sign client signature for OTP sign up`, TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
912
973
|
}
|
|
913
974
|
const clientSignature = {
|
|
914
975
|
message: message,
|
|
915
|
-
publicKey:
|
|
976
|
+
publicKey: verificationPublicKey,
|
|
916
977
|
scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
|
|
917
978
|
signature: signature,
|
|
918
979
|
};
|
|
919
|
-
const signupRes = await this.httpClient.
|
|
980
|
+
const signupRes = await this.httpClient.proxySignupV2({
|
|
920
981
|
...signUpBody,
|
|
921
982
|
clientSignature,
|
|
922
983
|
});
|
|
@@ -925,7 +986,6 @@ class TurnkeyClient {
|
|
|
925
986
|
}
|
|
926
987
|
const otpRes = await this.loginWithOtp({
|
|
927
988
|
verificationToken,
|
|
928
|
-
publicKey: publicKey,
|
|
929
989
|
...(invalidateExisting && { invalidateExisting }),
|
|
930
990
|
...(sessionKey && { sessionKey }),
|
|
931
991
|
});
|
|
@@ -934,39 +994,26 @@ class TurnkeyClient {
|
|
|
934
994
|
appProofs: signupRes.appProofs,
|
|
935
995
|
};
|
|
936
996
|
}, {
|
|
937
|
-
catchFn: async () => {
|
|
938
|
-
// Clean up the generated key pair if it wasn't successfully used
|
|
939
|
-
if (publicKey) {
|
|
940
|
-
try {
|
|
941
|
-
await this.apiKeyStamper?.deleteKeyPair(publicKey);
|
|
942
|
-
}
|
|
943
|
-
catch (cleanupError) {
|
|
944
|
-
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
},
|
|
948
997
|
errorCode: TurnkeyErrorCodes.OTP_SIGNUP_ERROR,
|
|
949
998
|
errorMessage: "Failed to sign up with OTP",
|
|
950
|
-
}, {
|
|
951
|
-
finallyFn: async () => {
|
|
952
|
-
this.apiKeyStamper?.clearTemporaryPublicKey();
|
|
953
|
-
},
|
|
954
999
|
});
|
|
955
1000
|
};
|
|
956
1001
|
/**
|
|
957
|
-
* Completes the OTP authentication flow by verifying the OTP
|
|
1002
|
+
* Completes the OTP authentication flow by verifying the encrypted OTP bundle and then either signing up or logging in the user.
|
|
958
1003
|
*
|
|
959
|
-
* - This function
|
|
1004
|
+
* - This function encrypts the OTP code and the session public key to the enclave's target key, verifies the OTP, and then either signs up or logs in the user.
|
|
960
1005
|
* - If the contact is not associated with an existing sub-organization, it will automatically create a new sub-organization and complete the sign-up flow.
|
|
961
1006
|
* - If the contact is already associated with a sub-organization, it will complete the login flow.
|
|
962
|
-
* -
|
|
1007
|
+
* - The key bound to the verification token during `verifyOtp` is always reused as the session public key.
|
|
1008
|
+
* - Supports passing a custom public key for binding into the verification token, invalidating existing session, specifying a session key, and providing additional sub-organization creation parameters.
|
|
963
1009
|
* - Handles both email and SMS OTP types.
|
|
964
1010
|
*
|
|
965
1011
|
* @param params.otpId - ID of the OTP to complete (returned from `initOtp`).
|
|
966
|
-
* @param params.otpCode - OTP code entered by the user.
|
|
1012
|
+
* @param params.otpCode - the OTP code entered by the user.
|
|
1013
|
+
* @param params.otpEncryptionTargetBundle - the encryption target bundle returned from `initOtp`.
|
|
967
1014
|
* @param params.contact - contact information for the user (e.g., email address or phone number).
|
|
968
1015
|
* @param params.otpType - type of OTP being completed (OtpType.Email or OtpType.Sms).
|
|
969
|
-
* @param params.publicKey - public key to
|
|
1016
|
+
* @param params.publicKey - public key to bind to the verification token via `verifyOtp`. If not provided, a new key pair will be generated. This key becomes the session public key.
|
|
970
1017
|
* @param params.invalidateExisting - flag to invalidate existing sessions for the user.
|
|
971
1018
|
* @param params.sessionKey - session key to use for session creation (defaults to the default session key).
|
|
972
1019
|
* @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
|
|
@@ -977,18 +1024,30 @@ class TurnkeyClient {
|
|
|
977
1024
|
* @throws {TurnkeyError} If there is an error during OTP verification, sign-up, or login.
|
|
978
1025
|
*/
|
|
979
1026
|
this.completeOtp = async (params) => {
|
|
980
|
-
const { otpId, otpCode, contact, otpType,
|
|
1027
|
+
const { otpId, otpCode, otpEncryptionTargetBundle, contact, otpType, invalidateExisting = false, sessionKey, createSubOrgParams, } = params;
|
|
1028
|
+
const publicKey = params.publicKey ?? (await this.createApiKeyPair());
|
|
1029
|
+
if (!publicKey) {
|
|
1030
|
+
throw new TurnkeyError("No public key available. Either pass a publicKey or ensure apiKeyStamper is configured.", TurnkeyErrorCodes.INVALID_REQUEST);
|
|
1031
|
+
}
|
|
981
1032
|
return withTurnkeyErrorHandling(async () => {
|
|
982
|
-
const {
|
|
983
|
-
otpId
|
|
984
|
-
otpCode
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
publicKey: publicKey,
|
|
1033
|
+
const { verificationToken } = await this.verifyOtp({
|
|
1034
|
+
otpId,
|
|
1035
|
+
otpCode,
|
|
1036
|
+
otpEncryptionTargetBundle,
|
|
1037
|
+
publicKey,
|
|
988
1038
|
});
|
|
989
1039
|
if (!verificationToken) {
|
|
990
1040
|
throw new TurnkeyError("No verification token returned from OTP verification", TurnkeyErrorCodes.VERIFY_OTP_ERROR);
|
|
991
1041
|
}
|
|
1042
|
+
const accountRes = await this.httpClient.proxyGetAccount({
|
|
1043
|
+
filterType: OtpTypeToFilterTypeMap[otpType],
|
|
1044
|
+
filterValue: contact,
|
|
1045
|
+
verificationToken: verificationToken,
|
|
1046
|
+
});
|
|
1047
|
+
if (!accountRes) {
|
|
1048
|
+
throw new TurnkeyError(`Account fetch failed`, TurnkeyErrorCodes.ACCOUNT_FETCH_ERROR);
|
|
1049
|
+
}
|
|
1050
|
+
const subOrganizationId = accountRes.organizationId;
|
|
992
1051
|
if (!subOrganizationId) {
|
|
993
1052
|
const signUpRes = await this.signUpWithOtp({
|
|
994
1053
|
verificationToken,
|
|
@@ -997,7 +1056,6 @@ class TurnkeyClient {
|
|
|
997
1056
|
...(createSubOrgParams && { createSubOrgParams }),
|
|
998
1057
|
...(invalidateExisting && { invalidateExisting }),
|
|
999
1058
|
...(sessionKey && { sessionKey }),
|
|
1000
|
-
publicKey: publicKey,
|
|
1001
1059
|
});
|
|
1002
1060
|
return {
|
|
1003
1061
|
...signUpRes,
|
|
@@ -1008,7 +1066,6 @@ class TurnkeyClient {
|
|
|
1008
1066
|
else {
|
|
1009
1067
|
const loginRes = await this.loginWithOtp({
|
|
1010
1068
|
verificationToken,
|
|
1011
|
-
publicKey: publicKey,
|
|
1012
1069
|
...(invalidateExisting && { invalidateExisting }),
|
|
1013
1070
|
...(sessionKey && { sessionKey }),
|
|
1014
1071
|
});
|
|
@@ -1021,6 +1078,16 @@ class TurnkeyClient {
|
|
|
1021
1078
|
}, {
|
|
1022
1079
|
errorMessage: "Failed to complete OTP process",
|
|
1023
1080
|
errorCode: TurnkeyErrorCodes.OTP_COMPLETION_ERROR,
|
|
1081
|
+
catchFn: async () => {
|
|
1082
|
+
if (!params.publicKey) {
|
|
1083
|
+
try {
|
|
1084
|
+
await this.deleteApiKeyPair({ publicKey });
|
|
1085
|
+
}
|
|
1086
|
+
catch (cleanupError) {
|
|
1087
|
+
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1024
1091
|
});
|
|
1025
1092
|
};
|
|
1026
1093
|
/**
|
|
@@ -1145,7 +1212,7 @@ class TurnkeyClient {
|
|
|
1145
1212
|
// Clean up the generated key pair if it wasn't successfully used
|
|
1146
1213
|
if (publicKey) {
|
|
1147
1214
|
try {
|
|
1148
|
-
await this.
|
|
1215
|
+
await this.deleteApiKeyPair({ publicKey });
|
|
1149
1216
|
}
|
|
1150
1217
|
catch (cleanupError) {
|
|
1151
1218
|
throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
@@ -1182,10 +1249,11 @@ class TurnkeyClient {
|
|
|
1182
1249
|
providerName,
|
|
1183
1250
|
oidcToken,
|
|
1184
1251
|
},
|
|
1252
|
+
...(createSubOrgParams?.oauthProviders ?? []),
|
|
1185
1253
|
],
|
|
1186
1254
|
},
|
|
1187
1255
|
});
|
|
1188
|
-
const signupRes = await this.httpClient.
|
|
1256
|
+
const signupRes = await this.httpClient.proxySignupV2(signUpBody);
|
|
1189
1257
|
if (!signupRes) {
|
|
1190
1258
|
throw new TurnkeyError(`Auth proxy OAuth signup failed`, TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR);
|
|
1191
1259
|
}
|
|
@@ -2347,37 +2415,71 @@ class TurnkeyClient {
|
|
|
2347
2415
|
});
|
|
2348
2416
|
};
|
|
2349
2417
|
/**
|
|
2350
|
-
* Adds
|
|
2418
|
+
* Adds one or more OAuth provider audiences to the user.
|
|
2351
2419
|
*
|
|
2352
|
-
* -
|
|
2353
|
-
* -
|
|
2354
|
-
* -
|
|
2355
|
-
* - If the user's email is not set or not verified, attempts to update and verify the email using the email from the
|
|
2356
|
-
* -
|
|
2420
|
+
* - Accepts either an `oidcToken`, a list of `oidcClaims`, or both. At least one must be provided.
|
|
2421
|
+
* - The issuer (`iss`) of the `oidcToken` and all `oidcClaims` must match, ensuring they belong to the same OAuth provider.
|
|
2422
|
+
* - Checks for an existing account for the `oidcToken` (if provided) and for each `oidcClaims` entry, and throws if any of them are already associated with a different sub-organization.
|
|
2423
|
+
* - If the user's email is not set or not verified and an `oidcToken` is provided, attempts to update and verify the email using the email from the token.
|
|
2424
|
+
* - Submits a single `createOauthProviders` request containing the token (if any) and all additional claims.
|
|
2357
2425
|
* - Optionally allows stamping the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
|
|
2358
2426
|
*
|
|
2359
2427
|
* @param params.providerName - name of the OAuth provider to add (e.g., "Google", "Apple").
|
|
2360
|
-
* @param params.oidcToken - OIDC token for the OAuth provider.
|
|
2428
|
+
* @param params.oidcToken - OIDC token for the OAuth provider. Either `oidcToken` or `oidcClaims` (or both) must be provided.
|
|
2429
|
+
* @param params.oidcClaims - additional `iss`/`sub`/`aud` claims to register as additional audiences for the same identity.
|
|
2361
2430
|
* @param params.organizationId - organization ID to specify the sub-organization (defaults to the current session's organizationId).
|
|
2362
2431
|
* @param params.userId - user ID to add the provider for a specific user (defaults to current session's userId).
|
|
2363
2432
|
* @param params.stampWith - parameter to stamp the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
|
|
2364
2433
|
* @returns A promise that resolves to an array of provider IDs associated with the user.
|
|
2365
|
-
* @throws {TurnkeyError} If there is no active session, if the
|
|
2434
|
+
* @throws {TurnkeyError} If there is no active session, if neither `oidcToken` nor `oidcClaims` was provided, if the issuer claims do not all match, if any of the audiences are already associated with another account, or if there is an error adding the OAuth provider.
|
|
2366
2435
|
*/
|
|
2367
2436
|
this.addOauthProvider = async (params) => {
|
|
2368
|
-
const { providerName, oidcToken, stampWith = this.config.defaultStamperType, } = params;
|
|
2437
|
+
const { providerName, oidcToken, oidcClaims = [], stampWith = this.config.defaultStamperType, } = params;
|
|
2438
|
+
if (!oidcToken && oidcClaims.length === 0) {
|
|
2439
|
+
throw new TurnkeyError("At least one of `oidcToken` or `oidcClaims` must be provided to add an OAuth provider", TurnkeyErrorCodes.INVALID_REQUEST);
|
|
2440
|
+
}
|
|
2369
2441
|
const session = await getActiveSessionOrThrowIfRequired(stampWith, this.storageManager.getActiveSession);
|
|
2370
2442
|
return withTurnkeyErrorHandling(async () => {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
if (
|
|
2379
|
-
|
|
2380
|
-
|
|
2443
|
+
// Parse the oidc token so we can get the email/iss. The email is used to update the user's email for social linking.
|
|
2444
|
+
// The iss is used to ensure all passed-in claims share the same issuer.
|
|
2445
|
+
const { email: oidcEmail, iss: tokenIss } = oidcToken
|
|
2446
|
+
? jwtDecode(oidcToken) || {}
|
|
2447
|
+
: { email: undefined, iss: undefined };
|
|
2448
|
+
// Verify that all passed-in claims share the same issuer (and match the token's issuer if a token was provided).
|
|
2449
|
+
const issuers = new Set();
|
|
2450
|
+
if (tokenIss)
|
|
2451
|
+
issuers.add(tokenIss);
|
|
2452
|
+
for (const claim of oidcClaims) {
|
|
2453
|
+
issuers.add(claim.iss);
|
|
2454
|
+
}
|
|
2455
|
+
if (issuers.size > 1) {
|
|
2456
|
+
throw new TurnkeyError("All `oidcToken` and `oidcClaims` entries must share the same issuer (`iss`)", TurnkeyErrorCodes.INVALID_REQUEST);
|
|
2457
|
+
}
|
|
2458
|
+
// Check that none of the audiences are already associated with a different sub-organization.
|
|
2459
|
+
const accountChecks = [];
|
|
2460
|
+
if (oidcToken) {
|
|
2461
|
+
// Look up the sub-organization associated with passed in oidc token
|
|
2462
|
+
accountChecks.push(this.httpClient.proxyGetAccount({
|
|
2463
|
+
filterType: FilterType.OidcToken,
|
|
2464
|
+
filterValue: oidcToken,
|
|
2465
|
+
}));
|
|
2466
|
+
}
|
|
2467
|
+
// Also look up the sub-organization for each oidcClaim
|
|
2468
|
+
for (const claim of oidcClaims) {
|
|
2469
|
+
accountChecks.push(this.httpClient.proxyGetAccount({
|
|
2470
|
+
filterType: FilterType.OidcClaims,
|
|
2471
|
+
filterValue: JSON.stringify(claim),
|
|
2472
|
+
}));
|
|
2473
|
+
}
|
|
2474
|
+
const accountResults = await Promise.all(accountChecks);
|
|
2475
|
+
for (const accountRes of accountResults) {
|
|
2476
|
+
if (!accountRes) {
|
|
2477
|
+
throw new TurnkeyError(`Account fetch failed`, TurnkeyErrorCodes.ACCOUNT_FETCH_ERROR);
|
|
2478
|
+
}
|
|
2479
|
+
if (accountRes.organizationId &&
|
|
2480
|
+
accountRes.organizationId !== session?.organizationId) {
|
|
2481
|
+
throw new TurnkeyError("Account already exists with this OIDC token or claims", TurnkeyErrorCodes.ACCOUNT_ALREADY_EXISTS);
|
|
2482
|
+
}
|
|
2381
2483
|
}
|
|
2382
2484
|
const userId = params?.userId || session?.userId;
|
|
2383
2485
|
if (!userId) {
|
|
@@ -2387,9 +2489,7 @@ class TurnkeyClient {
|
|
|
2387
2489
|
if (!organizationId) {
|
|
2388
2490
|
throw new TurnkeyError("Organization ID is required to add OAuth provider", TurnkeyErrorCodes.INVALID_REQUEST);
|
|
2389
2491
|
}
|
|
2390
|
-
|
|
2391
|
-
const { email: oidcEmail, iss } = jwtDecode(oidcToken) || {};
|
|
2392
|
-
if (iss === googleISS) {
|
|
2492
|
+
if (oidcToken && tokenIss === googleISS) {
|
|
2393
2493
|
const verifiedSuborg = await this.httpClient.proxyGetAccount({
|
|
2394
2494
|
filterType: "EMAIL",
|
|
2395
2495
|
filterValue: oidcEmail,
|
|
@@ -2422,14 +2522,13 @@ class TurnkeyClient {
|
|
|
2422
2522
|
}, stampWith);
|
|
2423
2523
|
}
|
|
2424
2524
|
}
|
|
2525
|
+
const oauthProviders = [
|
|
2526
|
+
...(oidcToken ? [{ providerName, oidcToken }] : []),
|
|
2527
|
+
...oidcClaims.map((claim) => ({ providerName, oidcClaims: claim })),
|
|
2528
|
+
];
|
|
2425
2529
|
const createProviderRes = await this.httpClient.createOauthProviders({
|
|
2426
2530
|
userId,
|
|
2427
|
-
oauthProviders
|
|
2428
|
-
{
|
|
2429
|
-
providerName,
|
|
2430
|
-
oidcToken,
|
|
2431
|
-
},
|
|
2432
|
-
],
|
|
2531
|
+
oauthProviders,
|
|
2433
2532
|
}, stampWith);
|
|
2434
2533
|
if (!createProviderRes) {
|
|
2435
2534
|
throw new TurnkeyError("Failed to create OAuth provider", TurnkeyErrorCodes.ADD_OAUTH_PROVIDER_ERROR);
|
|
@@ -2973,7 +3072,7 @@ class TurnkeyClient {
|
|
|
2973
3072
|
const session = await this.storageManager.getSession(sessionKey);
|
|
2974
3073
|
if (session) {
|
|
2975
3074
|
await Promise.all([
|
|
2976
|
-
this.
|
|
3075
|
+
this.deleteApiKeyPair({ publicKey: session.publicKey }),
|
|
2977
3076
|
this.storageManager.clearSession(sessionKey),
|
|
2978
3077
|
]);
|
|
2979
3078
|
}
|
|
@@ -3041,9 +3140,8 @@ class TurnkeyClient {
|
|
|
3041
3140
|
if (!this.httpClient) {
|
|
3042
3141
|
throw new TurnkeyError("HTTP client is not initialized. Please initialize the client before refreshing the session.", TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED);
|
|
3043
3142
|
}
|
|
3044
|
-
|
|
3143
|
+
const keyPair = publicKey ?? (await this.createApiKeyPair());
|
|
3045
3144
|
return withTurnkeyErrorHandling(async () => {
|
|
3046
|
-
keyPair = publicKey ?? (await this.apiKeyStamper?.createKeyPair());
|
|
3047
3145
|
if (!keyPair) {
|
|
3048
3146
|
throw new TurnkeyError("Failed to create new key pair.", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
3049
3147
|
}
|
|
@@ -3063,6 +3161,17 @@ class TurnkeyClient {
|
|
|
3063
3161
|
}, {
|
|
3064
3162
|
errorMessage: "Failed to refresh session",
|
|
3065
3163
|
errorCode: TurnkeyErrorCodes.REFRESH_SESSION_ERROR,
|
|
3164
|
+
catchFn: async () => {
|
|
3165
|
+
// we only delete the keypair if we generated it here
|
|
3166
|
+
if (!publicKey) {
|
|
3167
|
+
try {
|
|
3168
|
+
await this.deleteApiKeyPair({ publicKey: keyPair });
|
|
3169
|
+
}
|
|
3170
|
+
catch (cleanupError) {
|
|
3171
|
+
throw new TurnkeyError("Failed to clean up generated key pair", TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
},
|
|
3066
3175
|
});
|
|
3067
3176
|
};
|
|
3068
3177
|
/**
|
|
@@ -3183,7 +3292,7 @@ class TurnkeyClient {
|
|
|
3183
3292
|
for (const publicKey of publicKeys) {
|
|
3184
3293
|
if (!sessionTokensMap[publicKey]) {
|
|
3185
3294
|
try {
|
|
3186
|
-
await this.
|
|
3295
|
+
await this.deleteApiKeyPair({ publicKey });
|
|
3187
3296
|
}
|
|
3188
3297
|
catch (error) {
|
|
3189
3298
|
throw new TurnkeyError(`Failed to delete unused key pair ${publicKey}`, TurnkeyErrorCodes.INTERNAL_ERROR, error);
|
|
@@ -3198,34 +3307,84 @@ class TurnkeyClient {
|
|
|
3198
3307
|
/**
|
|
3199
3308
|
* Creates a new API key pair and returns the public key.
|
|
3200
3309
|
*
|
|
3201
|
-
* - This function generates a new API key pair and stores it in the underlying key store
|
|
3310
|
+
* - This function generates a new API key pair and stores it in the underlying key store.
|
|
3202
3311
|
* - If an external key pair is provided, it will use that key pair for creation instead of generating a new one.
|
|
3203
|
-
* - If `storeOverride` is set to true, the generated or provided public key will be set as the override key in the API key stamper, making it the active key for subsequent signing operations.
|
|
3204
3312
|
* - Ensures the API key stamper is initialized before proceeding.
|
|
3205
3313
|
* - Handles both native CryptoKeyPair objects and raw key material.
|
|
3206
3314
|
*
|
|
3207
3315
|
* @param params.externalKeyPair - An externally generated key pair (either a CryptoKeyPair or an object with publicKey/privateKey strings) to use instead of generating a new one.
|
|
3208
|
-
* @param params.storeOverride - If true, sets the generated or provided public key as the override key in the API key stamper (defaults to false).
|
|
3209
3316
|
* @return A promise that resolves to the public key of the created or provided API key pair as a string.
|
|
3210
3317
|
* @throws {TurnkeyError} If the API key stamper is not initialized or if there is an error during key pair creation or storage.
|
|
3211
3318
|
*/
|
|
3212
3319
|
this.createApiKeyPair = async (params) => {
|
|
3213
3320
|
return withTurnkeyErrorHandling(async () => {
|
|
3214
3321
|
const externalKeyPair = params?.externalKeyPair;
|
|
3215
|
-
const storeOverride = params?.storeOverride ?? false;
|
|
3216
3322
|
if (!this.apiKeyStamper) {
|
|
3217
3323
|
throw new TurnkeyError("API Key Stamper is not initialized.", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
3218
3324
|
}
|
|
3219
3325
|
const publicKey = await this.apiKeyStamper.createKeyPair(externalKeyPair ? externalKeyPair : undefined);
|
|
3220
|
-
if (storeOverride && publicKey) {
|
|
3221
|
-
this.apiKeyStamper.setTemporaryPublicKey(publicKey);
|
|
3222
|
-
}
|
|
3223
3326
|
return publicKey;
|
|
3224
3327
|
}, {
|
|
3225
3328
|
errorMessage: "Failed to create API key pair",
|
|
3226
3329
|
errorCode: TurnkeyErrorCodes.CREATE_API_KEY_PAIR_ERROR,
|
|
3227
3330
|
});
|
|
3228
3331
|
};
|
|
3332
|
+
/**
|
|
3333
|
+
* Deletes an API key pair from persistent storage.
|
|
3334
|
+
*
|
|
3335
|
+
* - This function removes a stored API key pair from the underlying key store (IndexedDB) based on its public key.
|
|
3336
|
+
* - Ensures that the API key stamper is initialized before proceeding with deletion.
|
|
3337
|
+
* - Intended for cleaning up key pairs that are not associated with active sessions, such as after failed authentication attempts where a temporary key pair was created but never used.
|
|
3338
|
+
* - Should NOT be used to delete key pairs that are associated with active sessions; use `clearSession` or `clearAllSessions` instead.
|
|
3339
|
+
*
|
|
3340
|
+
* @param params.publicKey - The public key of the API key pair to delete.
|
|
3341
|
+
* @returns A promise that resolves when the key pair is successfully deleted.
|
|
3342
|
+
* @throws {TurnkeyError} If the API key stamper is not initialized or if there is an error during key pair deletion.
|
|
3343
|
+
*/
|
|
3344
|
+
this.deleteApiKeyPair = async (params) => {
|
|
3345
|
+
const { publicKey } = params;
|
|
3346
|
+
return withTurnkeyErrorHandling(async () => {
|
|
3347
|
+
if (!this.apiKeyStamper) {
|
|
3348
|
+
throw new TurnkeyError("API Key Stamper is not initialized.", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
3349
|
+
}
|
|
3350
|
+
await this.apiKeyStamper.deleteKeyPair(publicKey);
|
|
3351
|
+
}, {
|
|
3352
|
+
errorMessage: "Failed to delete API key pair",
|
|
3353
|
+
errorCode: TurnkeyErrorCodes.DELETE_API_KEY_PAIR_ERROR,
|
|
3354
|
+
});
|
|
3355
|
+
};
|
|
3356
|
+
/**
|
|
3357
|
+
* Signs a message using a key pair stored in the API key stamper.
|
|
3358
|
+
*
|
|
3359
|
+
* - This function signs the provided message string with the private key identified by `publicKey`.
|
|
3360
|
+
* - The message is SHA-256 hashed internally before signing (ECDSA P-256).
|
|
3361
|
+
* - Returns a compact hex signature (r || s) suitable for use as a client signature.
|
|
3362
|
+
* - The key pair must already exist in the key store (e.g., created via `createApiKeyPair`).
|
|
3363
|
+
*
|
|
3364
|
+
* @param params.message - the message string to sign.
|
|
3365
|
+
* @param params.publicKey - the public key identifying which key pair to sign with.
|
|
3366
|
+
* @returns A promise that resolves to the compact hex signature string.
|
|
3367
|
+
* @throws {TurnkeyError} If the API key stamper is not initialized or the key pair is not found.
|
|
3368
|
+
*/
|
|
3369
|
+
this.signWithApiKey = async (params) => {
|
|
3370
|
+
const { message, publicKey } = params;
|
|
3371
|
+
let previousTemporaryKey;
|
|
3372
|
+
return withTurnkeyErrorHandling(async () => {
|
|
3373
|
+
if (!this.apiKeyStamper) {
|
|
3374
|
+
throw new TurnkeyError("API Key Stamper is not initialized.", TurnkeyErrorCodes.INTERNAL_ERROR);
|
|
3375
|
+
}
|
|
3376
|
+
previousTemporaryKey = this.apiKeyStamper.getTemporaryPublicKey();
|
|
3377
|
+
this.apiKeyStamper.setTemporaryPublicKey(publicKey);
|
|
3378
|
+
return await this.apiKeyStamper.sign(message, SignatureFormat.Raw);
|
|
3379
|
+
}, {
|
|
3380
|
+
errorMessage: "Failed to sign with API key",
|
|
3381
|
+
errorCode: TurnkeyErrorCodes.INTERNAL_ERROR,
|
|
3382
|
+
}, {
|
|
3383
|
+
finallyFn: async () => {
|
|
3384
|
+
this.apiKeyStamper?.setTemporaryPublicKey(previousTemporaryKey);
|
|
3385
|
+
},
|
|
3386
|
+
});
|
|
3387
|
+
};
|
|
3229
3388
|
/**
|
|
3230
3389
|
* Fetches the WalletKit proxy authentication configuration from the auth proxy.
|
|
3231
3390
|
*
|
|
@@ -3398,7 +3557,7 @@ class TurnkeyClient {
|
|
|
3398
3557
|
* @param params.stampWith - Optional stamper to use for polling.
|
|
3399
3558
|
*
|
|
3400
3559
|
* @returns A promise resolving to the transaction status payload if successful.
|
|
3401
|
-
* @throws {
|
|
3560
|
+
* @throws {TurnkeyError} If the transaction fails, is cancelled, or times out.
|
|
3402
3561
|
*/
|
|
3403
3562
|
async pollTransactionStatus(params) {
|
|
3404
3563
|
const { organizationId: organizationIdFromParams, stampWith = this.config.defaultStamperType, sendTransactionStatusId, pollingIntervalMs, } = params;
|
|
@@ -3417,14 +3576,13 @@ class TurnkeyClient {
|
|
|
3417
3576
|
sendTransactionStatusId,
|
|
3418
3577
|
});
|
|
3419
3578
|
const txStatus = resp?.txStatus;
|
|
3420
|
-
const txError = resp?.txError;
|
|
3421
3579
|
if (!txStatus)
|
|
3422
3580
|
return;
|
|
3423
|
-
if (
|
|
3581
|
+
if (txStatus === "FAILED" || txStatus === "CANCELLED") {
|
|
3424
3582
|
// TODO: use API enum in the future
|
|
3425
3583
|
clearInterval(ref);
|
|
3426
3584
|
clearTimeout(timeoutRef);
|
|
3427
|
-
reject(
|
|
3585
|
+
reject(new TurnkeyError(resp.error?.message || `Transaction ${resp.txStatus}`, TurnkeyErrorCodes.POLL_TRANSACTION_STATUS_ERROR, resp));
|
|
3428
3586
|
return;
|
|
3429
3587
|
}
|
|
3430
3588
|
if (txStatus === "COMPLETED" || txStatus === "INCLUDED") {
|