signer-test-sdk-core 0.0.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 (63) hide show
  1. package/README.md +81 -0
  2. package/dist/src/components/OnboardingUI/auth.d.ts +158 -0
  3. package/dist/src/components/OnboardingUI/auth.js +230 -0
  4. package/dist/src/components/OnboardingUI/auth.js.map +1 -0
  5. package/dist/src/components/OnboardingUI/index.d.ts +9 -0
  6. package/dist/src/components/OnboardingUI/index.js +9 -0
  7. package/dist/src/components/OnboardingUI/index.js.map +1 -0
  8. package/dist/src/components/OnboardingUI/types.d.ts +159 -0
  9. package/dist/src/components/OnboardingUI/types.js +5 -0
  10. package/dist/src/components/OnboardingUI/types.js.map +1 -0
  11. package/dist/src/core/AbstraxnWallet.d.ts +162 -0
  12. package/dist/src/core/AbstraxnWallet.js +593 -0
  13. package/dist/src/core/AbstraxnWallet.js.map +1 -0
  14. package/dist/src/core/AuthManager.d.ts +112 -0
  15. package/dist/src/core/AuthManager.js +685 -0
  16. package/dist/src/core/AuthManager.js.map +1 -0
  17. package/dist/src/core/Signer.d.ts +35 -0
  18. package/dist/src/core/Signer.js +156 -0
  19. package/dist/src/core/Signer.js.map +1 -0
  20. package/dist/src/index.d.ts +17 -0
  21. package/dist/src/index.js +19 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/interfaces/IAuth.d.ts +26 -0
  24. package/dist/src/interfaces/IAuth.js +2 -0
  25. package/dist/src/interfaces/IAuth.js.map +1 -0
  26. package/dist/src/interfaces/ISigner.d.ts +14 -0
  27. package/dist/src/interfaces/ISigner.js +2 -0
  28. package/dist/src/interfaces/ISigner.js.map +1 -0
  29. package/dist/src/interfaces/IStorage.d.ts +15 -0
  30. package/dist/src/interfaces/IStorage.js +2 -0
  31. package/dist/src/interfaces/IStorage.js.map +1 -0
  32. package/dist/src/interfaces/IWallet.d.ts +45 -0
  33. package/dist/src/interfaces/IWallet.js +2 -0
  34. package/dist/src/interfaces/IWallet.js.map +1 -0
  35. package/dist/src/services/ApiService.d.ts +131 -0
  36. package/dist/src/services/ApiService.js +626 -0
  37. package/dist/src/services/ApiService.js.map +1 -0
  38. package/dist/src/services/TokenService.d.ts +29 -0
  39. package/dist/src/services/TokenService.js +40 -0
  40. package/dist/src/services/TokenService.js.map +1 -0
  41. package/dist/src/services/TurnkeyService.d.ts +54 -0
  42. package/dist/src/services/TurnkeyService.js +91 -0
  43. package/dist/src/services/TurnkeyService.js.map +1 -0
  44. package/dist/src/storage/IndexedDBStorage.d.ts +39 -0
  45. package/dist/src/storage/IndexedDBStorage.js +280 -0
  46. package/dist/src/storage/IndexedDBStorage.js.map +1 -0
  47. package/dist/src/utils/constants.d.ts +52 -0
  48. package/dist/src/utils/constants.js +78 -0
  49. package/dist/src/utils/constants.js.map +1 -0
  50. package/dist/src/utils/errors.d.ts +23 -0
  51. package/dist/src/utils/errors.js +48 -0
  52. package/dist/src/utils/errors.js.map +1 -0
  53. package/dist/src/utils/helpers.d.ts +12 -0
  54. package/dist/src/utils/helpers.js +30 -0
  55. package/dist/src/utils/helpers.js.map +1 -0
  56. package/dist/src/utils/passkey.d.ts +33 -0
  57. package/dist/src/utils/passkey.js +122 -0
  58. package/dist/src/utils/passkey.js.map +1 -0
  59. package/dist/src/utils/types.d.ts +182 -0
  60. package/dist/src/utils/types.js +5 -0
  61. package/dist/src/utils/types.js.map +1 -0
  62. package/dist/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +49 -0
