@umituz/react-native-firebase 3.0.3 → 3.0.6
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/package.json +7 -1
- package/src/domains/account-deletion/index.ts +15 -10
- package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +235 -26
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts +160 -0
- package/src/domains/auth/domain/value-objects/FirebaseAuthConfig.ts +1 -1
- package/src/domains/auth/index.ts +156 -6
- package/src/domains/auth/infrastructure/config/FirebaseAuthClient.ts +60 -48
- package/src/domains/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +41 -5
- package/src/domains/auth/infrastructure/stores/auth.store.ts +4 -1
- package/src/domains/auth/presentation/hooks/useAnonymousAuth.ts +3 -1
- package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +115 -20
- package/src/domains/auth/presentation/hooks/utils/auth-state-change.handler.ts +5 -11
- package/src/domains/firestore/domain/constants/QuotaLimits.ts +101 -0
- package/src/domains/firestore/domain/entities/QuotaMetrics.ts +26 -0
- package/src/domains/firestore/domain/entities/RequestLog.ts +28 -0
- package/src/domains/firestore/domain/services/QuotaCalculator.ts +71 -0
- package/src/domains/firestore/index.ts +85 -31
- package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +82 -45
- package/src/domains/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +249 -4
- package/src/domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +306 -0
- package/src/domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +92 -0
- package/src/domains/firestore/infrastructure/repositories/BasePaginatedRepository.ts +9 -1
- package/src/domains/firestore/infrastructure/repositories/BaseQueryRepository.ts +34 -8
- package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +48 -9
- package/src/domains/firestore/infrastructure/services/RequestLoggerService.ts +168 -0
- package/src/domains/firestore/presentation/hooks/index.ts +10 -0
- package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
- package/src/domains/firestore/presentation/hooks/useFirestoreQuery.ts +1 -1
- package/src/domains/firestore/presentation/hooks/useFirestoreSnapshot.ts +2 -1
- package/src/domains/firestore/presentation/hooks/useSmartFirestoreSnapshot.ts +362 -0
- package/src/domains/firestore/presentation/query-keys/createFirestoreKeys.ts +32 -0
- package/src/domains/firestore/presentation/query-keys/index.ts +1 -0
- package/src/domains/firestore/utils/deduplication/pending-query-manager.util.ts +126 -0
- package/src/domains/firestore/utils/deduplication/query-key-generator.util.ts +41 -0
- package/src/domains/firestore/utils/deduplication/timer-manager.util.ts +83 -0
- package/src/domains/firestore/utils/pagination.helper.ts +5 -2
- package/src/domains/firestore/utils/transaction/transaction.util.ts +8 -2
- package/src/index.ts +324 -32
- package/src/shared/domain/utils/calculation.util.ts +305 -17
- package/src/shared/domain/utils/error-handlers/error-messages.ts +0 -15
- package/src/shared/domain/utils/index.ts +5 -0
- package/src/shared/infrastructure/config/base/ClientStateManager.ts +82 -0
- package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +136 -20
- package/src/shared/infrastructure/config/clients/FirebaseClientSingleton.ts +1 -1
- package/src/shared/infrastructure/config/initializers/FirebaseAppInitializer.ts +9 -0
- package/src/shared/infrastructure/config/services/FirebaseInitializationService.ts +1 -1
- package/src/shared/infrastructure/config/state/FirebaseClientState.ts +14 -36
- package/src/application/auth/index.ts +0 -10
- package/src/application/auth/use-cases/index.ts +0 -6
- package/src/domains/account-deletion/domain/index.ts +0 -8
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts +0 -79
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionTypes.ts +0 -32
- package/src/domains/auth/domain.ts +0 -16
- package/src/domains/auth/infrastructure/config/index.ts +0 -2
- package/src/domains/auth/infrastructure/config/initializers/index.ts +0 -1
- package/src/domains/auth/infrastructure/services/index.ts +0 -16
- package/src/domains/auth/infrastructure/services/utils/index.ts +0 -1
- package/src/domains/auth/infrastructure/stores/index.ts +0 -1
- package/src/domains/auth/infrastructure/utils/index.ts +0 -1
- package/src/domains/auth/infrastructure.ts +0 -11
- package/src/domains/auth/presentation/hooks/useAppleAuth.ts +0 -82
- package/src/domains/auth/presentation.ts +0 -31
- package/src/domains/firestore/domain/entities/Collection.ts +0 -122
- package/src/domains/firestore/domain/entities/CollectionFactory.ts +0 -55
- package/src/domains/firestore/domain/entities/CollectionHelpers.ts +0 -143
- package/src/domains/firestore/domain/entities/CollectionUtils.ts +0 -72
- package/src/domains/firestore/domain/entities/CollectionValidation.ts +0 -138
- package/src/domains/firestore/domain/index.ts +0 -61
- package/src/domains/firestore/domain/value-objects/QueryOptions.ts +0 -143
- package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +0 -95
- package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +0 -110
- package/src/domains/firestore/domain/value-objects/WhereClause.ts +0 -114
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +0 -101
- package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +0 -123
- package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +0 -83
- package/src/shared/infrastructure/base/ErrorHandler.ts +0 -81
- package/src/shared/infrastructure/base/ServiceBase.ts +0 -62
- package/src/shared/infrastructure/base/TypedGuard.ts +0 -131
- package/src/shared/infrastructure/base/index.ts +0 -34
- package/src/shared/types/firebase.types.ts +0 -274
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -60,6 +60,9 @@
|
|
|
60
60
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
61
61
|
"@react-native-community/datetimepicker": "^8.6.0",
|
|
62
62
|
"@react-native-community/slider": "^5.1.1",
|
|
63
|
+
"@react-navigation/bottom-tabs": "^7.9.0",
|
|
64
|
+
"@react-navigation/native": "^7.1.26",
|
|
65
|
+
"@react-navigation/stack": "^7.6.13",
|
|
63
66
|
"@tanstack/query-async-storage-persister": "^5.66.7",
|
|
64
67
|
"@tanstack/react-query": "^5.66.7",
|
|
65
68
|
"@tanstack/react-query-persist-client": "^5.66.7",
|
|
@@ -102,6 +105,9 @@
|
|
|
102
105
|
"typescript": "~5.9.2",
|
|
103
106
|
"zustand": "^5.0.3"
|
|
104
107
|
},
|
|
108
|
+
"optionalDependencies": {
|
|
109
|
+
"firebase-admin": "^13.0.2"
|
|
110
|
+
},
|
|
105
111
|
"publishConfig": {
|
|
106
112
|
"access": "public"
|
|
107
113
|
},
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Account Deletion Domain
|
|
3
3
|
* Handles Firebase account deletion with reauthentication
|
|
4
|
-
*
|
|
5
|
-
* Domain-Driven Design (DDD) Architecture
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
export type {
|
|
9
|
-
AccountDeletionOptions,
|
|
10
|
-
ReauthenticationResult,
|
|
11
|
-
AuthProviderType,
|
|
12
|
-
ReauthCredentialResult,
|
|
13
|
-
} from './application/ports/reauthentication.types';
|
|
14
|
-
|
|
15
6
|
export {
|
|
16
7
|
deleteCurrentUser,
|
|
17
8
|
deleteUserAccount,
|
|
18
|
-
isDeletionInProgress,
|
|
19
9
|
} from './infrastructure/services/account-deletion.service';
|
|
20
10
|
|
|
21
11
|
export type {
|
|
22
12
|
AccountDeletionResult,
|
|
23
13
|
} from './infrastructure/services/account-deletion.service';
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
AccountDeletionOptions,
|
|
17
|
+
ReauthenticationResult,
|
|
18
|
+
AuthProviderType,
|
|
19
|
+
ReauthCredentialResult,
|
|
20
|
+
} from './application/ports/reauthentication.types';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
getUserAuthProvider,
|
|
24
|
+
reauthenticateWithPassword,
|
|
25
|
+
reauthenticateWithGoogle,
|
|
26
|
+
reauthenticateWithApple,
|
|
27
|
+
getAppleReauthCredential,
|
|
28
|
+
} from './infrastructure/services/reauthentication.service';
|
|
@@ -1,41 +1,250 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Account Deletion Service
|
|
3
3
|
* Handles Firebase account deletion with reauthentication support
|
|
4
|
-
*
|
|
5
|
-
* Max lines: 150 (enforced for maintainability)
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
import type
|
|
6
|
+
import { deleteUser, type User } from "firebase/auth";
|
|
7
|
+
import { getFirebaseAuth } from "../../../auth/infrastructure/config/FirebaseAuthClient";
|
|
8
|
+
import { markUserDeleted } from "../../../auth/infrastructure/services/user-document.service";
|
|
9
|
+
import {
|
|
10
|
+
getUserAuthProvider,
|
|
11
|
+
reauthenticateWithApple,
|
|
12
|
+
reauthenticateWithPassword,
|
|
13
|
+
reauthenticateWithGoogle,
|
|
14
|
+
} from "./reauthentication.service";
|
|
15
|
+
import { successResult, type Result, toErrorInfo } from "../../../../shared/domain/utils";
|
|
9
16
|
import type { AccountDeletionOptions } from "../../application/ports/reauthentication.types";
|
|
10
|
-
import {
|
|
11
|
-
|
|
17
|
+
import { validateUserUnchanged } from "../../../auth/domain/utils/user-validation.util";
|
|
18
|
+
|
|
19
|
+
export interface AccountDeletionResult extends Result<void> {
|
|
20
|
+
requiresReauth?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Operation lock to prevent concurrent deletion attempts
|
|
24
|
+
let deletionInProgress = false;
|
|
12
25
|
|
|
13
26
|
export async function deleteCurrentUser(
|
|
14
27
|
options: AccountDeletionOptions = { autoReauthenticate: true }
|
|
15
28
|
): Promise<AccountDeletionResult> {
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
if (deletionInProgress) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: { code: "auth/operation-in-progress", message: "Account deletion already in progress" },
|
|
33
|
+
requiresReauth: false
|
|
34
|
+
};
|
|
35
|
+
}
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
deletionInProgress = true;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const auth = getFirebaseAuth();
|
|
41
|
+
const user = auth?.currentUser;
|
|
42
|
+
|
|
43
|
+
if (!auth || !user) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: { code: "auth/not-ready", message: "Auth not ready" },
|
|
47
|
+
requiresReauth: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const originalUserId = user.uid;
|
|
52
|
+
|
|
53
|
+
if (user.isAnonymous) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: { code: "auth/anonymous", message: "Cannot delete anonymous" },
|
|
57
|
+
requiresReauth: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const provider = getUserAuthProvider(user);
|
|
62
|
+
|
|
63
|
+
if (provider === "password" && options.autoReauthenticate && options.onPasswordRequired) {
|
|
64
|
+
const reauth = await attemptReauth(user, options, originalUserId);
|
|
65
|
+
if (reauth) {
|
|
66
|
+
return reauth;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const validation = validateUserUnchanged(auth, originalUserId);
|
|
72
|
+
if (!('valid' in validation)) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error: validation.error,
|
|
76
|
+
requiresReauth: false
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const marked = await markUserDeleted(user.uid);
|
|
81
|
+
if (!marked && __DEV__) {
|
|
82
|
+
console.warn('[AccountDeletion] Failed to mark user document as deleted before account removal');
|
|
83
|
+
}
|
|
84
|
+
await deleteUser(user);
|
|
85
|
+
return successResult();
|
|
86
|
+
} catch (error: unknown) {
|
|
87
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
88
|
+
const code = errorInfo.code;
|
|
89
|
+
const message = errorInfo.message;
|
|
90
|
+
|
|
91
|
+
const hasCredentials = !!(options.password || options.googleIdToken);
|
|
92
|
+
const shouldReauth = options.autoReauthenticate === true || hasCredentials;
|
|
93
|
+
|
|
94
|
+
if (code === "auth/requires-recent-login" && shouldReauth) {
|
|
95
|
+
const reauth = await attemptReauth(user, options, originalUserId);
|
|
96
|
+
if (reauth) return reauth;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: { code, message },
|
|
102
|
+
requiresReauth: code === "auth/requires-recent-login"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
} finally {
|
|
106
|
+
deletionInProgress = false;
|
|
107
|
+
}
|
|
28
108
|
}
|
|
29
109
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
110
|
+
async function attemptReauth(user: User, options: AccountDeletionOptions, originalUserId?: string): Promise<AccountDeletionResult | null> {
|
|
111
|
+
if (originalUserId) {
|
|
112
|
+
const authInstance = getFirebaseAuth();
|
|
113
|
+
const validation = validateUserUnchanged(authInstance, originalUserId);
|
|
114
|
+
if (!('valid' in validation)) {
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: validation.error,
|
|
118
|
+
requiresReauth: false
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const provider = getUserAuthProvider(user);
|
|
124
|
+
|
|
125
|
+
let res: { success: boolean; error?: { code?: string; message?: string } };
|
|
126
|
+
|
|
127
|
+
if (provider === "apple.com") {
|
|
128
|
+
res = await reauthenticateWithApple(user);
|
|
129
|
+
} else if (provider === "google.com") {
|
|
130
|
+
let googleToken = options.googleIdToken;
|
|
131
|
+
if (!googleToken && options.onGoogleReauthRequired) {
|
|
132
|
+
const token = await options.onGoogleReauthRequired();
|
|
133
|
+
if (!token) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: { code: "auth/google-reauth-cancelled", message: "Google reauth cancelled" },
|
|
137
|
+
requiresReauth: true
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
googleToken = token;
|
|
141
|
+
}
|
|
142
|
+
if (!googleToken) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: { code: "auth/google-reauth", message: "Google reauth required" },
|
|
146
|
+
requiresReauth: true
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
res = await reauthenticateWithGoogle(user, googleToken);
|
|
150
|
+
} else if (provider === "password") {
|
|
151
|
+
let password = options.password;
|
|
152
|
+
if (!password && options.onPasswordRequired) {
|
|
153
|
+
const pwd = await options.onPasswordRequired();
|
|
154
|
+
if (!pwd) {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: { code: "auth/password-reauth-cancelled", message: "Password reauth cancelled" },
|
|
158
|
+
requiresReauth: true
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
password = pwd;
|
|
162
|
+
}
|
|
163
|
+
if (!password) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: { code: "auth/password-reauth", message: "Password required" },
|
|
167
|
+
requiresReauth: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
res = await reauthenticateWithPassword(user, password);
|
|
171
|
+
} else {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (res.success) {
|
|
176
|
+
try {
|
|
177
|
+
const postReauthAuth = getFirebaseAuth();
|
|
178
|
+
// FIX: Explicit null check - don't fallback to user if auth state changed
|
|
179
|
+
if (!postReauthAuth?.currentUser) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: { code: 'auth/user-changed', message: 'User changed during reauthentication' },
|
|
183
|
+
requiresReauth: false
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const currentUser = postReauthAuth.currentUser;
|
|
188
|
+
|
|
189
|
+
if (originalUserId) {
|
|
190
|
+
const validationCheck = validateUserUnchanged(postReauthAuth, originalUserId);
|
|
191
|
+
if (!('valid' in validationCheck)) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: validationCheck.error,
|
|
195
|
+
requiresReauth: false
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const marked = await markUserDeleted(currentUser.uid);
|
|
201
|
+
if (!marked && __DEV__) {
|
|
202
|
+
console.warn('[AccountDeletion] Failed to mark user document as deleted before account removal (reauth path)');
|
|
203
|
+
}
|
|
204
|
+
await deleteUser(currentUser);
|
|
205
|
+
return successResult();
|
|
206
|
+
} catch (err: unknown) {
|
|
207
|
+
const errorInfo = toErrorInfo(err, 'auth/failed');
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: { code: errorInfo.code, message: errorInfo.message },
|
|
211
|
+
requiresReauth: false
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
error: {
|
|
219
|
+
code: res.error?.code || "auth/reauth-failed",
|
|
220
|
+
message: res.error?.message || "Reauth failed",
|
|
221
|
+
},
|
|
222
|
+
requiresReauth: true
|
|
223
|
+
};
|
|
38
224
|
}
|
|
39
225
|
|
|
40
|
-
|
|
41
|
-
|
|
226
|
+
export async function deleteUserAccount(user: User | null): Promise<AccountDeletionResult> {
|
|
227
|
+
if (!user || user.isAnonymous) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: { code: "auth/invalid", message: "Invalid user" },
|
|
231
|
+
requiresReauth: false
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const marked = await markUserDeleted(user.uid);
|
|
237
|
+
if (!marked && __DEV__) {
|
|
238
|
+
console.warn('[AccountDeletion] Failed to mark user document as deleted (deleteUserAccount)');
|
|
239
|
+
}
|
|
240
|
+
await deleteUser(user);
|
|
241
|
+
return successResult();
|
|
242
|
+
} catch (error: unknown) {
|
|
243
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
error: { code: errorInfo.code, message: errorInfo.message },
|
|
247
|
+
requiresReauth: errorInfo.code === "auth/requires-recent-login"
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reauthentication Service
|
|
3
|
+
* Handles Firebase Auth reauthentication for sensitive operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
reauthenticateWithCredential,
|
|
8
|
+
GoogleAuthProvider,
|
|
9
|
+
OAuthProvider,
|
|
10
|
+
EmailAuthProvider,
|
|
11
|
+
type User,
|
|
12
|
+
} from "firebase/auth";
|
|
13
|
+
import { Platform } from "react-native";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lazy-loads expo-apple-authentication (optional peer dependency).
|
|
17
|
+
* Returns null if the package is not installed.
|
|
18
|
+
*/
|
|
19
|
+
function getAppleAuthModule(): typeof import("expo-apple-authentication") | null {
|
|
20
|
+
try {
|
|
21
|
+
// Dynamic require needed for optional peer dependency
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
return require("expo-apple-authentication");
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
import { generateNonce, hashNonce } from "../../../auth/infrastructure/services/crypto.util";
|
|
29
|
+
import { executeOperation, failureResultFrom, toErrorInfo, ERROR_MESSAGES } from "../../../../shared/domain/utils";
|
|
30
|
+
import { isCancelledError } from "../../../../shared/domain/utils/error-handlers/error-checkers";
|
|
31
|
+
import type {
|
|
32
|
+
ReauthenticationResult,
|
|
33
|
+
AuthProviderType,
|
|
34
|
+
ReauthCredentialResult
|
|
35
|
+
} from "../../application/ports/reauthentication.types";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validates email format
|
|
39
|
+
*/
|
|
40
|
+
function isValidEmail(email: string): boolean {
|
|
41
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
42
|
+
return emailRegex.test(email);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validates password (Firebase minimum is 6 characters)
|
|
47
|
+
*/
|
|
48
|
+
function isValidPassword(password: string): boolean {
|
|
49
|
+
return password.length >= 6;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getUserAuthProvider(user: User): AuthProviderType {
|
|
53
|
+
if (user.isAnonymous) return "anonymous";
|
|
54
|
+
const data = user.providerData;
|
|
55
|
+
if (!data?.length) return "unknown";
|
|
56
|
+
if (data.find(p => p.providerId === "google.com")) return "google.com";
|
|
57
|
+
if (data.find(p => p.providerId === "apple.com")) return "apple.com";
|
|
58
|
+
if (data.find(p => p.providerId === "password")) return "password";
|
|
59
|
+
return "unknown";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function reauthenticateWithGoogle(user: User, idToken: string): Promise<ReauthenticationResult> {
|
|
63
|
+
return executeOperation(async () => {
|
|
64
|
+
await reauthenticateWithCredential(user, GoogleAuthProvider.credential(idToken));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function reauthenticateWithPassword(user: User, pass: string): Promise<ReauthenticationResult> {
|
|
69
|
+
const email = user.email;
|
|
70
|
+
if (!email) {
|
|
71
|
+
return failureResultFrom("auth/no-email", ERROR_MESSAGES.AUTH.NO_USER);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// FIX: Add email validation
|
|
75
|
+
if (!isValidEmail(email)) {
|
|
76
|
+
return failureResultFrom("auth/invalid-email", ERROR_MESSAGES.AUTH.INVALID_EMAIL);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// FIX: Add password validation
|
|
80
|
+
if (!isValidPassword(pass)) {
|
|
81
|
+
return failureResultFrom("auth/invalid-password", ERROR_MESSAGES.AUTH.INVALID_PASSWORD);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return executeOperation(async () => {
|
|
85
|
+
await reauthenticateWithCredential(user, EmailAuthProvider.credential(email, pass));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function getAppleReauthCredential(): Promise<ReauthCredentialResult> {
|
|
90
|
+
if (Platform.OS !== "ios") {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: { code: "auth/ios-only", message: "iOS only" }
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const AppleAuthentication = getAppleAuthModule();
|
|
98
|
+
if (!AppleAuthentication) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: { code: "auth/unavailable", message: "expo-apple-authentication is not installed" }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const isAvailable = await AppleAuthentication.isAvailableAsync();
|
|
107
|
+
if (!isAvailable) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: { code: "auth/unavailable", message: "Unavailable" }
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const nonce = await generateNonce();
|
|
115
|
+
const hashed = await hashNonce(nonce);
|
|
116
|
+
const apple = await AppleAuthentication.signInAsync({
|
|
117
|
+
requestedScopes: [
|
|
118
|
+
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
119
|
+
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
|
120
|
+
],
|
|
121
|
+
nonce: hashed,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!apple.identityToken) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: { code: "auth/no-token", message: "No token" }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const credential = new OAuthProvider("apple.com").credential({
|
|
132
|
+
idToken: apple.identityToken,
|
|
133
|
+
rawNonce: nonce
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
credential
|
|
139
|
+
};
|
|
140
|
+
} catch (error: unknown) {
|
|
141
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
142
|
+
const code = isCancelledError(errorInfo) ? "auth/cancelled" : errorInfo.code;
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: { code, message: errorInfo.message }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function reauthenticateWithApple(user: User): Promise<ReauthenticationResult> {
|
|
151
|
+
const res = await getAppleReauthCredential();
|
|
152
|
+
if (!res.success || !res.credential) {
|
|
153
|
+
return { success: false, error: res.error ?? { code: "auth/failed", message: "Failed to get credential" } };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const credential = res.credential;
|
|
157
|
+
return executeOperation(async () => {
|
|
158
|
+
await reauthenticateWithCredential(user, credential);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -21,7 +21,7 @@ export interface FirebaseAuthConfig {
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Auth state persistence type
|
|
24
|
-
* - 'local': Persist across app restarts (default)
|
|
24
|
+
* - 'local': Persist across browser/app restarts (default)
|
|
25
25
|
* - 'session': Persist only for current session
|
|
26
26
|
* - 'none': No persistence
|
|
27
27
|
*/
|
|
@@ -3,13 +3,163 @@
|
|
|
3
3
|
* Domain-Driven Design (DDD) Architecture
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// DOMAIN LAYER - Business Logic
|
|
8
|
+
// =============================================================================
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
export * from './infrastructure';
|
|
10
|
+
export type { FirebaseAuthConfig } from './domain/value-objects/FirebaseAuthConfig';
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
export
|
|
12
|
+
// Anonymous User Entity
|
|
13
|
+
export {
|
|
14
|
+
isAnonymousUser,
|
|
15
|
+
} from './domain/entities/AnonymousUser';
|
|
16
|
+
export type { AnonymousUser } from './domain/entities/AnonymousUser';
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// INFRASTRUCTURE LAYER - Implementation
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
getFirebaseAuth,
|
|
24
|
+
isFirebaseAuthInitialized,
|
|
25
|
+
getFirebaseAuthInitializationError,
|
|
26
|
+
resetFirebaseAuthClient,
|
|
27
|
+
firebaseAuthClient,
|
|
28
|
+
initializeFirebaseAuth,
|
|
29
|
+
} from './infrastructure/config/FirebaseAuthClient';
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
Auth,
|
|
33
|
+
} from './infrastructure/config/FirebaseAuthClient';
|
|
34
|
+
|
|
35
|
+
// Auth Utilities
|
|
36
|
+
export {
|
|
37
|
+
checkAuthState,
|
|
38
|
+
isAuthenticated,
|
|
39
|
+
isAnonymous,
|
|
40
|
+
getCurrentUserId,
|
|
41
|
+
getCurrentUser,
|
|
42
|
+
getCurrentUserIdFromGlobal,
|
|
43
|
+
getCurrentUserFromGlobal,
|
|
44
|
+
isCurrentUserAuthenticated,
|
|
45
|
+
isCurrentUserAnonymous,
|
|
46
|
+
verifyUserId,
|
|
47
|
+
isValidUser,
|
|
48
|
+
} from './infrastructure/services/auth-utils.service';
|
|
49
|
+
|
|
50
|
+
export type {
|
|
51
|
+
AuthCheckResult,
|
|
52
|
+
} from './infrastructure/services/auth-utils.service';
|
|
53
|
+
|
|
54
|
+
// Anonymous Auth Service
|
|
55
|
+
export {
|
|
56
|
+
AnonymousAuthService,
|
|
57
|
+
anonymousAuthService,
|
|
58
|
+
} from './infrastructure/services/anonymous-auth.service';
|
|
59
|
+
|
|
60
|
+
export type {
|
|
61
|
+
AnonymousAuthResult,
|
|
62
|
+
AnonymousAuthServiceInterface,
|
|
63
|
+
} from './infrastructure/services/anonymous-auth.service';
|
|
64
|
+
|
|
65
|
+
// Firestore Utilities
|
|
66
|
+
export {
|
|
67
|
+
shouldSkipFirestoreQuery,
|
|
68
|
+
createFirestoreQueryOptions,
|
|
69
|
+
} from './infrastructure/services/firestore-utils.service';
|
|
70
|
+
|
|
71
|
+
export type {
|
|
72
|
+
FirestoreQueryOptions,
|
|
73
|
+
FirestoreQueryResult,
|
|
74
|
+
FirestoreQuerySkipReason,
|
|
75
|
+
} from './infrastructure/services/firestore-utils.service';
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// INFRASTRUCTURE LAYER - Social Auth Services
|
|
80
|
+
// =============================================================================
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
GoogleAuthService,
|
|
84
|
+
googleAuthService,
|
|
85
|
+
} from './infrastructure/services/google-auth.service';
|
|
86
|
+
export type {
|
|
87
|
+
GoogleAuthConfig,
|
|
88
|
+
GoogleAuthResult,
|
|
89
|
+
} from './infrastructure/services/google-auth.types';
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
GoogleOAuthService,
|
|
93
|
+
googleOAuthService,
|
|
94
|
+
} from './infrastructure/services/google-oauth.service';
|
|
95
|
+
export type {
|
|
96
|
+
GoogleOAuthConfig,
|
|
97
|
+
} from './infrastructure/services/google-oauth.service';
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
AppleAuthService,
|
|
101
|
+
appleAuthService,
|
|
102
|
+
} from './infrastructure/services/apple-auth.service';
|
|
103
|
+
export type { AppleAuthResult } from './infrastructure/services/apple-auth.types';
|
|
104
|
+
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// PRESENTATION LAYER - Hooks
|
|
107
|
+
// =============================================================================
|
|
108
|
+
|
|
109
|
+
export { useFirebaseAuth } from './presentation/hooks/useFirebaseAuth';
|
|
110
|
+
export type { UseFirebaseAuthResult } from './presentation/hooks/useFirebaseAuth';
|
|
111
|
+
|
|
112
|
+
export { useAnonymousAuth } from './presentation/hooks/useAnonymousAuth';
|
|
113
|
+
export type { UseAnonymousAuthResult } from './presentation/hooks/useAnonymousAuth';
|
|
114
|
+
|
|
115
|
+
export { useSocialAuth } from './presentation/hooks/useSocialAuth';
|
|
116
|
+
export type {
|
|
117
|
+
SocialAuthConfig,
|
|
118
|
+
SocialAuthResult,
|
|
119
|
+
UseSocialAuthResult,
|
|
120
|
+
} from './presentation/hooks/useSocialAuth';
|
|
121
|
+
|
|
122
|
+
export { useGoogleOAuth } from './presentation/hooks/useGoogleOAuth';
|
|
123
|
+
export type {
|
|
124
|
+
UseGoogleOAuthResult,
|
|
125
|
+
} from './presentation/hooks/useGoogleOAuth';
|
|
126
|
+
|
|
127
|
+
// Password Management
|
|
128
|
+
export {
|
|
129
|
+
updateUserPassword,
|
|
130
|
+
} from './infrastructure/services/password.service';
|
|
131
|
+
|
|
132
|
+
// Email/Password Authentication
|
|
133
|
+
export {
|
|
134
|
+
signInWithEmail,
|
|
135
|
+
signUpWithEmail,
|
|
136
|
+
signOut,
|
|
137
|
+
linkAnonymousWithEmail,
|
|
138
|
+
} from './infrastructure/services/email-auth.service';
|
|
139
|
+
export type {
|
|
140
|
+
EmailCredentials,
|
|
141
|
+
EmailAuthResult,
|
|
142
|
+
} from './infrastructure/services/email-auth.service';
|
|
143
|
+
|
|
144
|
+
// Auth Listener
|
|
145
|
+
export {
|
|
146
|
+
setupAuthListener,
|
|
147
|
+
} from './infrastructure/services/auth-listener.service';
|
|
148
|
+
export type {
|
|
149
|
+
AuthListenerConfig,
|
|
150
|
+
AuthListenerResult,
|
|
151
|
+
} from './infrastructure/services/auth-listener.service';
|
|
152
|
+
|
|
153
|
+
// User Document Service
|
|
154
|
+
export {
|
|
155
|
+
ensureUserDocument,
|
|
156
|
+
markUserDeleted,
|
|
157
|
+
configureUserDocumentService,
|
|
158
|
+
} from './infrastructure/services/user-document.service';
|
|
159
|
+
export type {
|
|
160
|
+
UserDocumentUser,
|
|
161
|
+
UserDocumentConfig,
|
|
162
|
+
UserDocumentExtras,
|
|
163
|
+
} from './infrastructure/services/user-document.types';
|
|
14
164
|
|
|
15
165
|
|