@umituz/react-native-firebase 2.6.3 → 2.6.5

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 (108) hide show
  1. package/package.json +1 -1
  2. package/src/application/auth/index.ts +2 -34
  3. package/src/application/auth/use-cases/index.ts +1 -21
  4. package/src/domains/account-deletion/domain/index.ts +1 -8
  5. package/src/domains/account-deletion/index.ts +0 -42
  6. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts +79 -0
  7. package/src/domains/account-deletion/infrastructure/services/AccountDeletionTypes.ts +0 -1
  8. package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +2 -14
  9. package/src/domains/auth/index.ts +3 -12
  10. package/src/domains/auth/infrastructure/config/FirebaseAuthClient.ts +48 -60
  11. package/src/domains/auth/infrastructure/config/index.ts +2 -0
  12. package/src/domains/auth/infrastructure/config/initializers/index.ts +1 -0
  13. package/src/domains/auth/infrastructure/services/index.ts +16 -0
  14. package/src/domains/auth/infrastructure/services/utils/index.ts +1 -0
  15. package/src/domains/auth/infrastructure/stores/index.ts +1 -0
  16. package/src/domains/auth/infrastructure/utils/index.ts +1 -0
  17. package/src/domains/auth/infrastructure.ts +11 -0
  18. package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +18 -59
  19. package/src/domains/firestore/domain/entities/Collection.ts +0 -2
  20. package/src/domains/firestore/domain/index.ts +6 -2
  21. package/src/domains/firestore/domain/value-objects/WhereClause.ts +0 -14
  22. package/src/domains/firestore/index.ts +0 -1
  23. package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +42 -60
  24. package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
  25. package/src/domains/firestore/presentation/hooks/useFirestoreQuery.ts +1 -1
  26. package/src/shared/infrastructure/base/ErrorHandler.ts +81 -0
  27. package/src/shared/infrastructure/base/ServiceBase.ts +62 -0
  28. package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +39 -0
  29. package/src/application/auth/ports/AuthPort.ts.bak +0 -164
  30. package/src/application/auth/ports/AuthPort_part_aa +0 -150
  31. package/src/application/auth/ports/AuthPort_part_ab +0 -14
  32. package/src/application/auth/use-cases/SignInUseCase.ts.bak +0 -253
  33. package/src/application/auth/use-cases/SignInUseCaseHelpers.ts +0 -0
  34. package/src/application/auth/use-cases/SignInUseCaseMain.ts +0 -0
  35. package/src/application/auth/use-cases/SignInUseCase_part_aa +0 -150
  36. package/src/application/auth/use-cases/SignInUseCase_part_ab +0 -103
  37. package/src/application/auth/use-cases/SignOutUseCase.ts.bak +0 -288
  38. package/src/application/auth/use-cases/SignOutUseCaseCleanup.ts +0 -0
  39. package/src/application/auth/use-cases/SignOutUseCaseMain.ts +0 -0
  40. package/src/application/auth/use-cases/SignOutUseCase_part_aa +0 -150
  41. package/src/application/auth/use-cases/SignOutUseCase_part_ab +0 -138
  42. package/src/domains/account-deletion/domain/services/UserValidationHelpers.ts.bak +0 -181
  43. package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_aa +0 -150
  44. package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_ab +0 -31
  45. package/src/domains/account-deletion/domain/services/UserValidationService.ts.bak +0 -286
  46. package/src/domains/account-deletion/domain/services/UserValidationService_part_aa +0 -150
  47. package/src/domains/account-deletion/domain/services/UserValidationService_part_ab +0 -136
  48. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts.bak +0 -230
  49. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_aa +0 -150
  50. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_ab +0 -80
  51. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler.ts.bak +0 -174
  52. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_aa +0 -150
  53. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_ab +0 -24
  54. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository.ts.bak +0 -266
  55. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_aa +0 -150
  56. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_ab +0 -116
  57. package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts.bak +0 -160
  58. package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_aa +0 -150
  59. package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_ab +0 -10
  60. package/src/domains/auth/infrastructure.ts.bak +0 -156
  61. package/src/domains/auth/infrastructure_part_aa +0 -150
  62. package/src/domains/auth/infrastructure_part_ab +0 -6
  63. package/src/domains/auth/presentation/hooks/GoogleOAuthHelpers.ts +0 -0
  64. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService.ts.bak +0 -247
  65. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_aa +0 -150
  66. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_ab +0 -97
  67. package/src/domains/auth/presentation/hooks/GoogleOAuthService.ts +0 -0
  68. package/src/domains/firestore/domain/entities/Collection.ts.bak +0 -288
  69. package/src/domains/firestore/domain/entities/Collection_part_aa +0 -150
  70. package/src/domains/firestore/domain/entities/Collection_part_ab +0 -138
  71. package/src/domains/firestore/domain/entities/Document.ts.bak +0 -233
  72. package/src/domains/firestore/domain/entities/DocumentHelpers.ts +0 -0
  73. package/src/domains/firestore/domain/entities/DocumentMain.ts +0 -0
  74. package/src/domains/firestore/domain/entities/Document_part_aa +0 -150
  75. package/src/domains/firestore/domain/entities/Document_part_ab +0 -83
  76. package/src/domains/firestore/domain/services/QueryService.ts.bak +0 -182
  77. package/src/domains/firestore/domain/services/QueryServiceAnalysis.ts.bak +0 -169
  78. package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_aa +0 -150
  79. package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_ab +0 -19
  80. package/src/domains/firestore/domain/services/QueryServiceHelpers.ts.bak +0 -151
  81. package/src/domains/firestore/domain/services/QueryServiceHelpers_part_aa +0 -150
  82. package/src/domains/firestore/domain/services/QueryServiceHelpers_part_ab +0 -1
  83. package/src/domains/firestore/domain/services/QueryService_part_aa +0 -150
  84. package/src/domains/firestore/domain/services/QueryService_part_ab +0 -32
  85. package/src/domains/firestore/domain/value-objects/QueryOptions.ts.bak +0 -191
  86. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization.ts.bak +0 -207
  87. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_aa +0 -150
  88. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_ab +0 -57
  89. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation.ts.bak +0 -182
  90. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_aa +0 -150
  91. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_ab +0 -32
  92. package/src/domains/firestore/domain/value-objects/QueryOptions_part_aa +0 -150
  93. package/src/domains/firestore/domain/value-objects/QueryOptions_part_ab +0 -41
  94. package/src/domains/firestore/domain/value-objects/WhereClause.ts.bak +0 -299
  95. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts.bak +0 -207
  96. package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_aa +0 -150
  97. package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_ab +0 -57
  98. package/src/domains/firestore/domain/value-objects/WhereClause_part_aa +0 -150
  99. package/src/domains/firestore/domain/value-objects/WhereClause_part_ab +0 -149
  100. package/src/shared/infrastructure/base/ErrorHandler.ts.bak +0 -189
  101. package/src/shared/infrastructure/base/ErrorHandler_part_aa +0 -150
  102. package/src/shared/infrastructure/base/ErrorHandler_part_ab +0 -39
  103. package/src/shared/infrastructure/base/ServiceBase.ts.bak +0 -220
  104. package/src/shared/infrastructure/base/ServiceBase_part_aa +0 -150
  105. package/src/shared/infrastructure/base/ServiceBase_part_ab +0 -70
  106. package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts.bak +0 -155
  107. package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_aa +0 -150
  108. package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_ab +0 -5
