@umituz/react-native-auth 3.6.55 → 3.6.56
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/index.ts +2 -1
- package/src/infrastructure/services/AuthService.ts +3 -3
- package/src/infrastructure/services/initializeAuth.ts +8 -4
- package/src/infrastructure/utils/AuthValidation.ts +6 -3
- package/src/infrastructure/utils/UserMapper.ts +1 -1
- package/src/infrastructure/utils/userDocumentBuilder.util.ts +32 -27
- package/src/infrastructure/utils/validation/DateValidators.ts +14 -6
- package/src/infrastructure/utils/validation/NumberValidators.ts +2 -7
- package/src/presentation/hooks/useLoginForm.ts +2 -2
- package/src/presentation/hooks/useProfileUpdate.ts +3 -5
- package/src/presentation/hooks/useRegisterForm.ts +2 -2
- package/src/presentation/stores/auth.selectors.ts +2 -5
- package/src/presentation/utils/getAuthErrorMessage.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.56",
|
|
4
4
|
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -101,6 +101,7 @@ export {
|
|
|
101
101
|
validateAge,
|
|
102
102
|
} from './infrastructure/utils/validation/NumberValidators';
|
|
103
103
|
export {
|
|
104
|
+
calculateAge,
|
|
104
105
|
validateDateOfBirth,
|
|
105
106
|
validateDateRange,
|
|
106
107
|
} from './infrastructure/utils/validation/DateValidators';
|
|
@@ -222,7 +223,7 @@ export type { UserType, AuthState, AuthActions } from './presentation/stores/aut
|
|
|
222
223
|
export type { AuthListenerOptions } from './types/auth-store.types';
|
|
223
224
|
|
|
224
225
|
// UTILITIES
|
|
225
|
-
export { getAuthErrorLocalizationKey } from './presentation/utils/getAuthErrorMessage';
|
|
226
|
+
export { getAuthErrorLocalizationKey, resolveErrorMessage } from './presentation/utils/getAuthErrorMessage';
|
|
226
227
|
|
|
227
228
|
// App Service Helper (for configureAppServices)
|
|
228
229
|
export {
|
|
@@ -74,7 +74,7 @@ export class AuthService implements IAuthService {
|
|
|
74
74
|
authEventService.emitUserAuthenticated(user.uid);
|
|
75
75
|
return user;
|
|
76
76
|
} catch (error) {
|
|
77
|
-
authTracker.logOperationError("
|
|
77
|
+
authTracker.logOperationError("Sign up", error, { email: params.email });
|
|
78
78
|
throw error;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -88,7 +88,7 @@ export class AuthService implements IAuthService {
|
|
|
88
88
|
authEventService.emitUserAuthenticated(user.uid);
|
|
89
89
|
return user;
|
|
90
90
|
} catch (error) {
|
|
91
|
-
authTracker.logOperationError("
|
|
91
|
+
authTracker.logOperationError("Sign in", error, { email: params.email });
|
|
92
92
|
throw error;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -100,7 +100,7 @@ export class AuthService implements IAuthService {
|
|
|
100
100
|
await this.clearAnonymousModeIfNeeded();
|
|
101
101
|
authTracker.logOperationSuccess("Sign out");
|
|
102
102
|
} catch (error) {
|
|
103
|
-
authTracker.logOperationError("
|
|
103
|
+
authTracker.logOperationError("Sign out", error);
|
|
104
104
|
throw error;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -77,12 +77,12 @@ async function doInitializeAuth(
|
|
|
77
77
|
collectExtras: collectExtras || collectDeviceExtras,
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
let authServiceInitFailed = false;
|
|
80
81
|
try {
|
|
81
82
|
await initializeAuthService(auth, authConfig, storageProvider);
|
|
82
83
|
} catch (error) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
84
|
+
authServiceInitFailed = true;
|
|
85
|
+
console.warn("[initializeAuth] Auth service init failed, continuing:", error);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const handleAuthStateChange = createAuthStateHandler(conversionState, {
|
|
@@ -101,8 +101,12 @@ async function doInitializeAuth(
|
|
|
101
101
|
},
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
if (authServiceInitFailed) {
|
|
105
|
+
console.warn("[initializeAuth] Auth service initialization failed. Some auth features may not work.");
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
isInitialized = true;
|
|
105
|
-
return { success:
|
|
109
|
+
return { success: !authServiceInitFailed, auth };
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export function isAuthInitialized(): boolean {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
|
|
2
|
+
import type { ValidationResult } from "./validation/types";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export type { ValidationResult };
|
|
4
5
|
export interface PasswordStrengthResult extends ValidationResult { requirements: PasswordRequirements; }
|
|
5
6
|
export interface PasswordRequirements {
|
|
6
7
|
hasMinLength: boolean;
|
|
@@ -34,12 +35,14 @@ export function validatePasswordForRegister(
|
|
|
34
35
|
password: string,
|
|
35
36
|
config: PasswordConfig,
|
|
36
37
|
): PasswordStrengthResult {
|
|
38
|
+
if (!password) {
|
|
39
|
+
return { isValid: false, error: "auth.validation.passwordRequired", requirements: { hasMinLength: false } };
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
const req: PasswordRequirements = {
|
|
38
43
|
hasMinLength: password.length >= config.minLength,
|
|
39
44
|
};
|
|
40
45
|
|
|
41
|
-
if (!password) return { isValid: false, error: "auth.validation.passwordRequired", requirements: req };
|
|
42
|
-
|
|
43
46
|
if (!req.hasMinLength) return { isValid: false, error: "auth.validation.passwordTooShort", requirements: req };
|
|
44
47
|
|
|
45
48
|
return { isValid: true, requirements: req };
|
|
@@ -22,7 +22,7 @@ interface FirebaseUserLike {
|
|
|
22
22
|
/**
|
|
23
23
|
* Extract auth provider from Firebase user's providerData
|
|
24
24
|
*/
|
|
25
|
-
function extractProvider(user: FirebaseUserLike): AuthProviderType {
|
|
25
|
+
export function extractProvider(user: FirebaseUserLike): AuthProviderType {
|
|
26
26
|
if (user.isAnonymous) {
|
|
27
27
|
return "anonymous";
|
|
28
28
|
}
|
|
@@ -8,25 +8,27 @@ import type {
|
|
|
8
8
|
UserDocumentUser,
|
|
9
9
|
UserDocumentExtras,
|
|
10
10
|
} from "../../infrastructure/services/UserDocument.types";
|
|
11
|
+
import { extractProvider } from "./UserMapper";
|
|
12
|
+
import type { AuthProviderType } from "../../domain/entities/AuthUser";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Map AuthProviderType to sign-up method string
|
|
16
|
+
*/
|
|
17
|
+
const PROVIDER_TO_SIGNUP_METHOD: Record<string, string> = {
|
|
18
|
+
"google.com": "google",
|
|
19
|
+
"apple.com": "apple",
|
|
20
|
+
"password": "email",
|
|
21
|
+
"anonymous": "anonymous",
|
|
22
|
+
};
|
|
11
23
|
|
|
12
24
|
/**
|
|
13
25
|
* Gets the sign-up method from user provider data
|
|
26
|
+
* Uses extractProvider from UserMapper as single source of truth for provider detection
|
|
14
27
|
*/
|
|
15
28
|
export function getSignUpMethod(user: UserDocumentUser): string | undefined {
|
|
16
|
-
|
|
17
|
-
if (user.email
|
|
18
|
-
|
|
19
|
-
user as unknown as { providerData?: { providerId: string | null }[] }
|
|
20
|
-
).providerData;
|
|
21
|
-
if (providerData && providerData.length > 0) {
|
|
22
|
-
const providerId = providerData[0]?.providerId;
|
|
23
|
-
if (providerId === "google.com") return "google";
|
|
24
|
-
if (providerId === "apple.com") return "apple";
|
|
25
|
-
if (providerId === "password") return "email";
|
|
26
|
-
}
|
|
27
|
-
return "email";
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
29
|
+
const provider: AuthProviderType = extractProvider(user as Parameters<typeof extractProvider>[0]);
|
|
30
|
+
if (provider === "unknown") return user.email ? "email" : undefined;
|
|
31
|
+
return PROVIDER_TO_SIGNUP_METHOD[provider];
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -58,6 +60,20 @@ export function buildBaseData(
|
|
|
58
60
|
return data;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Apply anonymous-to-authenticated conversion fields to document data
|
|
65
|
+
*/
|
|
66
|
+
function applyConversionFields(data: Record<string, unknown>, extras?: UserDocumentExtras): void {
|
|
67
|
+
if (extras?.previousAnonymousUserId) {
|
|
68
|
+
data.previousAnonymousUserId = extras.previousAnonymousUserId;
|
|
69
|
+
data.convertedFromAnonymous = true;
|
|
70
|
+
data.convertedAt = serverTimestamp();
|
|
71
|
+
}
|
|
72
|
+
if (extras?.signUpMethod) {
|
|
73
|
+
data.signUpMethod = extras.signUpMethod;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
/**
|
|
62
78
|
* Builds user document data for creation
|
|
63
79
|
*/
|
|
@@ -74,13 +90,7 @@ export function buildCreateData(
|
|
|
74
90
|
lastLoginAt: serverTimestamp(),
|
|
75
91
|
};
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
createData.previousAnonymousUserId = extras.previousAnonymousUserId;
|
|
79
|
-
createData.convertedFromAnonymous = true;
|
|
80
|
-
createData.convertedAt = serverTimestamp();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (extras?.signUpMethod) createData.signUpMethod = extras.signUpMethod;
|
|
93
|
+
applyConversionFields(createData, extras);
|
|
84
94
|
|
|
85
95
|
return createData;
|
|
86
96
|
}
|
|
@@ -98,12 +108,7 @@ export function buildUpdateData(
|
|
|
98
108
|
updatedAt: serverTimestamp(),
|
|
99
109
|
};
|
|
100
110
|
|
|
101
|
-
|
|
102
|
-
updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
|
|
103
|
-
updateData.convertedFromAnonymous = true;
|
|
104
|
-
updateData.convertedAt = serverTimestamp();
|
|
105
|
-
if (extras?.signUpMethod) updateData.signUpMethod = extras.signUpMethod;
|
|
106
|
-
}
|
|
111
|
+
applyConversionFields(updateData, extras);
|
|
107
112
|
|
|
108
113
|
return updateData;
|
|
109
114
|
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { ValidationResult } from './types';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Calculate age from a birth date
|
|
5
|
+
*/
|
|
6
|
+
export function calculateAge(birthDate: Date): number {
|
|
7
|
+
const today = new Date();
|
|
8
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
9
|
+
const m = today.getMonth() - birthDate.getMonth();
|
|
10
|
+
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
11
|
+
age--;
|
|
12
|
+
}
|
|
13
|
+
return age;
|
|
14
|
+
}
|
|
15
|
+
|
|
3
16
|
/**
|
|
4
17
|
* Validate date of birth
|
|
5
18
|
*/
|
|
@@ -12,12 +25,7 @@ export const validateDateOfBirth = (
|
|
|
12
25
|
return { isValid: false, error: "Please enter a valid date" };
|
|
13
26
|
}
|
|
14
27
|
|
|
15
|
-
const
|
|
16
|
-
let age = today.getFullYear() - date.getFullYear();
|
|
17
|
-
const m = today.getMonth() - date.getMonth();
|
|
18
|
-
if (m < 0 || (m === 0 && today.getDate() < date.getDate())) {
|
|
19
|
-
age--;
|
|
20
|
-
}
|
|
28
|
+
const age = calculateAge(date);
|
|
21
29
|
|
|
22
30
|
if (age < minAge) {
|
|
23
31
|
return { isValid: false, error: errorKey || `You must be at least ${minAge} years old` };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ValidationResult } from './types';
|
|
2
|
+
import { calculateAge } from './DateValidators';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Validate numeric range
|
|
@@ -40,13 +41,7 @@ export const validateAge = (
|
|
|
40
41
|
minAge: number,
|
|
41
42
|
errorKey?: string
|
|
42
43
|
): ValidationResult => {
|
|
43
|
-
const
|
|
44
|
-
let age = today.getFullYear() - birthDate.getFullYear();
|
|
45
|
-
const m = today.getMonth() - birthDate.getMonth();
|
|
46
|
-
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
47
|
-
age--;
|
|
48
|
-
}
|
|
49
|
-
|
|
44
|
+
const age = calculateAge(birthDate);
|
|
50
45
|
const isValid = age >= minAge;
|
|
51
46
|
return {
|
|
52
47
|
isValid,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback } from "react";
|
|
2
2
|
import { useAuth } from "./useAuth";
|
|
3
|
-
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
3
|
+
import { getAuthErrorLocalizationKey, resolveErrorMessage } from "../utils/getAuthErrorMessage";
|
|
4
4
|
import { validateEmail, validatePasswordForLogin } from "../../infrastructure/utils/AuthValidation";
|
|
5
5
|
import { alertService } from "@umituz/react-native-design-system";
|
|
6
6
|
|
|
@@ -39,7 +39,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
39
39
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
40
40
|
|
|
41
41
|
const getErrorMessage = useCallback((key: string) => {
|
|
42
|
-
return translations?.errors
|
|
42
|
+
return resolveErrorMessage(key, translations?.errors);
|
|
43
43
|
}, [translations]);
|
|
44
44
|
|
|
45
45
|
const handleEmailChange = useCallback(
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Apps should use Firebase SDK directly or backend API
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { useCallback } from "react";
|
|
8
8
|
import { useAuth } from "./useAuth";
|
|
9
9
|
import type { UpdateProfileParams } from "../../domain/entities/UserProfile";
|
|
10
10
|
|
|
@@ -16,8 +16,6 @@ export interface UseProfileUpdateReturn {
|
|
|
16
16
|
|
|
17
17
|
export const useProfileUpdate = (): UseProfileUpdateReturn => {
|
|
18
18
|
const { user } = useAuth();
|
|
19
|
-
const [isUpdating] = useState(false);
|
|
20
|
-
const [error] = useState<string | null>(null);
|
|
21
19
|
|
|
22
20
|
const updateProfile = useCallback(
|
|
23
21
|
(_params: UpdateProfileParams) => {
|
|
@@ -37,8 +35,8 @@ export const useProfileUpdate = (): UseProfileUpdateReturn => {
|
|
|
37
35
|
|
|
38
36
|
return {
|
|
39
37
|
updateProfile,
|
|
40
|
-
isUpdating,
|
|
41
|
-
error,
|
|
38
|
+
isUpdating: false,
|
|
39
|
+
error: null,
|
|
42
40
|
};
|
|
43
41
|
};
|
|
44
42
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "../../infrastructure/utils/AuthValidation";
|
|
7
7
|
import { DEFAULT_PASSWORD_CONFIG } from "../../domain/value-objects/AuthConfig";
|
|
8
8
|
import { useAuth } from "./useAuth";
|
|
9
|
-
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
9
|
+
import { getAuthErrorLocalizationKey, resolveErrorMessage } from "../utils/getAuthErrorMessage";
|
|
10
10
|
import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
|
|
11
11
|
import { alertService } from "@umituz/react-native-design-system";
|
|
12
12
|
|
|
@@ -60,7 +60,7 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
|
|
|
60
60
|
}>({});
|
|
61
61
|
|
|
62
62
|
const getErrorMessage = useCallback((key: string) => {
|
|
63
|
-
return translations?.errors
|
|
63
|
+
return resolveErrorMessage(key, translations?.errors);
|
|
64
64
|
}, [translations]);
|
|
65
65
|
|
|
66
66
|
const passwordRequirements = useMemo((): PasswordRequirements => {
|
|
@@ -98,10 +98,7 @@ export const selectUserType = (state: AuthStore): UserType => {
|
|
|
98
98
|
return "none";
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
state.firebaseUser?.isAnonymous ?? state.user?.isAnonymous ?? false;
|
|
103
|
-
|
|
104
|
-
return isAnonymous ? "anonymous" : "authenticated";
|
|
101
|
+
return selectIsAnonymous(state) ? "anonymous" : "authenticated";
|
|
105
102
|
};
|
|
106
103
|
|
|
107
104
|
/**
|
|
@@ -117,5 +114,5 @@ export const selectIsAuthReady = (state: AuthStore): boolean => {
|
|
|
117
114
|
*/
|
|
118
115
|
export const selectIsRegisteredUser = (state: AuthStore): boolean => {
|
|
119
116
|
if (!state.initialized) return false;
|
|
120
|
-
return
|
|
117
|
+
return selectIsAuthenticated(state);
|
|
121
118
|
};
|
|
@@ -80,3 +80,11 @@ export function getAuthErrorLocalizationKey(error: unknown): string {
|
|
|
80
80
|
// Default to unknown error
|
|
81
81
|
return "auth.errors.unknownError";
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve an error key to a localized message using the provided error map.
|
|
86
|
+
* Falls back to the key itself if no translation is found.
|
|
87
|
+
*/
|
|
88
|
+
export function resolveErrorMessage(key: string, errors?: Record<string, string>): string {
|
|
89
|
+
return errors?.[key] || key;
|
|
90
|
+
}
|