@umituz/react-native-firebase 2.4.13 → 2.4.15
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 +1 -1
- package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +29 -21
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts +2 -2
- package/src/domains/auth/domain/utils/user-metadata.util.ts +72 -0
- package/src/domains/auth/domain/utils/user-validation.util.ts +77 -0
- package/src/domains/auth/infrastructure/services/apple-auth.service.ts +6 -27
- package/src/domains/auth/infrastructure/services/auth-guard.service.ts +39 -33
- package/src/domains/auth/infrastructure/services/base/base-auth.service.ts +2 -2
- package/src/domains/auth/infrastructure/services/email-auth.service.ts +18 -38
- package/src/domains/auth/infrastructure/services/google-auth.service.ts +6 -27
- package/src/domains/auth/infrastructure/services/utils/auth-result-converter.util.ts +71 -0
- package/src/domains/auth/infrastructure/utils/auth-guard.util.ts +98 -0
- package/src/domains/firestore/index.ts +1 -1
- package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +3 -16
- package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +1 -4
- package/src/domains/firestore/utils/firestore-helper.ts +0 -3
- package/src/domains/firestore/utils/operation/operation-executor.util.ts +3 -3
- package/src/domains/firestore/utils/result/result.util.ts +7 -25
- package/src/index.ts +1 -1
- package/src/shared/domain/utils/async-executor.util.ts +1 -6
- package/src/shared/domain/utils/error-handler.util.ts +4 -2
- package/src/shared/domain/utils/error-handlers/error-checkers.ts +1 -1
- package/src/shared/domain/utils/error-handlers/error-converters.ts +6 -28
- package/src/shared/domain/utils/executors/basic-executors.util.ts +10 -6
- package/src/shared/domain/utils/index.ts +0 -4
- package/src/shared/domain/utils/result/result-creators.ts +3 -16
- package/src/shared/domain/utils/type-guards.util.ts +33 -8
- package/src/shared/domain/utils/validators/firebase.validator.ts +4 -5
- package/src/shared/domain/utils/validators/url.validator.ts +2 -2
- package/src/shared/domain/utils/validators/user-input.validator.ts +1 -1
- package/src/shared/domain/utils/validators/validation.util.ts +63 -0
- package/src/shared/domain/utils/executors/error-converters.util.ts +0 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.15",
|
|
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",
|
|
@@ -14,8 +14,9 @@ import {
|
|
|
14
14
|
reauthenticateWithPassword,
|
|
15
15
|
reauthenticateWithGoogle,
|
|
16
16
|
} from "./reauthentication.service";
|
|
17
|
-
import { successResult, type Result,
|
|
17
|
+
import { successResult, type Result, toErrorInfo } from "../../../../shared/domain/utils";
|
|
18
18
|
import type { AccountDeletionOptions } from "../../application/ports/reauthentication.types";
|
|
19
|
+
import { validateUserUnchanged } from "../../../auth/domain/utils/user-validation.util";
|
|
19
20
|
|
|
20
21
|
export interface AccountDeletionResult extends Result<void> {
|
|
21
22
|
requiresReauth?: boolean;
|
|
@@ -101,15 +102,16 @@ export async function deleteCurrentUser(
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
try {
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
105
|
+
// Verify user hasn't changed before deletion
|
|
106
|
+
const validation = validateUserUnchanged(auth, originalUserId);
|
|
107
|
+
if (!('valid' in validation)) {
|
|
108
|
+
// User changed - validation is a FailureResult
|
|
107
109
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
108
110
|
console.log("[deleteCurrentUser] User changed during operation");
|
|
109
111
|
}
|
|
110
112
|
return {
|
|
111
113
|
success: false,
|
|
112
|
-
error:
|
|
114
|
+
error: validation.error,
|
|
113
115
|
requiresReauth: false
|
|
114
116
|
};
|
|
115
117
|
}
|
|
@@ -131,7 +133,7 @@ export async function deleteCurrentUser(
|
|
|
131
133
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
132
134
|
console.error("[deleteCurrentUser] deleteUser failed:", error);
|
|
133
135
|
}
|
|
134
|
-
const errorInfo =
|
|
136
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
135
137
|
const code = errorInfo.code;
|
|
136
138
|
const message = errorInfo.message;
|
|
137
139
|
|
|
@@ -160,17 +162,18 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
|
|
|
160
162
|
console.log("[attemptReauth] Called");
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
//
|
|
165
|
+
// Verify user hasn't changed if originalUserId provided
|
|
164
166
|
if (originalUserId) {
|
|
165
167
|
const auth = getFirebaseAuth();
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
+
const validation = validateUserUnchanged(auth, originalUserId);
|
|
169
|
+
if (!('valid' in validation)) {
|
|
170
|
+
// User changed - validation is a FailureResult
|
|
168
171
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
169
172
|
console.log("[attemptReauth] User changed during reauthentication");
|
|
170
173
|
}
|
|
171
174
|
return {
|
|
172
175
|
success: false,
|
|
173
|
-
error:
|
|
176
|
+
error: validation.error,
|
|
174
177
|
requiresReauth: false
|
|
175
178
|
};
|
|
176
179
|
}
|
|
@@ -269,16 +272,21 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
|
|
|
269
272
|
const auth = getFirebaseAuth();
|
|
270
273
|
const currentUser = auth?.currentUser || user;
|
|
271
274
|
|
|
272
|
-
//
|
|
273
|
-
if (originalUserId
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
// Final verification before deletion
|
|
276
|
+
if (originalUserId) {
|
|
277
|
+
const auth = getFirebaseAuth();
|
|
278
|
+
const validation = validateUserUnchanged(auth, originalUserId);
|
|
279
|
+
if (!('valid' in validation)) {
|
|
280
|
+
// User changed - validation is a FailureResult
|
|
281
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
282
|
+
console.log("[attemptReauth] User changed after reauthentication");
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: validation.error,
|
|
287
|
+
requiresReauth: false
|
|
288
|
+
};
|
|
276
289
|
}
|
|
277
|
-
return {
|
|
278
|
-
success: false,
|
|
279
|
-
error: { code: "auth/user-changed", message: "User changed during operation" },
|
|
280
|
-
requiresReauth: false
|
|
281
|
-
};
|
|
282
290
|
}
|
|
283
291
|
|
|
284
292
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
@@ -295,7 +303,7 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
|
|
|
295
303
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
296
304
|
console.error("[attemptReauth] deleteUser failed after reauth:", err);
|
|
297
305
|
}
|
|
298
|
-
const errorInfo =
|
|
306
|
+
const errorInfo = toErrorInfo(err, 'auth/failed');
|
|
299
307
|
return {
|
|
300
308
|
success: false,
|
|
301
309
|
error: { code: errorInfo.code, message: errorInfo.message },
|
|
@@ -330,7 +338,7 @@ export async function deleteUserAccount(user: User | null): Promise<AccountDelet
|
|
|
330
338
|
await deleteUser(user);
|
|
331
339
|
return successResult();
|
|
332
340
|
} catch (error: unknown) {
|
|
333
|
-
const errorInfo =
|
|
341
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
334
342
|
return {
|
|
335
343
|
success: false,
|
|
336
344
|
error: { code: errorInfo.code, message: errorInfo.message },
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import * as AppleAuthentication from "expo-apple-authentication";
|
|
14
14
|
import { Platform } from "react-native";
|
|
15
15
|
import { generateNonce, hashNonce } from "../../../auth/infrastructure/services/crypto.util";
|
|
16
|
-
import { executeOperation, failureResultFrom,
|
|
16
|
+
import { executeOperation, failureResultFrom, toErrorInfo, ERROR_MESSAGES } from "../../../../shared/domain/utils";
|
|
17
17
|
import { isCancelledError } from "../../../../shared/domain/utils/error-handler.util";
|
|
18
18
|
import type {
|
|
19
19
|
ReauthenticationResult,
|
|
@@ -123,7 +123,7 @@ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult
|
|
|
123
123
|
credential
|
|
124
124
|
};
|
|
125
125
|
} catch (error: unknown) {
|
|
126
|
-
const errorInfo =
|
|
126
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
127
127
|
const code = isCancelledError(errorInfo) ? "auth/cancelled" : errorInfo.code;
|
|
128
128
|
return {
|
|
129
129
|
success: false,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Metadata Utilities
|
|
3
|
+
* Shared utilities for working with Firebase user metadata
|
|
4
|
+
* Eliminates duplicate isNewUser logic across auth services
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { UserCredential, User, UserMetadata } from 'firebase/auth';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if user is new based on metadata timestamps
|
|
11
|
+
* Compares creation time and last sign-in time to determine if this is the user's first login
|
|
12
|
+
*
|
|
13
|
+
* A user is considered "new" if their account creation time matches their last sign-in time,
|
|
14
|
+
* indicating this is their first authentication.
|
|
15
|
+
*
|
|
16
|
+
* @param input - UserCredential, User, or UserMetadata to check
|
|
17
|
+
* @returns True if user is new (first sign-in), false otherwise
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // From UserCredential
|
|
22
|
+
* const credential = await signInWithCredential(auth, oauthCredential);
|
|
23
|
+
* if (isNewUser(credential)) {
|
|
24
|
+
* // Show onboarding
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // From User object
|
|
28
|
+
* const user = auth.currentUser;
|
|
29
|
+
* if (user && isNewUser(user)) {
|
|
30
|
+
* // First time user
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // From UserMetadata directly
|
|
34
|
+
* if (isNewUser(user.metadata)) {
|
|
35
|
+
* // New user
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function isNewUser(userCredential: UserCredential): boolean;
|
|
40
|
+
export function isNewUser(user: User): boolean;
|
|
41
|
+
export function isNewUser(metadata: UserMetadata): boolean;
|
|
42
|
+
export function isNewUser(input: UserCredential | User | UserMetadata): boolean {
|
|
43
|
+
let metadata: UserMetadata;
|
|
44
|
+
|
|
45
|
+
// Extract metadata from different input types
|
|
46
|
+
if ('user' in input) {
|
|
47
|
+
// UserCredential
|
|
48
|
+
metadata = input.user.metadata;
|
|
49
|
+
} else if ('metadata' in input) {
|
|
50
|
+
// User
|
|
51
|
+
metadata = input.metadata;
|
|
52
|
+
} else {
|
|
53
|
+
// UserMetadata
|
|
54
|
+
metadata = input;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { creationTime, lastSignInTime } = metadata;
|
|
58
|
+
|
|
59
|
+
// Validate timestamps exist and are strings
|
|
60
|
+
if (
|
|
61
|
+
!creationTime ||
|
|
62
|
+
!lastSignInTime ||
|
|
63
|
+
typeof creationTime !== 'string' ||
|
|
64
|
+
typeof lastSignInTime !== 'string'
|
|
65
|
+
) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Convert to timestamps for reliable comparison
|
|
70
|
+
// If creation time === last sign in time, this is the first sign-in
|
|
71
|
+
return new Date(creationTime).getTime() === new Date(lastSignInTime).getTime();
|
|
72
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Validation Utilities
|
|
3
|
+
* Shared utilities for validating user state during operations
|
|
4
|
+
* Eliminates duplicate user validation patterns in account deletion and reauthentication
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Auth } from 'firebase/auth';
|
|
8
|
+
import { failureResultFrom } from '../../../../shared/domain/utils/result/result-creators';
|
|
9
|
+
import { ERROR_MESSAGES } from '../../../../shared/domain/utils/error-handlers/error-messages';
|
|
10
|
+
import type { FailureResult } from '../../../../shared/domain/utils/result/result-types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate that current user hasn't changed during operation
|
|
14
|
+
* Common pattern in account deletion and reauthentication flows
|
|
15
|
+
*
|
|
16
|
+
* This ensures operations complete on the same user that started them,
|
|
17
|
+
* preventing race conditions where user signs out/in during sensitive operations.
|
|
18
|
+
*
|
|
19
|
+
* @param auth - Firebase Auth instance (can be null)
|
|
20
|
+
* @param originalUserId - User ID captured at operation start
|
|
21
|
+
* @returns Success indicator or failure result if user changed
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const originalUserId = auth.currentUser?.uid;
|
|
26
|
+
* if (!originalUserId) {
|
|
27
|
+
* return failureResultFrom('auth/no-user', 'No user signed in');
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // ... perform operation ...
|
|
31
|
+
*
|
|
32
|
+
* const validation = validateUserUnchanged(auth, originalUserId);
|
|
33
|
+
* if (!validation.valid) {
|
|
34
|
+
* return validation; // Return the failure result
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function validateUserUnchanged(
|
|
39
|
+
auth: Auth | null,
|
|
40
|
+
originalUserId: string
|
|
41
|
+
): { valid: true } | FailureResult {
|
|
42
|
+
const currentUserId = auth?.currentUser?.uid;
|
|
43
|
+
|
|
44
|
+
if (currentUserId !== originalUserId) {
|
|
45
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
46
|
+
console.log(
|
|
47
|
+
`[validateUserUnchanged] User changed during operation. Original: ${originalUserId}, Current: ${currentUserId || 'none'}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return failureResultFrom(
|
|
51
|
+
'auth/user-changed',
|
|
52
|
+
ERROR_MESSAGES.AUTH.USER_CHANGED || 'User changed during operation'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { valid: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Capture current user ID for later validation
|
|
61
|
+
* Returns null if no user is signed in
|
|
62
|
+
*
|
|
63
|
+
* @param auth - Firebase Auth instance
|
|
64
|
+
* @returns Current user ID or null
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const auth = getFirebaseAuth();
|
|
69
|
+
* const originalUserId = captureUserId(auth);
|
|
70
|
+
* if (!originalUserId) {
|
|
71
|
+
* return failureResultFrom('auth/no-user', 'No user signed in');
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function captureUserId(auth: Auth | null): string | null {
|
|
76
|
+
return auth?.currentUser?.uid ?? null;
|
|
77
|
+
}
|
|
@@ -15,7 +15,9 @@ import type { AppleAuthResult } from "./apple-auth.types";
|
|
|
15
15
|
import {
|
|
16
16
|
isCancellationError,
|
|
17
17
|
} from "./base/base-auth.service";
|
|
18
|
-
import { executeAuthOperation,
|
|
18
|
+
import { executeAuthOperation, type Result } from "../../../../shared/domain/utils";
|
|
19
|
+
import { isNewUser as checkIsNewUser } from "../../domain/utils/user-metadata.util";
|
|
20
|
+
import { convertToOAuthResult } from "./utils/auth-result-converter.util";
|
|
19
21
|
|
|
20
22
|
// Conditional import - expo-apple-authentication is optional
|
|
21
23
|
let AppleAuthentication: any = null;
|
|
@@ -42,19 +44,7 @@ export class AppleAuthService {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
private convertToAppleAuthResult(result: Result<{ userCredential: any; isNewUser: boolean }>): AppleAuthResult {
|
|
45
|
-
|
|
46
|
-
if (isSuccess(result) && result.data) {
|
|
47
|
-
return {
|
|
48
|
-
success: true,
|
|
49
|
-
userCredential: result.data.userCredential,
|
|
50
|
-
isNewUser: result.data.isNewUser,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
success: false,
|
|
55
|
-
error: result.error?.message ?? "Apple sign-in failed",
|
|
56
|
-
code: result.error?.code,
|
|
57
|
-
};
|
|
47
|
+
return convertToOAuthResult(result, "Apple sign-in failed");
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
async signIn(auth: Auth): Promise<AppleAuthResult> {
|
|
@@ -99,19 +89,8 @@ export class AppleAuthService {
|
|
|
99
89
|
|
|
100
90
|
const userCredential = await signInWithCredential(auth, credential);
|
|
101
91
|
|
|
102
|
-
// Check if user is new
|
|
103
|
-
|
|
104
|
-
const creationTime = userCredential.user.metadata.creationTime;
|
|
105
|
-
const lastSignInTime = userCredential.user.metadata.lastSignInTime;
|
|
106
|
-
|
|
107
|
-
// FIX: Add typeof validation for metadata timestamps
|
|
108
|
-
const isNewUser =
|
|
109
|
-
creationTime &&
|
|
110
|
-
lastSignInTime &&
|
|
111
|
-
typeof creationTime === 'string' &&
|
|
112
|
-
typeof lastSignInTime === 'string'
|
|
113
|
-
? new Date(creationTime).getTime() === new Date(lastSignInTime).getTime()
|
|
114
|
-
: false;
|
|
92
|
+
// Check if user is new using shared utility
|
|
93
|
+
const isNewUser = checkIsNewUser(userCredential);
|
|
115
94
|
|
|
116
95
|
return {
|
|
117
96
|
userCredential,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getCurrentUser,
|
|
13
13
|
} from './auth-utils.service';
|
|
14
14
|
import { ERROR_MESSAGES } from '../../../../shared/domain/utils/error-handlers/error-messages';
|
|
15
|
+
import { withAuth, withAuthSync } from '../utils/auth-guard.util';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Auth Guard Service
|
|
@@ -23,26 +24,33 @@ export class AuthGuardService {
|
|
|
23
24
|
* @throws Error if user is not authenticated
|
|
24
25
|
*/
|
|
25
26
|
async requireAuthenticatedUser(): Promise<string> {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const result = await withAuth(async (auth) => {
|
|
28
|
+
const userId = getCurrentUserId(auth);
|
|
29
|
+
if (!userId) {
|
|
30
|
+
throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
|
|
31
|
+
}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const currentUser = getCurrentUser(auth);
|
|
34
|
+
if (!currentUser) {
|
|
35
|
+
throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (currentUser.isAnonymous) {
|
|
39
|
+
throw new Error(ERROR_MESSAGES.AUTH.NON_ANONYMOUS_ONLY);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return userId;
|
|
43
|
+
});
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
throw new Error(result.error?.message || 'Authentication failed');
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
if (
|
|
42
|
-
throw new Error(
|
|
49
|
+
if (!result.data) {
|
|
50
|
+
throw new Error('No user ID returned');
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
return
|
|
53
|
+
return result.data;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
/**
|
|
@@ -61,35 +69,33 @@ export class AuthGuardService {
|
|
|
61
69
|
* Check if current user is authenticated (not guest)
|
|
62
70
|
*/
|
|
63
71
|
isAuthenticated(): boolean {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
const result = withAuthSync((auth) => {
|
|
73
|
+
const currentUser = getCurrentUser(auth);
|
|
74
|
+
if (!currentUser) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
78
|
+
// User must not be anonymous
|
|
79
|
+
return !currentUser.isAnonymous;
|
|
80
|
+
});
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
return !currentUser.isAnonymous;
|
|
82
|
+
return result.success && result.data ? result.data : false;
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
/**
|
|
79
86
|
* Check if current user is guest (anonymous)
|
|
80
87
|
*/
|
|
81
88
|
isGuest(): boolean {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
const result = withAuthSync((auth) => {
|
|
90
|
+
const currentUser = getCurrentUser(auth);
|
|
91
|
+
if (!currentUser) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
95
|
+
return currentUser.isAnonymous;
|
|
96
|
+
});
|
|
91
97
|
|
|
92
|
-
return
|
|
98
|
+
return result.success && result.data ? result.data : false;
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { UserCredential } from 'firebase/auth';
|
|
9
|
-
import {
|
|
9
|
+
import { toErrorInfo, type Result } from '../../../../../shared/domain/utils';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Authentication result with user credential
|
|
@@ -45,7 +45,7 @@ export function isCancellationError(error: unknown): boolean {
|
|
|
45
45
|
* Create failure result from error
|
|
46
46
|
*/
|
|
47
47
|
export function createFailureResult(error: unknown): { success: false; error: { code: string; message: string } } {
|
|
48
|
-
const errorInfo =
|
|
48
|
+
const errorInfo = toErrorInfo(error, 'auth/failed');
|
|
49
49
|
return {
|
|
50
50
|
success: false,
|
|
51
51
|
error: errorInfo,
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
type User,
|
|
14
14
|
} from "firebase/auth";
|
|
15
15
|
import { getFirebaseAuth } from "../config/FirebaseAuthClient";
|
|
16
|
-
import { executeOperation, failureResultFrom, successResult,
|
|
16
|
+
import { executeOperation, failureResultFrom, successResult, type Result, ERROR_MESSAGES } from "../../../../shared/domain/utils";
|
|
17
|
+
import { withAuth } from "../utils/auth-guard.util";
|
|
17
18
|
|
|
18
19
|
export interface EmailCredentials {
|
|
19
20
|
email: string;
|
|
@@ -30,22 +31,14 @@ export async function signInWithEmail(
|
|
|
30
31
|
email: string,
|
|
31
32
|
password: string
|
|
32
33
|
): Promise<EmailAuthResult> {
|
|
33
|
-
|
|
34
|
-
if (!auth) {
|
|
35
|
-
return failureResultFrom("auth/not-ready", ERROR_MESSAGES.AUTH.NOT_INITIALIZED);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try {
|
|
34
|
+
return withAuth(async (auth) => {
|
|
39
35
|
const userCredential = await signInWithEmailAndPassword(
|
|
40
36
|
auth,
|
|
41
37
|
email.trim(),
|
|
42
38
|
password
|
|
43
39
|
);
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
// FIX: Use toAuthErrorInfo() instead of unsafe cast
|
|
47
|
-
return { success: false, error: toAuthErrorInfo(error) };
|
|
48
|
-
}
|
|
40
|
+
return userCredential.user;
|
|
41
|
+
});
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
/**
|
|
@@ -55,12 +48,7 @@ export async function signInWithEmail(
|
|
|
55
48
|
export async function signUpWithEmail(
|
|
56
49
|
credentials: EmailCredentials
|
|
57
50
|
): Promise<EmailAuthResult> {
|
|
58
|
-
|
|
59
|
-
if (!auth) {
|
|
60
|
-
return failureResultFrom("auth/not-ready", ERROR_MESSAGES.AUTH.NOT_INITIALIZED);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
51
|
+
return withAuth(async (auth) => {
|
|
64
52
|
const currentUser = auth.currentUser;
|
|
65
53
|
const isAnonymous = currentUser?.isAnonymous ?? false;
|
|
66
54
|
let userCredential;
|
|
@@ -84,7 +72,6 @@ export async function signUpWithEmail(
|
|
|
84
72
|
// Update display name if provided (non-critical operation)
|
|
85
73
|
if (credentials.displayName && userCredential.user) {
|
|
86
74
|
const trimmedName = credentials.displayName.trim();
|
|
87
|
-
// FIX: Only update if non-empty after trim
|
|
88
75
|
if (trimmedName.length > 0) {
|
|
89
76
|
try {
|
|
90
77
|
await updateProfile(userCredential.user, {
|
|
@@ -98,11 +85,8 @@ export async function signUpWithEmail(
|
|
|
98
85
|
}
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
// FIX: Use toAuthErrorInfo() instead of unsafe cast
|
|
104
|
-
return { success: false, error: toAuthErrorInfo(error) };
|
|
105
|
-
}
|
|
88
|
+
return userCredential.user;
|
|
89
|
+
});
|
|
106
90
|
}
|
|
107
91
|
|
|
108
92
|
/**
|
|
@@ -126,22 +110,18 @@ export async function linkAnonymousWithEmail(
|
|
|
126
110
|
email: string,
|
|
127
111
|
password: string
|
|
128
112
|
): Promise<EmailAuthResult> {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
return withAuth(async (auth) => {
|
|
114
|
+
const currentUser = auth.currentUser;
|
|
115
|
+
if (!currentUser) {
|
|
116
|
+
throw new Error(ERROR_MESSAGES.AUTH.NO_USER);
|
|
117
|
+
}
|
|
133
118
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
119
|
+
if (!currentUser.isAnonymous) {
|
|
120
|
+
throw new Error(ERROR_MESSAGES.AUTH.INVALID_USER);
|
|
121
|
+
}
|
|
138
122
|
|
|
139
|
-
try {
|
|
140
123
|
const credential = EmailAuthProvider.credential(email.trim(), password);
|
|
141
124
|
const userCredential = await linkWithCredential(currentUser, credential);
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
// FIX: Use toAuthErrorInfo() instead of unsafe cast
|
|
145
|
-
return { success: false, error: toAuthErrorInfo(error) };
|
|
146
|
-
}
|
|
125
|
+
return userCredential.user;
|
|
126
|
+
});
|
|
147
127
|
}
|
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
type Auth,
|
|
10
10
|
} from "firebase/auth";
|
|
11
11
|
import type { GoogleAuthConfig, GoogleAuthResult } from "./google-auth.types";
|
|
12
|
-
import { executeAuthOperation,
|
|
12
|
+
import { executeAuthOperation, type Result } from "../../../../shared/domain/utils";
|
|
13
13
|
import { ConfigurableService } from "../../../../shared/domain/utils/service-config.util";
|
|
14
|
+
import { isNewUser as checkIsNewUser } from "../../domain/utils/user-metadata.util";
|
|
15
|
+
import { convertToOAuthResult } from "./utils/auth-result-converter.util";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Google Auth Service
|
|
@@ -25,19 +27,7 @@ export class GoogleAuthService extends ConfigurableService<GoogleAuthConfig> {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
private convertToGoogleAuthResult(result: Result<{ userCredential: any; isNewUser: boolean }>): GoogleAuthResult {
|
|
28
|
-
|
|
29
|
-
if (isSuccess(result) && result.data) {
|
|
30
|
-
return {
|
|
31
|
-
success: true,
|
|
32
|
-
userCredential: result.data.userCredential,
|
|
33
|
-
isNewUser: result.data.isNewUser,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
success: false,
|
|
38
|
-
error: result.error?.message ?? "Google sign-in failed",
|
|
39
|
-
code: result.error?.code,
|
|
40
|
-
};
|
|
30
|
+
return convertToOAuthResult(result, "Google sign-in failed");
|
|
41
31
|
}
|
|
42
32
|
|
|
43
33
|
async signInWithIdToken(
|
|
@@ -48,19 +38,8 @@ export class GoogleAuthService extends ConfigurableService<GoogleAuthConfig> {
|
|
|
48
38
|
const credential = GoogleAuthProvider.credential(idToken);
|
|
49
39
|
const userCredential = await signInWithCredential(auth, credential);
|
|
50
40
|
|
|
51
|
-
// Check if user is new
|
|
52
|
-
|
|
53
|
-
const creationTime = userCredential.user.metadata.creationTime;
|
|
54
|
-
const lastSignInTime = userCredential.user.metadata.lastSignInTime;
|
|
55
|
-
|
|
56
|
-
// FIX: Add typeof validation for metadata timestamps
|
|
57
|
-
const isNewUser =
|
|
58
|
-
creationTime &&
|
|
59
|
-
lastSignInTime &&
|
|
60
|
-
typeof creationTime === 'string' &&
|
|
61
|
-
typeof lastSignInTime === 'string'
|
|
62
|
-
? new Date(creationTime).getTime() === new Date(lastSignInTime).getTime()
|
|
63
|
-
: false;
|
|
41
|
+
// Check if user is new using shared utility
|
|
42
|
+
const isNewUser = checkIsNewUser(userCredential);
|
|
64
43
|
|
|
65
44
|
return {
|
|
66
45
|
userCredential,
|