@@ -0,0 +1,685 @@
1
+ import { STORAGE_KEYS, TURNKEY_CONFIG, TURNKEY_ENDPOINTS } from '../utils/constants';
2
+ import { AuthenticationError } from '../utils/errors';
3
+ import { generateChallenge, createPasskeyCredential, generateAuthenticatorName, generateUserName, generateOrganizationName, } from '../utils/passkey';
4
+ import { WebauthnStamper } from '@turnkey/webauthn-stamper';
5
+ import { TurnkeyClient } from '@turnkey/http';
6
+ export class AuthManager {
7
+ constructor(apiService, turnkeyService, tokenService, storage) {
8
+ this.apiService = apiService;
9
+ this.turnkeyService = turnkeyService;
10
+ this.tokenService = tokenService;
11
+ this.storage = storage;
12
+ this.currentUser = null;
13
+ this.whoamiPromise = null;
14
+ }
15
+ /**
16
+ * Initialize OTP authentication
17
+ */
18
+ async loginWithOTP(email) {
19
+ // Clear previous IndexedDB data and reset TurnkeyService before starting new login
20
+ await this.turnkeyService.reset();
21
+ // Initialize storage if not already initialized
22
+ await this.turnkeyService.init();
23
+ // Get public key from storage (internal - users don't see this)
24
+ const publicKey = await this.turnkeyService.getPublicKey();
25
+ if (!publicKey) {
26
+ throw new AuthenticationError('Failed to get public key from storage');
27
+ }
28
+ // Initialize OTP with backend
29
+ const response = await this.apiService.initOtp(email);
30
+ return { otpId: response.otpId };
31
+ }
32
+ /**
33
+ * Verify OTP and complete authentication
34
+ */
35
+ async verifyOTP(otpId, otpCode) {
36
+ // Clear previous IndexedDB data and reset TurnkeyService before verifying OTP
37
+ await this.turnkeyService.reset();
38
+ // Ensure storage is initialized
39
+ await this.turnkeyService.init();
40
+ // Get public key from storage (internal)
41
+ const publicKey = await this.turnkeyService.getPublicKey();
42
+ if (!publicKey) {
43
+ throw new AuthenticationError('Failed to get public key from storage');
44
+ }
45
+ try {
46
+ // Verify OTP with backend
47
+ const response = await this.apiService.verifyOtp(otpId, otpCode, publicKey);
48
+ if (!response.result?.user) {
49
+ throw new AuthenticationError('Failed to get user information');
50
+ }
51
+ // Set user in memory (not storing to storage)
52
+ this.currentUser = response.result.user;
53
+ // Store user in storage
54
+ await this.storage.setItem(STORAGE_KEYS.USER, this.currentUser);
55
+ // Handle whoami call based on isPolicy
56
+ // If isPolicy is true, ONLY call whoami (do NOT call PROXY)
57
+ // If isPolicy is false, call PROXY first, then whoami
58
+ const isPolicy = Boolean(this.currentUser.isPolicy);
59
+ if (isPolicy) {
60
+ // If isPolicy is true, call whoami directly (NO PROXY call)
61
+ try {
62
+ await this.callWhoami();
63
+ }
64
+ catch (error) {
65
+ // Log error but don't fail the verification if whoami call fails
66
+ console.warn('Whoami call after OTP verification failed:', error);
67
+ }
68
+ }
69
+ else {
70
+ // If isPolicy is false, call PROXY first, then whoami
71
+ try {
72
+ await this.callProxyAfterVerification('otp');
73
+ // Call whoami after PROXY succeeds
74
+ await this.callWhoami();
75
+ }
76
+ catch (error) {
77
+ // Log error but don't fail the verification if PROXY or whoami call fails
78
+ console.warn('PROXY or whoami call after OTP verification failed:', error);
79
+ }
80
+ }
81
+ return this.currentUser;
82
+ }
83
+ catch (error) {
84
+ // If verify OTP API fails (e.g., duplicate public key error), reset IndexedDB
85
+ // This allows the user to retry with a fresh key
86
+ try {
87
+ await this.turnkeyService.reset();
88
+ }
89
+ catch (resetError) {
90
+ // Log reset error but don't fail - we still want to throw the original error
91
+ console.warn('Failed to reset IndexedDB after verify error:', resetError);
92
+ }
93
+ // Re-throw the original error
94
+ throw error;
95
+ }
96
+ }
97
+ /**
98
+ * Call PROXY endpoint after successful authentication
99
+ * This is called internally after user is authenticated
100
+ * Calls Turnkey create_policy endpoint via PROXY
101
+ * @param authMethod - 'otp' for OTP login, 'google' for Google OAuth login
102
+ */
103
+ async callProxyAfterVerification(authMethod = 'otp') {
104
+ if (!this.currentUser?.subOrganizationId) {
105
+ return;
106
+ }
107
+ // Different policies for OTP vs OAuth login
108
+ const isOauth = authMethod !== 'otp';
109
+ const policyConfig = isOauth
110
+ ? {
111
+ policyName: 'Allow user to verify OTP',
112
+ effect: 'EFFECT_ALLOW',
113
+ condition: 'activity.resource in [\'AUTH\', \'OAUTH\'] && activity.action in [\'CREATE\',\'VERIFY\']',
114
+ consensus: 'true',
115
+ notes: 'Allow user to verify OTP',
116
+ }
117
+ : {
118
+ policyName: 'Allow user to verify OTP',
119
+ effect: 'EFFECT_ALLOW',
120
+ condition: 'activity.resource in [\'AUTH\', \'OTP\'] && activity.action in [\'CREATE\',\'VERIFY\']',
121
+ consensus: 'true',
122
+ notes: 'Allow user to verify OTP',
123
+ };
124
+ // Create request body for PROXY call (matching Turnkey stampCreatePolicy format)
125
+ const body = JSON.stringify({
126
+ timestampMs: new Date().getTime().toString(),
127
+ organizationId: this.currentUser.subOrganizationId,
128
+ parameters: policyConfig,
129
+ type: 'ACTIVITY_TYPE_CREATE_POLICY_V3',
130
+ });
131
+ // Create stamp for the request
132
+ const stamp = await this.turnkeyService.createStamp(body);
133
+ // Call PROXY endpoint to proxy the request to Turnkey
134
+ const turnkeyUrl = `${TURNKEY_CONFIG.BASE_URL}${TURNKEY_ENDPOINTS.CREATE_POLICY}`;
135
+ await this.apiService.callProxy(turnkeyUrl, 'POST', body, stamp);
136
+ }
137
+ /**
138
+ * Login with Google OAuth
139
+ */
140
+ async loginWithGoogle() {
141
+ // Initialize storage
142
+ await this.turnkeyService.init();
143
+ // Get public key from storage (internal) - force refresh to ensure we get latest from IndexedDB
144
+ // This is important in case user deleted keys from IndexedDB without reloading
145
+ const publicKey = await this.turnkeyService.getPublicKey(true);
146
+ const originUrl = window.location.href;
147
+ if (!publicKey) {
148
+ throw new AuthenticationError('Failed to get public key from storage');
149
+ }
150
+ // Redirect to Google OAuth
151
+ const authUrl = this.apiService.getGoogleAuthUrl(publicKey, originUrl);
152
+ window.location.href = authUrl;
153
+ }
154
+ /**
155
+ * Login with Discord OAuth
156
+ */
157
+ async loginWithDiscord() {
158
+ await this.turnkeyService.init();
159
+ // Force refresh to ensure we get latest public key from IndexedDB
160
+ const publicKey = await this.turnkeyService.getPublicKey(true);
161
+ const originUrl = window.location.href;
162
+ if (!publicKey) {
163
+ throw new AuthenticationError('Failed to get public key from storage');
164
+ }
165
+ const authUrl = this.apiService.getDiscordAuthUrl(publicKey, originUrl);
166
+ window.location.href = authUrl;
167
+ }
168
+ /**
169
+ * Login with X (Twitter) OAuth
170
+ */
171
+ async loginWithTwitter() {
172
+ await this.turnkeyService.init();
173
+ // Force refresh to ensure we get latest public key from IndexedDB
174
+ const publicKey = await this.turnkeyService.getPublicKey(true);
175
+ const originUrl = window.location.href;
176
+ if (!publicKey) {
177
+ throw new AuthenticationError('Failed to get public key from storage');
178
+ }
179
+ const authUrl = this.apiService.getTwitterAuthUrl(publicKey, originUrl);
180
+ window.location.href = authUrl;
181
+ }
182
+ /**
183
+ * Handle Google OAuth callback
184
+ * This is called when user returns from Google OAuth
185
+ * Calls the backend API to get accessToken, refreshToken, and user data
186
+ */
187
+ async handleGoogleCallback() {
188
+ // Initialize storage WITHOUT resetting - this ensures we use the same public key
189
+ // that was used during loginWithGoogle()
190
+ // Turnkey's IndexedDbStamper will automatically reuse existing keys from IndexedDB
191
+ await this.turnkeyService.init();
192
+ const params = new URLSearchParams(window.location.search);
193
+ // Check for errors in URL
194
+ if (params.get('error')) {
195
+ throw new AuthenticationError(params.get('error') || 'Google authentication failed');
196
+ }
197
+ // Extract code and state from URL (standard OAuth flow)
198
+ const code = params.get('code');
199
+ const state = params.get('state');
200
+ // Fallback: Check if tokens are in URL params (legacy flow)
201
+ const accessToken = params.get('accessToken');
202
+ const refreshToken = params.get('refreshToken');
203
+ const userJson = params.get('user');
204
+ // If code and state are present, call the API endpoint
205
+ if (code && state) {
206
+ try {
207
+ const result = await this.apiService.handleGoogleCallback(code, state);
208
+ // Store user
209
+ this.currentUser = result.user;
210
+ await this.storage.setItem(STORAGE_KEYS.USER, result.user);
211
+ // Tokens are already stored by ApiService.handleGoogleCallback
212
+ // Now call whoami API to get user details and store the response
213
+ // whoami MUST succeed for the login to be considered successful
214
+ await this.callWhoami();
215
+ // After whoami succeeds, check if isPolicy is false and call PROXY if needed
216
+ const isPolicy = Boolean(this.currentUser.isPolicy);
217
+ if (!isPolicy) {
218
+ try {
219
+ await this.callProxyAfterVerification('google');
220
+ }
221
+ catch (proxyError) {
222
+ // Log error but don't fail the login if PROXY call fails
223
+ console.warn('PROXY call after Google login failed:', proxyError);
224
+ }
225
+ }
226
+ // Verify whoami response exists before returning user
227
+ const whoamiResponse = await this.getWhoami();
228
+ if (!whoamiResponse) {
229
+ throw new AuthenticationError('Whoami response not available after successful call');
230
+ }
231
+ return result.user;
232
+ }
233
+ catch (error) {
234
+ throw new AuthenticationError(error instanceof Error ? error.message : 'Failed to handle Google callback');
235
+ }
236
+ }
237
+ // Legacy flow: tokens in URL params
238
+ if (accessToken && refreshToken && userJson) {
239
+ // Store tokens
240
+ await this.tokenService.setTokens({ accessToken, refreshToken });
241
+ // Parse and store user
242
+ try {
243
+ const user = JSON.parse(userJson);
244
+ this.currentUser = user;
245
+ await this.storage.setItem(STORAGE_KEYS.USER, user);
246
+ // Call whoami API to get user details and store the response
247
+ // whoami MUST succeed for the login to be considered successful
248
+ await this.callWhoami();
249
+ // After whoami succeeds, check if isPolicy is false and call PROXY if needed
250
+ const isPolicy = Boolean(this.currentUser.isPolicy);
251
+ if (!isPolicy) {
252
+ try {
253
+ await this.callProxyAfterVerification('google');
254
+ }
255
+ catch (proxyError) {
256
+ // Log error but don't fail the login if PROXY call fails
257
+ console.warn('PROXY call after Google login failed (legacy flow):', proxyError);
258
+ }
259
+ }
260
+ // Verify whoami response exists before returning user
261
+ const whoamiResponse = await this.getWhoami();
262
+ if (!whoamiResponse) {
263
+ throw new AuthenticationError('Whoami response not available after successful call');
264
+ }
265
+ return user;
266
+ }
267
+ catch (error) {
268
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
269
+ throw new AuthenticationError(`Failed to parse user data: ${errorMessage}`);
270
+ }
271
+ }
272
+ // No valid authentication data found
273
+ return null;
274
+ }
275
+ /**
276
+ * Handle Discord OAuth callback
277
+ */
278
+ async handleDiscordCallback() {
279
+ await this.turnkeyService.init();
280
+ const params = new URLSearchParams(window.location.search);
281
+ if (params.get('error')) {
282
+ throw new AuthenticationError(params.get('error') || 'Discord authentication failed');
283
+ }
284
+ const code = params.get('code');
285
+ const state = params.get('state');
286
+ const accessToken = params.get('accessToken');
287
+ const refreshToken = params.get('refreshToken');
288
+ const userJson = params.get('user');
289
+ if (code && state) {
290
+ try {
291
+ const result = await this.apiService.handleDiscordCallback(code, state);
292
+ this.currentUser = result.user;
293
+ await this.storage.setItem(STORAGE_KEYS.USER, result.user);
294
+ const isPolicy = Boolean(this.currentUser.isPolicy);
295
+ if (!isPolicy) {
296
+ try {
297
+ await this.callProxyAfterVerification('discord');
298
+ }
299
+ catch (proxyError) {
300
+ console.warn('PROXY call after Discord login failed:', proxyError);
301
+ }
302
+ }
303
+ else {
304
+ try {
305
+ await this.callWhoami();
306
+ }
307
+ catch (whoamiError) {
308
+ console.warn('Whoami call after Discord login failed:', whoamiError);
309
+ }
310
+ }
311
+ const whoamiResponse = await this.getWhoami();
312
+ if (!whoamiResponse) {
313
+ throw new AuthenticationError('Whoami response not available after successful call');
314
+ }
315
+ return result.user;
316
+ }
317
+ catch (error) {
318
+ throw new AuthenticationError(error instanceof Error ? error.message : 'Failed to handle Discord callback');
319
+ }
320
+ }
321
+ if (accessToken && refreshToken && userJson) {
322
+ await this.tokenService.setTokens({ accessToken, refreshToken });
323
+ try {
324
+ const user = JSON.parse(userJson);
325
+ this.currentUser = user;
326
+ await this.storage.setItem(STORAGE_KEYS.USER, user);
327
+ await this.callWhoami();
328
+ const whoamiResponse = await this.getWhoami();
329
+ if (!whoamiResponse) {
330
+ throw new AuthenticationError('Whoami response not available after successful call');
331
+ }
332
+ return user;
333
+ }
334
+ catch (error) {
335
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
336
+ throw new AuthenticationError(`Failed to parse user data: ${errorMessage}`);
337
+ }
338
+ }
339
+ return null;
340
+ }
341
+ /**
342
+ * Handle X (Twitter) OAuth callback
343
+ */
344
+ async handleTwitterCallback() {
345
+ await this.turnkeyService.init();
346
+ const params = new URLSearchParams(window.location.search);
347
+ if (params.get('error')) {
348
+ throw new AuthenticationError(params.get('error') || 'X (Twitter) authentication failed');
349
+ }
350
+ const code = params.get('code');
351
+ const state = params.get('state');
352
+ const accessToken = params.get('accessToken');
353
+ const refreshToken = params.get('refreshToken');
354
+ const userJson = params.get('user');
355
+ if (code && state) {
356
+ try {
357
+ const result = await this.apiService.handleTwitterCallback(code, state);
358
+ this.currentUser = result.user;
359
+ await this.storage.setItem(STORAGE_KEYS.USER, result.user);
360
+ const isPolicy = Boolean(this.currentUser.isPolicy);
361
+ if (!isPolicy) {
362
+ try {
363
+ await this.callProxyAfterVerification('twitter');
364
+ }
365
+ catch (proxyError) {
366
+ console.warn('PROXY call after Twitter login failed:', proxyError);
367
+ }
368
+ }
369
+ else {
370
+ try {
371
+ await this.callWhoami();
372
+ }
373
+ catch (whoamiError) {
374
+ console.warn('Whoami call after Twitter login failed:', whoamiError);
375
+ }
376
+ }
377
+ const whoamiResponse = await this.getWhoami();
378
+ if (!whoamiResponse) {
379
+ throw new AuthenticationError('Whoami response not available after successful call');
380
+ }
381
+ return result.user;
382
+ }
383
+ catch (error) {
384
+ throw new AuthenticationError(error instanceof Error ? error.message : 'Failed to handle X (Twitter) callback');
385
+ }
386
+ }
387
+ if (accessToken && refreshToken && userJson) {
388
+ await this.tokenService.setTokens({ accessToken, refreshToken });
389
+ try {
390
+ const user = JSON.parse(userJson);
391
+ this.currentUser = user;
392
+ await this.storage.setItem(STORAGE_KEYS.USER, user);
393
+ await this.callWhoami();
394
+ const whoamiResponse = await this.getWhoami();
395
+ if (!whoamiResponse) {
396
+ throw new AuthenticationError('Whoami response not available after successful call');
397
+ }
398
+ return user;
399
+ }
400
+ catch (error) {
401
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
402
+ throw new AuthenticationError(`Failed to parse user data: ${errorMessage}`);
403
+ }
404
+ }
405
+ return null;
406
+ }
407
+ /**
408
+ * Logout
409
+ */
410
+ async logout() {
411
+ this.currentUser = null;
412
+ await this.tokenService.clearTokens();
413
+ // Clear all storage items
414
+ await this.storage.removeItem(STORAGE_KEYS.USER);
415
+ await this.storage.removeItem(STORAGE_KEYS.WHOAMI);
416
+ await this.storage.removeItem(STORAGE_KEYS.VERIFY_RESPONSE);
417
+ await this.storage.removeItem(STORAGE_KEYS.WALLET_CONFIG);
418
+ // Clear all localStorage
419
+ await this.storage.clear();
420
+ }
421
+ /**
422
+ * Refresh access token
423
+ */
424
+ async refreshToken() {
425
+ return await this.apiService.refreshToken();
426
+ }
427
+ /**
428
+ * Get current user
429
+ */
430
+ getCurrentUser() {
431
+ return this.currentUser;
432
+ }
433
+ /**
434
+ * Check if authenticated
435
+ */
436
+ async isAuthenticated() {
437
+ if (this.currentUser) {
438
+ return true;
439
+ }
440
+ // Try to load user from storage
441
+ const user = await this.storage.getItem(STORAGE_KEYS.USER);
442
+ if (user) {
443
+ this.currentUser = user;
444
+ return true;
445
+ }
446
+ return false;
447
+ }
448
+ /**
449
+ * Load user from storage
450
+ */
451
+ async loadUserFromStorage() {
452
+ const user = await this.storage.getItem(STORAGE_KEYS.USER);
453
+ if (user) {
454
+ this.currentUser = user;
455
+ }
456
+ }
457
+ /**
458
+ * Get stored verification response
459
+ * Returns the full OTP verification response that was saved
460
+ */
461
+ async getVerifyResponse() {
462
+ return await this.storage.getItem(STORAGE_KEYS.VERIFY_RESPONSE);
463
+ }
464
+ /**
465
+ * Call whoami API and store the response
466
+ * This is called after successful OTP verification
467
+ * Always calls backend endpoint with access token (backend handles Turnkey call)
468
+ */
469
+ async callWhoami() {
470
+ if (!this.currentUser?.subOrganizationId) {
471
+ return;
472
+ }
473
+ // If whoami is already being called, wait for the existing call to complete
474
+ // This prevents duplicate API calls
475
+ if (this.whoamiPromise) {
476
+ try {
477
+ await this.whoamiPromise;
478
+ // If the existing call succeeded, we're done
479
+ return;
480
+ }
481
+ catch (error) {
482
+ // If the existing call failed, clear the promise and continue to retry
483
+ this.whoamiPromise = null;
484
+ }
485
+ }
486
+ // Check if we already have a cached response (avoid unnecessary API call)
487
+ const cachedWhoami = await this.getWhoami();
488
+ if (cachedWhoami) {
489
+ return;
490
+ }
491
+ // Create and store the promise for this call
492
+ this.whoamiPromise = (async () => {
493
+ try {
494
+ // Ensure storage is initialized before calling whoami
495
+ // This is critical for createStamp to work in getWhoami
496
+ await this.turnkeyService.init();
497
+ // Call whoami via backend endpoint with access token
498
+ // Backend will handle the Turnkey call internally
499
+ const whoamiResponse = await this.apiService.getWhoami(this.currentUser.subOrganizationId);
500
+ // Store whoami response in localStorage
501
+ await this.storage.setItem(STORAGE_KEYS.WHOAMI, whoamiResponse);
502
+ }
503
+ catch (error) {
504
+ console.error('Failed to get whoami information:', error);
505
+ // Clear promise on error so it can be retried
506
+ this.whoamiPromise = null;
507
+ throw error;
508
+ }
509
+ finally {
510
+ // Only clear if it's still the same promise (in case of concurrent calls)
511
+ if (this.whoamiPromise) {
512
+ this.whoamiPromise = null;
513
+ }
514
+ }
515
+ })();
516
+ return await this.whoamiPromise;
517
+ }
518
+ /**
519
+ * Get stored whoami response
520
+ */
521
+ async getWhoami() {
522
+ return await this.storage.getItem(STORAGE_KEYS.WHOAMI);
523
+ }
524
+ /**
525
+ * Refresh whoami by making a fresh API call
526
+ * This bypasses the cache and always calls the API
527
+ */
528
+ async refreshWhoami() {
529
+ if (!this.currentUser?.subOrganizationId) {
530
+ throw new AuthenticationError('User not authenticated');
531
+ }
532
+ // Clear any existing promise to force a fresh call
533
+ this.whoamiPromise = null;
534
+ try {
535
+ // Ensure storage is initialized before calling whoami
536
+ await this.turnkeyService.init();
537
+ // Call whoami via backend endpoint with access token
538
+ // Backend will handle the Turnkey call internally
539
+ const whoamiResponse = await this.apiService.getWhoami(this.currentUser.subOrganizationId);
540
+ // Store whoami response in localStorage
541
+ await this.storage.setItem(STORAGE_KEYS.WHOAMI, whoamiResponse);
542
+ return whoamiResponse;
543
+ }
544
+ catch (error) {
545
+ console.error('Failed to refresh whoami information:', error);
546
+ throw error;
547
+ }
548
+ }
549
+ /**
550
+ * Signup with passkey
551
+ * Creates a WebAuthn credential and registers the user
552
+ */
553
+ async signupWithPasskey() {
554
+ try {
555
+ // Clear previous IndexedDB data and reset TurnkeyService before starting new signup
556
+ await this.turnkeyService.reset();
557
+ // Initialize storage if not already initialized
558
+ await this.turnkeyService.init();
559
+ // Get public key from storage (internal - users don't see this)
560
+ const targetPublicKey = await this.turnkeyService.getPublicKey();
561
+ if (!targetPublicKey) {
562
+ throw new AuthenticationError('Failed to get public key from storage');
563
+ }
564
+ // Get rpId from current hostname
565
+ const rpId = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
566
+ // Generate challenge
567
+ const challengeBase64 = generateChallenge();
568
+ // Create passkey credential
569
+ const passkeyData = await createPasskeyCredential(rpId, challengeBase64);
570
+ // Generate authenticator name
571
+ const authenticatorName = generateAuthenticatorName(rpId);
572
+ // Generate user and organization names
573
+ const userName = generateUserName();
574
+ const organizationName = generateOrganizationName();
575
+ // Format the attestation data
576
+ const authenticator = {
577
+ authenticatorName,
578
+ challenge: passkeyData.challenge,
579
+ attestation: {
580
+ credentialId: passkeyData.credentialId,
581
+ attestationObject: passkeyData.attestationObject,
582
+ clientDataJson: passkeyData.clientDataJson,
583
+ transports: passkeyData.transports,
584
+ },
585
+ };
586
+ // Call passkey signup API
587
+ const response = await this.apiService.passkeySignup({
588
+ userName,
589
+ authenticators: [authenticator],
590
+ organizationName,
591
+ targetPublicKey,
592
+ });
593
+ if (!response.result?.user) {
594
+ throw new AuthenticationError('Failed to get user information from passkey signup');
595
+ }
596
+ // Set user in memory
597
+ this.currentUser = response.result.user;
598
+ // Store user in storage
599
+ await this.storage.setItem(STORAGE_KEYS.USER, this.currentUser);
600
+ // For passkey signup, skip proxy and call whoami directly
601
+ try {
602
+ await this.callWhoami();
603
+ }
604
+ catch (error) {
605
+ console.warn('Whoami call after passkey signup failed:', error);
606
+ }
607
+ return this.currentUser;
608
+ }
609
+ catch (error) {
610
+ // If passkey signup API fails, reset IndexedDB
611
+ // This allows the user to retry with a fresh key
612
+ try {
613
+ await this.turnkeyService.reset();
614
+ }
615
+ catch (resetError) {
616
+ // Log reset error but don't fail - we still want to throw the original error
617
+ console.warn('Failed to reset IndexedDB after passkey signup error:', resetError);
618
+ }
619
+ // Re-throw the original error
620
+ throw error;
621
+ }
622
+ }
623
+ /**
624
+ * Login with passkey
625
+ * Uses WebauthnStamper to authenticate with existing passkey
626
+ */
627
+ async loginWithPasskey() {
628
+ try {
629
+ const rpId = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
630
+ // Create WebauthnStamper
631
+ const stamper = new WebauthnStamper({
632
+ rpId: rpId,
633
+ userVerification: 'required',
634
+ });
635
+ // Create Turnkey client with WebauthnStamper
636
+ const httpClient = new TurnkeyClient({ baseUrl: TURNKEY_CONFIG.BASE_URL }, stamper);
637
+ // Root organization ID for stamping (provided)
638
+ const rootOrganizationId = '0ba77025-ceba-4093-b5b3-a22c838d2e28';
639
+ // Initialize storage and try to get public key from Turnkey IndexedDB stamper
640
+ await this.turnkeyService.init();
641
+ const storagePublicKey = await this.turnkeyService.getPublicKey();
642
+ const storedUser = await this.storage.getItem(STORAGE_KEYS.USER);
643
+ const publicKey = storagePublicKey || storedUser?.publicKey || this.currentUser?.publicKey;
644
+ if (!publicKey) {
645
+ throw new AuthenticationError('Failed to get public key for passkey login. Please complete passkey signup first.');
646
+ }
647
+ // Create stamp + body using Turnkey stampStampLogin
648
+ const { body, stamp } = await httpClient.stampStampLogin({
649
+ type: 'ACTIVITY_TYPE_STAMP_LOGIN',
650
+ timestampMs: new Date().getTime().toString(),
651
+ organizationId: rootOrganizationId,
652
+ parameters: {
653
+ publicKey,
654
+ expirationSeconds: '86400',
655
+ invalidateExisting: true,
656
+ },
657
+ });
658
+ // Call backend PASSKEY_LOGIN endpoint with stamp + body
659
+ const parsedBody = typeof body === 'string' ? JSON.parse(body) : body;
660
+ const stampValue = stamp?.stampHeaderValue ?? stamp;
661
+ const response = await this.apiService.passkeyLogin({ body: parsedBody, stamp: stampValue });
662
+ if (!response.result?.user) {
663
+ throw new AuthenticationError('Failed to get user information from passkey login');
664
+ }
665
+ // Set user and store
666
+ this.currentUser = response.result.user;
667
+ await this.storage.setItem(STORAGE_KEYS.USER, this.currentUser);
668
+ // Fetch whoami directly (no proxy for passkey)
669
+ try {
670
+ await this.callWhoami();
671
+ }
672
+ catch (error) {
673
+ console.warn('Whoami call after passkey login failed:', error);
674
+ }
675
+ return this.currentUser;
676
+ }
677
+ catch (error) {
678
+ if (error instanceof AuthenticationError) {
679
+ throw error;
680
+ }
681
+ throw new AuthenticationError(error instanceof Error ? error.message : 'Failed to login with passkey');
682
+ }
683
+ }
684
+ }
685
+ //# sourceMappingURL=AuthManager.js.map