@@ -1,181 +0,0 @@
1
- /**
2
- * User Validation Helpers
3
- * Single Responsibility: Provide helper methods for user validation
4
- *
5
- * Helper methods separated from main UserValidationService.
6
- * Provides utility functions for user state checking.
7
- *
8
- * Max lines: 150 (enforced for maintainability)
9
- */
10
-
11
- import type { User } from 'firebase/auth';
12
- import type { UserValidationService } from './UserValidationService';
13
-
14
- /**
15
- * Check if user email matches pattern
16
- */
17
- export function isValidEmailFormat(email: string): boolean {
18
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
19
- return emailRegex.test(email);
20
- }
21
-
22
- /**
23
- * Get password strength indicator
24
- */
25
- export function getPasswordStrength(password: string): 'weak' | 'medium' | 'strong' {
26
- if (password.length < 6) {
27
- return 'weak';
28
- }
29
-
30
- if (password.length < 8) {
31
- return 'medium';
32
- }
33
-
34
- // Check for variety
35
- const hasLower = /[a-z]/.test(password);
36
- const hasUpper = /[A-Z]/.test(password);
37
- const hasNumber = /[0-9]/.test(password);
38
- const hasSpecial = /[^a-zA-Z0-9]/.test(password);
39
-
40
- const varietyScore = [hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length;
41
-
42
- if (varietyScore >= 3) {
43
- return 'strong';
44
- }
45
-
46
- return 'medium';
47
- }
48
-
49
- /**
50
- * Check if account is locked
51
- */
52
- export function isAccountLocked(errorCode?: string): boolean {
53
- return errorCode === 'auth/too-many-requests' || errorCode === 'auth/user-disabled';
54
- }
55
-
56
- /**
57
- * Get user-friendly error message
58
- */
59
- export function getAuthErrorMessage(errorCode: string): string {
60
- const errorMessages: Record<string, string> = {
61
- 'auth/invalid-credentials': 'Invalid email or password',
62
- 'auth/user-not-found': 'No account found with this email',
63
- 'auth/wrong-password': 'Incorrect password',
64
- 'auth/unverified-email': 'Please verify your email first',
65
- 'auth/user-disabled': 'This account has been disabled',
66
- 'auth/too-many-requests': 'Too many attempts. Please try again later',
67
- 'auth/invalid-email': 'Invalid email address',
68
- };
69
-
70
- return errorMessages[errorCode] || 'Authentication failed. Please try again.';
71
- }
72
-
73
- /**
74
- * Check if user session is valid
75
- */
76
- export function isSessionValid(user: User, maxAgeMs: number = 30 * 60 * 1000): boolean {
77
- if (!user.metadata.lastSignInTime) {
78
- return false;
79
- }
80
-
81
- const lastSignIn = new Date(user.metadata.lastSignInTime).getTime();
82
- const timeSinceSignIn = Date.now() - lastSignIn;
83
-
84
- return timeSinceSignIn <= maxAgeMs;
85
- }
86
-
87
- /**
88
- * Check if user needs to refresh session
89
- */
90
- export function needsSessionRefresh(user: User, maxAgeMs: number = 30 * 60 * 1000): boolean {
91
- return !isSessionValid(user, maxAgeMs);
92
- }
93
-
94
- /**
95
- * Get user display name
96
- */
97
- export function getUserDisplayName(user: User): string {
98
- if (user.displayName) {
99
- return user.displayName;
100
- }
101
-
102
- if (user.email) {
103
- return user.email.split('@')[0];
104
- }
105
-
106
- return 'User';
107
- }
108
-
109
- /**
110
- * Get user initials for avatar
111
- */
112
- export function getUserInitials(user: User): string {
113
- if (user.displayName) {
114
- const names = user.displayName.split(' ');
115
- if (names.length >= 2) {
116
- return (names[0][0] + names[names.length - 1][0]).toUpperCase();
117
- }
118
- return names[0][0].toUpperCase();
119
- }
120
-
121
- if (user.email) {
122
- return user.email[0].toUpperCase();
123
- }
124
-
125
- return 'U';
126
- }
127
-
128
- /**
129
- * Check if user profile is complete
130
- */
131
- export function isProfileComplete(user: User): boolean {
132
- return !!(
133
- user.email &&
134
- user.displayName &&
135
- user.emailVerified &&
136
- user.metadata.lastSignInTime
137
- );
138
- }
139
-
140
- /**
141
- * Get profile completion percentage
142
- */
143
- export function getProfileCompletionPercentage(user: User): number {
144
- let completion = 0;
145
-
146
- if (user.email) completion += 25;
147
- if (user.displayName) completion += 25;
148
- if (user.emailVerified) completion += 25;
149
- if (user.metadata.lastSignInTime) completion += 25;
150
-
151
- return completion;
152
- }
153
-
154
- /**
155
- * Check if user can perform sensitive operation
156
- */
157
- export function canPerformSensitiveOperation(user: User, maxSessionAgeMs: number = 5 * 60 * 1000): boolean {
158
- return isSessionValid(user, maxSessionAgeMs);
159
- }
160
-
161
- /**
162
- * Get user account age in days
163
- */
164
- export function getAccountAgeDays(user: User): number | null {
165
- if (!user.metadata.creationTime) {
166
- return null;
167
- }
168
-
169
- const creationTime = new Date(user.metadata.creationTime).getTime();
170
- const ageMs = Date.now() - creationTime;
171
-
172
- return Math.floor(ageMs / (24 * 60 * 60 * 1000));
173
- }
174
-
175
- /**
176
- * Check if account is old enough for operation
177
- */
178
- export function isAccountOldEnough(user: User, minDays: number = 1): boolean {
179
- const ageDays = getAccountAgeDays(user);
180
- return ageDays !== null && ageDays >= minDays;
181
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * User Validation Helpers
3
- * Single Responsibility: Provide helper methods for user validation
4
- *
5
- * Helper methods separated from main UserValidationService.
6
- * Provides utility functions for user state checking.
7
- *
8
- * Max lines: 150 (enforced for maintainability)
9
- */
10
-
11
- import type { User } from 'firebase/auth';
12
- import type { UserValidationService } from './UserValidationService';
13
-
14
- /**
15
- * Check if user email matches pattern
16
- */
17
- export function isValidEmailFormat(email: string): boolean {
18
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
19
- return emailRegex.test(email);
20
- }
21
-
22
- /**
23
- * Get password strength indicator
24
- */
25
- export function getPasswordStrength(password: string): 'weak' | 'medium' | 'strong' {
26
- if (password.length < 6) {
27
- return 'weak';
28
- }
29
-
30
- if (password.length < 8) {
31
- return 'medium';
32
- }
33
-
34
- // Check for variety
35
- const hasLower = /[a-z]/.test(password);
36
- const hasUpper = /[A-Z]/.test(password);
37
- const hasNumber = /[0-9]/.test(password);
38
- const hasSpecial = /[^a-zA-Z0-9]/.test(password);
39
-
40
- const varietyScore = [hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length;
41
-
42
- if (varietyScore >= 3) {
43
- return 'strong';
44
- }
45
-
46
- return 'medium';
47
- }
48
-
49
- /**
50
- * Check if account is locked
51
- */
52
- export function isAccountLocked(errorCode?: string): boolean {
53
- return errorCode === 'auth/too-many-requests' || errorCode === 'auth/user-disabled';
54
- }
55
-
56
- /**
57
- * Get user-friendly error message
58
- */
59
- export function getAuthErrorMessage(errorCode: string): string {
60
- const errorMessages: Record<string, string> = {
61
- 'auth/invalid-credentials': 'Invalid email or password',
62
- 'auth/user-not-found': 'No account found with this email',
63
- 'auth/wrong-password': 'Incorrect password',
64
- 'auth/unverified-email': 'Please verify your email first',
65
- 'auth/user-disabled': 'This account has been disabled',
66
- 'auth/too-many-requests': 'Too many attempts. Please try again later',
67
- 'auth/invalid-email': 'Invalid email address',
68
- };
69
-
70
- return errorMessages[errorCode] || 'Authentication failed. Please try again.';
71
- }
72
-
73
- /**
74
- * Check if user session is valid
75
- */
76
- export function isSessionValid(user: User, maxAgeMs: number = 30 * 60 * 1000): boolean {
77
- if (!user.metadata.lastSignInTime) {
78
- return false;
79
- }
80
-
81
- const lastSignIn = new Date(user.metadata.lastSignInTime).getTime();
82
- const timeSinceSignIn = Date.now() - lastSignIn;
83
-
84
- return timeSinceSignIn <= maxAgeMs;
85
- }
86
-
87
- /**
88
- * Check if user needs to refresh session
89
- */
90
- export function needsSessionRefresh(user: User, maxAgeMs: number = 30 * 60 * 1000): boolean {
91
- return !isSessionValid(user, maxAgeMs);
92
- }
93
-
94
- /**
95
- * Get user display name
96
- */
97
- export function getUserDisplayName(user: User): string {
98
- if (user.displayName) {
99
- return user.displayName;
100
- }
101
-
102
- if (user.email) {
103
- return user.email.split('@')[0];
104
- }
105
-
106
- return 'User';
107
- }
108
-
109
- /**
110
- * Get user initials for avatar
111
- */
112
- export function getUserInitials(user: User): string {
113
- if (user.displayName) {
114
- const names = user.displayName.split(' ');
115
- if (names.length >= 2) {
116
- return (names[0][0] + names[names.length - 1][0]).toUpperCase();
117
- }
118
- return names[0][0].toUpperCase();
119
- }
120
-
121
- if (user.email) {
122
- return user.email[0].toUpperCase();
123
- }
124
-
125
- return 'U';
126
- }
127
-
128
- /**
129
- * Check if user profile is complete
130
- */
131
- export function isProfileComplete(user: User): boolean {
132
- return !!(
133
- user.email &&
134
- user.displayName &&
135
- user.emailVerified &&
136
- user.metadata.lastSignInTime
137
- );
138
- }
139
-
140
- /**
141
- * Get profile completion percentage
142
- */
143
- export function getProfileCompletionPercentage(user: User): number {
144
- let completion = 0;
145
-
146
- if (user.email) completion += 25;
147
- if (user.displayName) completion += 25;
148
- if (user.emailVerified) completion += 25;
149
- if (user.metadata.lastSignInTime) completion += 25;
150
-
@@ -1,31 +0,0 @@
1
- return completion;
2
- }
3
-
4
- /**
5
- * Check if user can perform sensitive operation
6
- */
7
- export function canPerformSensitiveOperation(user: User, maxSessionAgeMs: number = 5 * 60 * 1000): boolean {
8
- return isSessionValid(user, maxSessionAgeMs);
9
- }
10
-
11
- /**
12
- * Get user account age in days
13
- */
14
- export function getAccountAgeDays(user: User): number | null {
15
- if (!user.metadata.creationTime) {
16
- return null;
17
- }
18
-
19
- const creationTime = new Date(user.metadata.creationTime).getTime();
20
- const ageMs = Date.now() - creationTime;
21
-
22
- return Math.floor(ageMs / (24 * 60 * 60 * 1000));
23
- }
24
-
25
- /**
26
- * Check if account is old enough for operation
27
- */
28
- export function isAccountOldEnough(user: User, minDays: number = 1): boolean {
29
- const ageDays = getAccountAgeDays(user);
30
- return ageDays !== null && ageDays >= minDays;
31
- }
@@ -1,286 +0,0 @@
1
- /**
2
- * User Validation Service (Main)
3
- * Single Responsibility: Validate user state for account operations
4
- *
5
- * Domain service that encapsulates user validation logic.
6
- * Moves business logic from infrastructure to domain layer.
7
- *
8
- * Max lines: 150 (enforced for maintainability)
9
- */
10
-
11
- import type { Auth, User } from 'firebase/auth';
12
- import type { Result } from '../../../../shared/domain/utils';
13
- import { successResult, failureResultFrom } from '../../../../shared/domain/utils';
14
-
15
- /**
16
- * User validation service
17
- * Provides user validation functionality for account operations
18
- */
19
- export class UserValidationService {
20
- /**
21
- * Validate user is ready for deletion
22
- * Checks user exists, not anonymous, and has required provider
23
- */
24
- validateForDeletion(user: User | null): Result<{ userId: string; provider: string }> {
25
- if (!user) {
26
- return failureResultFrom('auth/not-ready', 'No authenticated user');
27
- }
28
-
29
- if (user.isAnonymous) {
30
- return failureResultFrom('auth/anonymous', 'Cannot delete anonymous account');
31
- }
32
-
33
- const provider = this.getUserAuthProvider(user);
34
- if (!provider) {
35
- return failureResultFrom('auth/unsupported', 'Unsupported auth provider');
36
- }
37
-
38
- return successResult({
39
- userId: user.uid,
40
- provider,
41
- });
42
- }
43
-
44
- /**
45
- * Validate user hasn't changed during operation
46
- * Critical for preventing race conditions
47
- */
48
- validateUserUnchanged(auth: Auth | null, originalUserId: string): Result<void> {
49
- if (!auth) {
50
- return failureResultFrom('auth/not-ready', 'Auth not initialized');
51
- }
52
-
53
- const currentUserId = auth.currentUser?.uid;
54
-
55
- if (!currentUserId) {
56
- return failureResultFrom('auth/user-signed-out', 'User signed out during operation');
57
- }
58
-
59
- if (currentUserId !== originalUserId) {
60
- return failureResultFrom('auth/user-changed', 'User changed during operation');
61
- }
62
-
63
- return successResult();
64
- }
65
-
66
- /**
67
- * Validate user credentials are present
68
- */
69
- validateCredentials(
70
- user: User,
71
- options: {
72
- password?: string;
73
- googleIdToken?: string;
74
- }
75
- ): Result<void> {
76
- const provider = this.getUserAuthProvider(user);
77
-
78
- if (provider === 'password' && !options.password) {
79
- return failureResultFrom('auth/password-required', 'Password required for reauthentication');
80
- }
81
-
82
- if (provider === 'google.com' && !options.googleIdToken) {
83
- return failureResultFrom('auth/google-token-required', 'Google ID token required for reauthentication');
84
- }
85
-
86
- return successResult();
87
- }
88
-
89
- /**
90
- * Get user's auth provider
91
- * Returns provider ID or null if unknown
92
- */
93
- getUserAuthProvider(user: User): string | null {
94
- if (!user.providerData || user.providerData.length === 0) {
95
- return null;
96
- }
97
-
98
- // Check all providers
99
- for (const userInfo of user.providerData) {
100
- if (userInfo.providerId) {
101
- return userInfo.providerId;
102
- }
103
- }
104
-
105
- return null;
106
- }
107
-
108
- /**
109
- * Check if user has specific provider
110
- */
111
- hasProvider(user: User, providerId: string): boolean {
112
- if (!user.providerData || user.providerData.length === 0) {
113
- return false;
114
- }
115
-
116
- return user.providerData.some(
117
- userInfo => userInfo.providerId === providerId
118
- );
119
- }
120
-
121
- /**
122
- * Check if user is email/password user
123
- */
124
- isEmailPasswordUser(user: User): boolean {
125
- return this.hasProvider(user, 'password');
126
- }
127
-
128
- /**
129
- * Check if user is Google user
130
- */
131
- isGoogleUser(user: User): boolean {
132
- return this.hasProvider(user, 'google.com');
133
- }
134
-
135
- /**
136
- * Check if user is Apple user
137
- */
138
- isAppleUser(user: User): boolean {
139
- return this.hasProvider(user, 'apple.com');
140
- }
141
-
142
- /**
143
- * Check if user requires reauthentication
144
- * Based on provider and operation type
145
- */
146
- requiresReauthentication(user: User, operation: 'delete' | 'update'): boolean {
147
- // Email/password users always need recent auth
148
- if (this.isEmailPasswordUser(user)) {
149
- return true;
150
- }
151
-
152
- // Social providers may require recent auth for sensitive operations
153
- if (operation === 'delete') {
154
- return true;
155
- }
156
-
157
- return false;
158
- }
159
-
160
- /**
161
- * Validate user email
162
- */
163
- validateEmail(user: User): Result<void> {
164
- const email = user.email;
165
-
166
- if (!email) {
167
- return failureResultFrom('auth/no-email', 'User has no email');
168
- }
169
-
170
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
171
- if (!emailRegex.test(email)) {
172
- return failureResultFrom('auth/invalid-email', 'Invalid email format');
173
- }
174
-
175
- return successResult();
176
- }
177
-
178
- /**
179
- * Validate user is verified (if applicable)
180
- */
181
- validateVerified(user: User, requireVerification: boolean = false): Result<void> {
182
- if (requireVerification && !user.emailVerified) {
183
- return failureResultFrom('auth/unverified', 'Email not verified');
184
- }
185
-
186
- return successResult();
187
- }
188
-
189
- /**
190
- * Get user metadata
191
- */
192
- getUserMetadata(user: User): {
193
- readonly createdAt: number | null;
194
- readonly lastSignInAt: number | null;
195
- } {
196
- return {
197
- createdAt: user.metadata.creationTime ? new Date(user.metadata.creationTime).getTime() : null,
198
- lastSignInAt: user.metadata.lastSignInTime ? new Date(user.metadata.lastSignInTime).getTime() : null,
199
- };
200
- }
201
-
202
- /**
203
- * Check if account is new (created recently)
204
- */
205
- isAccountNew(user: User, maxAgeMs: number = 24 * 60 * 60 * 1000): boolean {
206
- const metadata = this.getUserMetadata(user);
207
- if (!metadata.createdAt) return false;
208
-
209
- const age = Date.now() - metadata.createdAt;
210
- return age <= maxAgeMs;
211
- }
212
-
213
- /**
214
- * Check if user recently signed in
215
- */
216
- isRecentSignIn(user: User, maxAgeMs: number = 5 * 60 * 1000): boolean {
217
- const metadata = this.getUserMetadata(user);
218
- if (!metadata.lastSignInAt) return false;
219
-
220
- const timeSinceSignIn = Date.now() - metadata.lastSignInAt;
221
- return timeSinceSignIn <= maxAgeMs;
222
- }
223
-
224
- /**
225
- * Validate user can perform operation
226
- * Comprehensive check combining multiple validations
227
- */
228
- validateCanPerformOperation(
229
- user: User | null,
230
- operation: 'delete' | 'update',
231
- options: {
232
- requireVerified?: boolean;
233
- maxSignInAge?: number;
234
- password?: string;
235
- googleIdToken?: string;
236
- } = {}
237
- ): Result<{ userId: string; provider: string }> {
238
- // Validate user ready for operation
239
- const deletionValidation = this.validateForDeletion(user);
240
- if (!deletionValidation.success) {
241
- return deletionValidation;
242
- }
243
-
244
- const userId = deletionValidation.data!.userId;
245
- const provider = deletionValidation.data!.provider;
246
-
247
- // Validate email
248
- if (user) {
249
- const emailValidation = this.validateEmail(user);
250
- if (!emailValidation.success) {
251
- return emailValidation;
252
- }
253
-
254
- // Validate verification status
255
- const verifiedValidation = this.validateVerified(user, options.requireVerified);
256
- if (!verifiedValidation.success) {
257
- return verifiedValidation;
258
- }
259
-
260
- // Check if recent sign-in required
261
- if (options.maxSignInAge && !this.isRecentSignIn(user, options.maxSignInAge)) {
262
- return failureResultFrom('auth/stale-session', 'Session too old, please sign in again');
263
- }
264
-
265
- // Validate credentials
266
- const credentialsValidation = this.validateCredentials(user, options);
267
- if (!credentialsValidation.success) {
268
- return credentialsValidation;
269
- }
270
- }
271
-
272
- return successResult({ userId, provider });
273
- }
274
- }
275
-
276
- /**
277
- * Factory function to create user validation service
278
- */
279
- export function createUserValidationService(): UserValidationService {
280
- return new UserValidationService();
281
- }
282
-
283
- /**
284
- * Default instance for convenience
285
- */
286
- export const userValidationService = createUserValidationService();