@xbg.solutions/bpsk-utils-firebase-auth 1.2.3

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 (61) hide show
  1. package/lib/index.d.ts +13 -0
  2. package/lib/index.d.ts.map +1 -0
  3. package/lib/index.js +20 -0
  4. package/lib/index.js.map +1 -0
  5. package/lib/services/auth/auth.service.d.ts +89 -0
  6. package/lib/services/auth/auth.service.d.ts.map +1 -0
  7. package/lib/services/auth/auth.service.js +615 -0
  8. package/lib/services/auth/auth.service.js.map +1 -0
  9. package/lib/services/auth/email-link.d.ts +99 -0
  10. package/lib/services/auth/email-link.d.ts.map +1 -0
  11. package/lib/services/auth/email-link.js +715 -0
  12. package/lib/services/auth/email-link.js.map +1 -0
  13. package/lib/services/auth/index.d.ts +15 -0
  14. package/lib/services/auth/index.d.ts.map +1 -0
  15. package/lib/services/auth/index.js +18 -0
  16. package/lib/services/auth/index.js.map +1 -0
  17. package/lib/services/auth/phone-auth.d.ts +65 -0
  18. package/lib/services/auth/phone-auth.d.ts.map +1 -0
  19. package/lib/services/auth/phone-auth.js +150 -0
  20. package/lib/services/auth/phone-auth.js.map +1 -0
  21. package/lib/services/auth/user-creation.d.ts +17 -0
  22. package/lib/services/auth/user-creation.d.ts.map +1 -0
  23. package/lib/services/auth/user-creation.js +39 -0
  24. package/lib/services/auth/user-creation.js.map +1 -0
  25. package/lib/services/token/index.d.ts +29 -0
  26. package/lib/services/token/index.d.ts.map +1 -0
  27. package/lib/services/token/index.js +20 -0
  28. package/lib/services/token/index.js.map +1 -0
  29. package/lib/services/token/token.service.d.ts +57 -0
  30. package/lib/services/token/token.service.d.ts.map +1 -0
  31. package/lib/services/token/token.service.js +554 -0
  32. package/lib/services/token/token.service.js.map +1 -0
  33. package/lib/stores/auth.service.d.ts +6 -0
  34. package/lib/stores/auth.service.d.ts.map +1 -0
  35. package/lib/stores/auth.service.js +6 -0
  36. package/lib/stores/auth.service.js.map +1 -0
  37. package/lib/stores/auth.store.d.ts +56 -0
  38. package/lib/stores/auth.store.d.ts.map +1 -0
  39. package/lib/stores/auth.store.js +64 -0
  40. package/lib/stores/auth.store.js.map +1 -0
  41. package/lib/stores/token.store.d.ts +41 -0
  42. package/lib/stores/token.store.d.ts.map +1 -0
  43. package/lib/stores/token.store.js +36 -0
  44. package/lib/stores/token.store.js.map +1 -0
  45. package/lib/stores/user-creation.d.ts +8 -0
  46. package/lib/stores/user-creation.d.ts.map +1 -0
  47. package/lib/stores/user-creation.js +11 -0
  48. package/lib/stores/user-creation.js.map +1 -0
  49. package/lib/utils/auth-guard.d.ts +58 -0
  50. package/lib/utils/auth-guard.d.ts.map +1 -0
  51. package/lib/utils/auth-guard.js +109 -0
  52. package/lib/utils/auth-guard.js.map +1 -0
  53. package/lib/utils/signout.d.ts +82 -0
  54. package/lib/utils/signout.d.ts.map +1 -0
  55. package/lib/utils/signout.js +168 -0
  56. package/lib/utils/signout.js.map +1 -0
  57. package/lib/utils/tokens.d.ts +136 -0
  58. package/lib/utils/tokens.d.ts.map +1 -0
  59. package/lib/utils/tokens.js +479 -0
  60. package/lib/utils/tokens.js.map +1 -0
  61. package/package.json +31 -0
@@ -0,0 +1,715 @@
1
+ /**
2
+ * src/lib/services/auth/email-link.ts
3
+ * Email Link Authentication service
4
+ *
5
+ * Provides functionality for Firebase Email Link authentication:
6
+ * - Sending authentication links
7
+ * - Verifying links
8
+ * - Managing email persistence
9
+ * - Error handling specific to email link flow
10
+ */
11
+ import { sendSignInLinkToEmail, isSignInWithEmailLink, signInWithEmailLink, getAuth } from 'firebase/auth';
12
+ import { getFirebaseAuth, getFirebaseState, processFirebaseError } from '@xbg.solutions/bpsk-core';
13
+ import { secureStorage } from '@xbg.solutions/bpsk-utils-secure-storage';
14
+ import { loggerService } from '@xbg.solutions/bpsk-core';
15
+ import { publish } from '@xbg.solutions/bpsk-core';
16
+ import { AppError, withErrorHandling } from '@xbg.solutions/bpsk-core';
17
+ import { AUTH_EVENTS, AUTH_CONFIG } from '@xbg.solutions/bpsk-core';
18
+ import { AUTH_NAMESPACE, EMAIL_FOR_SIGN_IN_KEY } from '@xbg.solutions/bpsk-utils-secure-storage';
19
+ // Create a context-aware logger
20
+ const emailLinkLogger = loggerService.withContext('EmailLinkService');
21
+ // Constants for verification tracking
22
+ const VERIFICATION_ATTEMPT_KEY = 'email_verification_attempt';
23
+ /**
24
+ * Tracks a verification attempt to prevent loops and handle reloads properly
25
+ */
26
+ function trackVerificationAttempt(oobCode) {
27
+ // Get existing attempt data if any
28
+ const existing = secureStorage.getItem(VERIFICATION_ATTEMPT_KEY, {
29
+ namespace: AUTH_NAMESPACE,
30
+ mechanism: 'localStorage'
31
+ });
32
+ if (existing && existing.oobCode === oobCode) {
33
+ // Update attempts count for existing verification
34
+ secureStorage.setItem(VERIFICATION_ATTEMPT_KEY, {
35
+ timestamp: Date.now(),
36
+ oobCode,
37
+ attempts: existing.attempts + 1
38
+ }, {
39
+ namespace: AUTH_NAMESPACE,
40
+ mechanism: 'localStorage',
41
+ ttl: 300 // 5 minutes
42
+ });
43
+ }
44
+ else {
45
+ // First attempt for this verification code
46
+ secureStorage.setItem(VERIFICATION_ATTEMPT_KEY, {
47
+ timestamp: Date.now(),
48
+ oobCode,
49
+ attempts: 1
50
+ }, {
51
+ namespace: AUTH_NAMESPACE,
52
+ mechanism: 'localStorage',
53
+ ttl: 300 // 5 minutes
54
+ });
55
+ }
56
+ }
57
+ /**
58
+ * Gets information about the current verification attempt
59
+ */
60
+ function getVerificationAttempt(oobCode) {
61
+ if (!oobCode) {
62
+ return { isRepeat: false, attempts: 0 };
63
+ }
64
+ const stored = secureStorage.getItem(VERIFICATION_ATTEMPT_KEY, {
65
+ namespace: AUTH_NAMESPACE,
66
+ mechanism: 'localStorage',
67
+ fallbackMechanisms: ['sessionStorage'] // Added fallback for backward compatibility
68
+ });
69
+ if (stored && stored.oobCode === oobCode) {
70
+ return {
71
+ isRepeat: true,
72
+ attempts: stored.attempts,
73
+ oobCode: stored.oobCode
74
+ };
75
+ }
76
+ return { isRepeat: false, attempts: 0 };
77
+ }
78
+ /**
79
+ * Sends an email authentication link
80
+ *
81
+ * @param options Email link configuration options
82
+ * @returns Promise resolving to success status
83
+ */
84
+ // Helper function that will be reimplemented in the future
85
+ export async function checkUserExistsAndCreate(email) {
86
+ // Feature currently disabled, always return true to continue normal flow
87
+ emailLinkLogger.info('User creation on first sign-in is disabled');
88
+ return true;
89
+ }
90
+ export async function sendEmailLink(options) {
91
+ const timerId = emailLinkLogger.startTimer('sendEmailLink');
92
+ try {
93
+ emailLinkLogger.info('Sending email authentication link', { email: options.email });
94
+ // Auto-creation is disabled, but we'll keep the infrastructure for future reimplementation
95
+ // This section will be reimplemented differently in the future
96
+ const auth = await getFirebaseAuth();
97
+ // Prepare action code settings
98
+ const actionCodeSettings = options.actionCodeSettings || {
99
+ url: AUTH_CONFIG.DEFAULT_EMAIL_LINK_REDIRECT,
100
+ handleCodeInApp: true
101
+ };
102
+ // Send the email link
103
+ await sendSignInLinkToEmail(auth, options.email, actionCodeSettings);
104
+ // Store the email if requested
105
+ if (options.rememberEmail !== false) {
106
+ try {
107
+ // Use our standardized storeEmail function to ensure consistency
108
+ const success = storeEmail(options.email);
109
+ if (success) {
110
+ emailLinkLogger.info('Successfully stored email for authentication');
111
+ }
112
+ else {
113
+ emailLinkLogger.warn('Failed to store email using storeEmail function');
114
+ }
115
+ }
116
+ catch (err) {
117
+ emailLinkLogger.warn('Error storing email', err instanceof Error ? err : new Error(String(err)));
118
+ }
119
+ }
120
+ // Publish event
121
+ publish(AUTH_EVENTS.EMAIL_LINK_SENT, {
122
+ email: options.email,
123
+ context: { timestamp: Date.now() }
124
+ }, 'EmailLinkService');
125
+ emailLinkLogger.endTimer(timerId, { success: true });
126
+ return {
127
+ success: true,
128
+ method: 'emailLink'
129
+ };
130
+ }
131
+ catch (error) {
132
+ emailLinkLogger.endTimer(timerId, { success: false });
133
+ const processedError = processFirebaseError(error, 'Failed to send email authentication link', {
134
+ action: 'auth/email-link-send',
135
+ userMessage: 'We couldn\'t send the authentication link. Please check your email address and try again.'
136
+ });
137
+ emailLinkLogger.error('Email link sending failed', processedError, {
138
+ email: options.email
139
+ });
140
+ // Publish failure event
141
+ publish(AUTH_EVENTS.LOGIN_FAILURE, {
142
+ error: {
143
+ message: processedError.message,
144
+ code: processedError.firebaseCode
145
+ },
146
+ method: 'emailLink',
147
+ context: { email: options.email }
148
+ }, 'EmailLinkService');
149
+ return {
150
+ success: false,
151
+ error: processedError,
152
+ method: 'emailLink'
153
+ };
154
+ }
155
+ }
156
+ /**
157
+ * Verifies an email authentication link and signs in the user
158
+ *
159
+ * @param options Email link verification options
160
+ * @returns Promise resolving to authentication result
161
+ */
162
+ export async function verifyEmailLink(options = {}) {
163
+ const timerId = emailLinkLogger.startTimer('verifyEmailLink');
164
+ try {
165
+ // Publish verification start event
166
+ publish(AUTH_EVENTS.EMAIL_LINK_VERIFICATION_START, {
167
+ context: { timestamp: Date.now() }
168
+ }, 'EmailLinkService');
169
+ const auth = await getFirebaseAuth();
170
+ // Get the email link
171
+ const link = options.link || window.location.href;
172
+ // Extract oobCode from link for verification tracking
173
+ let oobCode = null;
174
+ try {
175
+ const url = new URL(link);
176
+ oobCode = url.searchParams.get('oobCode');
177
+ if (oobCode) {
178
+ // Track this verification attempt
179
+ trackVerificationAttempt(oobCode);
180
+ // Check for potential reload loops
181
+ const verificationAttempt = getVerificationAttempt(oobCode);
182
+ if (verificationAttempt.attempts > 5) {
183
+ emailLinkLogger.warn('Excessive verification attempts detected', {
184
+ attempts: verificationAttempt.attempts,
185
+ oobCode
186
+ });
187
+ // Don't throw an error here, continue with verification
188
+ // but log the warning
189
+ }
190
+ }
191
+ }
192
+ catch (e) {
193
+ // If URL parsing fails, just continue without tracking
194
+ emailLinkLogger.warn('Failed to extract oobCode for tracking', {
195
+ error: e instanceof Error ? e.message : String(e)
196
+ });
197
+ }
198
+ // Check if the link is valid
199
+ if (!isSignInWithEmailLink(auth, link)) {
200
+ const error = new AppError('Invalid email link', {
201
+ category: 'auth',
202
+ userMessage: 'The authentication link is invalid. Please request a new link.',
203
+ context: { link }
204
+ });
205
+ emailLinkLogger.error('Email link verification failed', error);
206
+ // Publish failure event
207
+ publish(AUTH_EVENTS.EMAIL_LINK_VERIFICATION_FAILURE, {
208
+ error: {
209
+ message: error.message,
210
+ code: undefined
211
+ },
212
+ method: 'emailLink'
213
+ }, 'EmailLinkService');
214
+ return {
215
+ success: false,
216
+ error,
217
+ method: 'emailLink'
218
+ };
219
+ }
220
+ // Get the email address
221
+ let email = options.email;
222
+ if (!email) {
223
+ // Try to get email from secure storage - first from localStorage, then from sessionStorage
224
+ const storedEmail = secureStorage.getItem(EMAIL_FOR_SIGN_IN_KEY, {
225
+ namespace: AUTH_NAMESPACE,
226
+ mechanism: 'localStorage',
227
+ fallbackMechanisms: ['sessionStorage'] // Added fallback for backward compatibility
228
+ });
229
+ // IMPORTANT: We don't accept email from URL for security reasons
230
+ // Check for stored email
231
+ if (!storedEmail) {
232
+ const error = new AppError('Email not found', {
233
+ category: 'auth',
234
+ userMessage: 'Please provide the email address you used to request the link.',
235
+ context: { link }
236
+ });
237
+ emailLinkLogger.error('Email link verification failed', error);
238
+ // Publish failure event
239
+ publish(AUTH_EVENTS.EMAIL_LINK_VERIFICATION_FAILURE, {
240
+ error: {
241
+ message: error.message,
242
+ code: undefined
243
+ },
244
+ method: 'emailLink'
245
+ }, 'EmailLinkService');
246
+ return {
247
+ success: false,
248
+ error,
249
+ method: 'emailLink'
250
+ };
251
+ }
252
+ email = storedEmail;
253
+ }
254
+ emailLinkLogger.info('Verifying email link', {
255
+ email,
256
+ link,
257
+ isValid: isSignInWithEmailLink(auth, link)
258
+ });
259
+ // Sign in with the email link
260
+ console.log('Attempting to sign in with:', { email, link });
261
+ const credential = await signInWithEmailLink(auth, email, link);
262
+ const user = credential.user;
263
+ // Check verification attempt count before cleaning up email
264
+ const verificationAttempt = getVerificationAttempt(oobCode ?? undefined);
265
+ // IMPORTANT: Keep email until token verification is complete
266
+ // Don't remove the email immediately to avoid issues with session establishment
267
+ // We'll use a short TTL as a fallback in case the token event never triggers
268
+ // Keep the email temporarily to ensure token verification completes
269
+ // NOTE: The Firebase token will be the source of truth for authentication,
270
+ // not the email in storage. The token persists via Firebase's own mechanisms.
271
+ try {
272
+ // Use our standard storage mechanism for consistency
273
+ // This is just for temporary storage until the token is established
274
+ window.localStorage.setItem('emailForSignIn', email);
275
+ // Also store in memory as a backup
276
+ secureStorage.setItem(EMAIL_FOR_SIGN_IN_KEY, email, {
277
+ namespace: AUTH_NAMESPACE,
278
+ mechanism: 'memory',
279
+ ttl: 300 // 5 minutes as fallback if token event doesn't trigger
280
+ });
281
+ emailLinkLogger.info('Keeping email in storage until token is established', {
282
+ email,
283
+ attempts: verificationAttempt.attempts
284
+ });
285
+ }
286
+ catch (err) {
287
+ emailLinkLogger.warn('Error keeping email in storage during verification', err instanceof Error ? err : new Error(String(err)));
288
+ // Non-fatal error, continue with verification
289
+ }
290
+ // DETERMINISTIC CLEANUP WITH MULTIPLE FALLBACKS
291
+ // Approach 1: Immediate cleanup attempt as soon as we have a successful login
292
+ // This is the primary deterministic cleanup
293
+ try {
294
+ // If we've already successfully signed in, we can clear immediately
295
+ const auth = getAuth();
296
+ if (auth && auth.currentUser) {
297
+ emailLinkLogger.info('User already authenticated, clearing stored email immediately');
298
+ clearStoredEmail();
299
+ }
300
+ }
301
+ catch (e) {
302
+ emailLinkLogger.warn('Error during immediate cleanup check', e instanceof Error ? e : new Error(String(e)));
303
+ }
304
+ // Approach 2: Delayed cleanup after token processing is complete
305
+ // This is for cases where auth state takes a moment to propagate
306
+ setTimeout(() => {
307
+ try {
308
+ const auth = getAuth();
309
+ if (auth && auth.currentUser) {
310
+ emailLinkLogger.info('User authenticated after delay, clearing stored email');
311
+ clearStoredEmail();
312
+ }
313
+ else {
314
+ emailLinkLogger.info('No authenticated user found after delay, email will be cleared by TTL');
315
+ }
316
+ }
317
+ catch (err) {
318
+ emailLinkLogger.warn('Error checking auth state after verification delay', err instanceof Error ? err : new Error(String(err)));
319
+ }
320
+ }, 2000); // Wait 2 seconds to ensure token processing is complete
321
+ // Approach 3: Subscribe to auth state changes
322
+ // This catches auth state changes that might happen after our timeouts
323
+ try {
324
+ const unsubscribe = getAuth().onAuthStateChanged((user) => {
325
+ if (user) {
326
+ emailLinkLogger.info('Auth state changed to authenticated, clearing stored email');
327
+ clearStoredEmail();
328
+ // Cleanup subscription after first auth state change
329
+ unsubscribe();
330
+ }
331
+ });
332
+ // Ensure the subscription is cleaned up after a reasonable time
333
+ setTimeout(() => {
334
+ try {
335
+ unsubscribe();
336
+ emailLinkLogger.info('Cleaned up auth state subscription');
337
+ }
338
+ catch (e) {
339
+ // Ignore any errors in unsubscribe
340
+ }
341
+ }, 5000); // 5 seconds should be more than enough time for auth to complete
342
+ }
343
+ catch (e) {
344
+ emailLinkLogger.warn('Could not set up auth state change listener', e instanceof Error ? e : new Error(String(e)));
345
+ }
346
+ // Approach 4: TTL-based cleanup
347
+ // The memory storage already has a TTL of 300 seconds (5 minutes)
348
+ // For localStorage, we'll add a timestamp that our getStoredEmail function can check
349
+ try {
350
+ // Add expiration timestamp to localStorage
351
+ const expiresAt = Date.now() + (300 * 1000); // 5 minutes
352
+ window.localStorage.setItem('emailForSignIn_expiresAt', expiresAt.toString());
353
+ emailLinkLogger.info('Added expiration timestamp to email storage as final fallback');
354
+ }
355
+ catch (e) {
356
+ // Not critical, the other cleanup methods should work
357
+ }
358
+ // Publish success event
359
+ publish(AUTH_EVENTS.EMAIL_LINK_VERIFICATION_SUCCESS, {
360
+ user,
361
+ method: 'emailLink',
362
+ context: { email }
363
+ }, 'EmailLinkService');
364
+ emailLinkLogger.endTimer(timerId, { success: true });
365
+ return {
366
+ success: true,
367
+ user,
368
+ credential,
369
+ method: 'emailLink'
370
+ };
371
+ }
372
+ catch (error) {
373
+ emailLinkLogger.endTimer(timerId, { success: false });
374
+ const processedError = processFirebaseError(error, 'Failed to verify email link', {
375
+ action: 'auth/email-link-verify',
376
+ userMessage: 'We couldn\'t verify the authentication link. It may have expired or already been used.'
377
+ });
378
+ emailLinkLogger.error('Email link verification failed', processedError);
379
+ // Publish failure event
380
+ publish(AUTH_EVENTS.EMAIL_LINK_VERIFICATION_FAILURE, {
381
+ error: {
382
+ message: processedError.message,
383
+ code: processedError.firebaseCode
384
+ },
385
+ method: 'emailLink'
386
+ }, 'EmailLinkService');
387
+ return {
388
+ success: false,
389
+ error: processedError,
390
+ method: 'emailLink'
391
+ };
392
+ }
393
+ }
394
+ /**
395
+ * Safely sends an email authentication link with error handling
396
+ */
397
+ export const safeSendEmailLink = withErrorHandling(sendEmailLink);
398
+ /**
399
+ * Safely verifies an email authentication link with error handling
400
+ */
401
+ export const safeVerifyEmailLink = withErrorHandling(verifyEmailLink);
402
+ /**
403
+ * Checks if the current URL is an email sign-in link
404
+ *
405
+ * @returns Promise resolving to whether the URL is an email sign-in link
406
+ */
407
+ export async function isEmailSignInLink() {
408
+ try {
409
+ const auth = await getFirebaseAuth(); // Await the Promise
410
+ const currentUrl = window.location.href;
411
+ // Log the check for debugging
412
+ console.log('Checking if URL is email sign-in link:', currentUrl);
413
+ const isValid = isSignInWithEmailLink(auth, currentUrl);
414
+ console.log('Firebase reports URL is valid:', isValid);
415
+ return isValid;
416
+ }
417
+ catch (error) {
418
+ emailLinkLogger.error('Failed to check if URL is email sign-in link', error instanceof Error ? error : new Error(String(error)));
419
+ return false;
420
+ }
421
+ }
422
+ /**
423
+ * Synchronous version that can be used in places where async isn't supported
424
+ * This uses the current auth instance if available, otherwise returns false
425
+ */
426
+ export function isEmailSignInLinkSync() {
427
+ try {
428
+ // Get the current Firebase state without async initialization
429
+ const firebaseState = getFirebaseState();
430
+ // If Firebase isn't initialized or has an error, return false
431
+ if (!firebaseState.initialized || firebaseState.error || !firebaseState.auth) {
432
+ return false;
433
+ }
434
+ const auth = firebaseState.auth;
435
+ const currentUrl = window.location.href;
436
+ return isSignInWithEmailLink(auth, currentUrl);
437
+ }
438
+ catch (error) {
439
+ emailLinkLogger.error('Failed to check if URL is email sign-in link (sync)', error instanceof Error ? error : new Error(String(error)));
440
+ return false;
441
+ }
442
+ }
443
+ /**
444
+ * Gets the stored email for sign-in
445
+ *
446
+ * @returns The stored email or null if not found
447
+ */
448
+ export function getStoredEmail() {
449
+ try {
450
+ let foundEmail = null;
451
+ // Primary source: Firebase standard location - localStorage with key 'emailForSignIn'
452
+ try {
453
+ const email = window.localStorage.getItem('emailForSignIn');
454
+ if (email) {
455
+ // Check expiration timestamp if present
456
+ try {
457
+ const expiresAtStr = window.localStorage.getItem('emailForSignIn_expiresAt');
458
+ if (expiresAtStr) {
459
+ const expiresAt = parseInt(expiresAtStr, 10);
460
+ if (!isNaN(expiresAt) && Date.now() > expiresAt) {
461
+ // Email has expired, clean it up
462
+ emailLinkLogger.info('Email in localStorage has expired, clearing it');
463
+ window.localStorage.removeItem('emailForSignIn');
464
+ window.localStorage.removeItem('emailForSignIn_expiresAt');
465
+ // Continue to fallback mechanisms
466
+ }
467
+ else {
468
+ // Email is still valid
469
+ emailLinkLogger.info('Retrieved valid email from localStorage', { email });
470
+ foundEmail = email;
471
+ }
472
+ }
473
+ else {
474
+ // No expiration timestamp, assume valid
475
+ emailLinkLogger.info('Retrieved email from localStorage (no expiration)', { email });
476
+ foundEmail = email;
477
+ }
478
+ }
479
+ catch (e) {
480
+ // Error checking expiration, assume email is valid
481
+ emailLinkLogger.warn('Error checking email expiration', e instanceof Error ? e : new Error(String(e)));
482
+ foundEmail = email;
483
+ }
484
+ }
485
+ }
486
+ catch (e) {
487
+ emailLinkLogger.warn('Error accessing localStorage', e instanceof Error ? e : new Error(String(e)));
488
+ }
489
+ // Return if we found an email
490
+ if (foundEmail) {
491
+ return foundEmail;
492
+ }
493
+ // Fallback 1: Try sessionStorage
494
+ try {
495
+ const sessionEmail = window.sessionStorage.getItem('emailForSignIn');
496
+ if (sessionEmail) {
497
+ emailLinkLogger.info('Retrieved email from sessionStorage', { email: sessionEmail });
498
+ // Copy to localStorage for Firebase compatibility
499
+ try {
500
+ window.localStorage.setItem('emailForSignIn', sessionEmail);
501
+ }
502
+ catch (e) {
503
+ // Non-critical error
504
+ }
505
+ return sessionEmail;
506
+ }
507
+ }
508
+ catch (e) {
509
+ emailLinkLogger.warn('Error accessing sessionStorage', e instanceof Error ? e : new Error(String(e)));
510
+ }
511
+ // Fallback 2: Try secureStorage with sessionStorage mechanism
512
+ try {
513
+ const secureSessionEmail = secureStorage.getItem(EMAIL_FOR_SIGN_IN_KEY, {
514
+ namespace: AUTH_NAMESPACE,
515
+ mechanism: 'sessionStorage'
516
+ });
517
+ if (secureSessionEmail) {
518
+ emailLinkLogger.info('Retrieved email from secure sessionStorage', { email: secureSessionEmail });
519
+ // Copy to localStorage for Firebase compatibility
520
+ try {
521
+ window.localStorage.setItem('emailForSignIn', secureSessionEmail);
522
+ }
523
+ catch (e) {
524
+ // Non-critical error
525
+ }
526
+ return secureSessionEmail;
527
+ }
528
+ }
529
+ catch (e) {
530
+ // Non-critical error
531
+ }
532
+ // Fallback 3: Try memory storage
533
+ try {
534
+ const memoryEmail = secureStorage.getItem(EMAIL_FOR_SIGN_IN_KEY, {
535
+ namespace: AUTH_NAMESPACE,
536
+ mechanism: 'memory'
537
+ });
538
+ if (memoryEmail) {
539
+ emailLinkLogger.info('Retrieved email from memory storage', { email: memoryEmail });
540
+ // Copy to localStorage for Firebase compatibility
541
+ try {
542
+ window.localStorage.setItem('emailForSignIn', memoryEmail);
543
+ }
544
+ catch (e) {
545
+ // Non-critical error
546
+ }
547
+ return memoryEmail;
548
+ }
549
+ }
550
+ catch (e) {
551
+ // Non-critical error
552
+ }
553
+ // If we get here, we couldn't find the email anywhere
554
+ emailLinkLogger.info('No stored email found in any storage location');
555
+ return null;
556
+ }
557
+ catch (error) {
558
+ emailLinkLogger.error('Failed to get stored email', error instanceof Error ? error : new Error(String(error)));
559
+ return null;
560
+ }
561
+ }
562
+ /**
563
+ * Stores an email for sign-in
564
+ *
565
+ * IMPORTANT: We use localStorage WITHOUT encryption to ensure cross-tab compatibility.
566
+ * This is a necessary security trade-off for email link authentication which requires
567
+ * the email to be available in any tab where the user opens the verification link.
568
+ *
569
+ * @param email Email to store
570
+ * @returns Whether storage was successful
571
+ */
572
+ export function storeEmail(email) {
573
+ try {
574
+ let success = false;
575
+ let sessionStorageSuccess = false;
576
+ // IMPORTANT: Firebase's email link auth specifically looks for 'emailForSignIn'
577
+ // in localStorage (not in secure storage). This is the standard location where
578
+ // Firebase expects to find the email.
579
+ try {
580
+ // 1. Store in localStorage (Firebase's standard location)
581
+ window.localStorage.setItem('emailForSignIn', email);
582
+ // 2. Add expiration timestamp for automatic cleanup
583
+ const expiresAt = Date.now() + (60 * 60 * 1000); // 1 hour
584
+ window.localStorage.setItem('emailForSignIn_expiresAt', expiresAt.toString());
585
+ success = true;
586
+ // 3. Also store in sessionStorage as backup
587
+ try {
588
+ window.sessionStorage.setItem('emailForSignIn', email);
589
+ sessionStorageSuccess = true;
590
+ }
591
+ catch (e) {
592
+ // Non-critical error
593
+ }
594
+ emailLinkLogger.info('Stored email directly in localStorage for Firebase compatibility');
595
+ }
596
+ catch (err) {
597
+ emailLinkLogger.warn('Failed to store email in localStorage', { error: err instanceof Error ? err.message : String(err) });
598
+ }
599
+ // Also keep in secure storage
600
+ const memorySuccess = secureStorage.setItem(EMAIL_FOR_SIGN_IN_KEY, email, {
601
+ namespace: AUTH_NAMESPACE,
602
+ mechanism: 'memory',
603
+ ttl: AUTH_CONFIG.EMAIL_PERSISTENCE_DURATION
604
+ });
605
+ // Store in secure storage with sessionStorage mechanism too
606
+ try {
607
+ secureStorage.setItem(EMAIL_FOR_SIGN_IN_KEY, email, {
608
+ namespace: AUTH_NAMESPACE,
609
+ mechanism: 'sessionStorage',
610
+ ttl: AUTH_CONFIG.EMAIL_PERSISTENCE_DURATION * 2 // Double the TTL for extra safety
611
+ });
612
+ }
613
+ catch (e) {
614
+ // Non-critical error
615
+ }
616
+ emailLinkLogger.info('Email stored for auth', {
617
+ directStorage: true,
618
+ localStorage: success,
619
+ sessionStorage: sessionStorageSuccess,
620
+ memory: memorySuccess
621
+ });
622
+ return success;
623
+ }
624
+ catch (error) {
625
+ emailLinkLogger.error('Failed to store email', error instanceof Error ? error : new Error(String(error)));
626
+ return false;
627
+ }
628
+ }
629
+ /**
630
+ * Clears the stored email for sign-in
631
+ *
632
+ * @returns Whether removal was successful
633
+ */
634
+ export function clearStoredEmail() {
635
+ try {
636
+ let success = false;
637
+ let sessionSuccess = false;
638
+ // Clear from localStorage (Firebase's standard location)
639
+ try {
640
+ window.localStorage.removeItem('emailForSignIn');
641
+ window.localStorage.removeItem('emailForSignIn_expiresAt'); // Also clear expiration timestamp
642
+ success = true;
643
+ emailLinkLogger.info('Cleared email from localStorage');
644
+ }
645
+ catch (err) {
646
+ emailLinkLogger.warn('Failed to clear email from localStorage', { error: err instanceof Error ? err.message : String(err) });
647
+ }
648
+ // Clear from sessionStorage
649
+ try {
650
+ window.sessionStorage.removeItem('emailForSignIn');
651
+ sessionSuccess = true;
652
+ emailLinkLogger.info('Cleared email from sessionStorage');
653
+ }
654
+ catch (err) {
655
+ emailLinkLogger.warn('Failed to clear email from sessionStorage', { error: err instanceof Error ? err.message : String(err) });
656
+ }
657
+ // Clear from secure storage with various mechanisms
658
+ let memoryResult = false;
659
+ let secureStorageResult = false;
660
+ // Memory storage
661
+ try {
662
+ memoryResult = secureStorage.removeItem(EMAIL_FOR_SIGN_IN_KEY, {
663
+ namespace: AUTH_NAMESPACE,
664
+ mechanism: 'memory'
665
+ });
666
+ }
667
+ catch (e) {
668
+ // Non-critical error
669
+ }
670
+ // Session storage
671
+ try {
672
+ secureStorageResult = secureStorage.removeItem(EMAIL_FOR_SIGN_IN_KEY, {
673
+ namespace: AUTH_NAMESPACE,
674
+ mechanism: 'sessionStorage'
675
+ });
676
+ }
677
+ catch (e) {
678
+ // Non-critical error
679
+ }
680
+ // Local storage mechanism
681
+ try {
682
+ secureStorage.removeItem(EMAIL_FOR_SIGN_IN_KEY, {
683
+ namespace: AUTH_NAMESPACE,
684
+ mechanism: 'localStorage'
685
+ });
686
+ }
687
+ catch (e) {
688
+ // Non-critical error
689
+ }
690
+ emailLinkLogger.info('Cleared stored email from all storage mechanisms', {
691
+ localStorage: success,
692
+ sessionStorage: sessionSuccess,
693
+ memory: memoryResult,
694
+ secureStorage: secureStorageResult
695
+ });
696
+ return success || sessionSuccess || memoryResult || secureStorageResult;
697
+ }
698
+ catch (error) {
699
+ emailLinkLogger.error('Failed to clear stored email', error instanceof Error ? error : new Error(String(error)));
700
+ return false;
701
+ }
702
+ }
703
+ export default {
704
+ sendEmailLink,
705
+ verifyEmailLink,
706
+ safeSendEmailLink,
707
+ safeVerifyEmailLink,
708
+ isEmailSignInLink,
709
+ isEmailSignInLinkSync,
710
+ getStoredEmail,
711
+ storeEmail,
712
+ clearStoredEmail,
713
+ checkUserExistsAndCreate
714
+ };
715
+ //# sourceMappingURL=email-link.js.map