@umituz/react-native-auth 3.6.61 → 3.6.63

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 (40) hide show
  1. package/package.json +5 -5
  2. package/src/application/ports/IAuthRepository.ts +3 -3
  3. package/src/index.ts +5 -18
  4. package/src/infrastructure/providers/FirebaseAuthProvider.ts +6 -2
  5. package/src/infrastructure/repositories/AuthRepository.ts +3 -3
  6. package/src/infrastructure/services/AuthEventService.ts +4 -0
  7. package/src/infrastructure/services/AuthService.ts +14 -36
  8. package/src/infrastructure/services/initializeAuth.ts +0 -15
  9. package/src/infrastructure/utils/AuthValidation.ts +5 -2
  10. package/src/infrastructure/utils/UserMapper.ts +9 -6
  11. package/src/infrastructure/utils/authStateHandler.ts +0 -7
  12. package/src/infrastructure/utils/validation/sanitization.ts +2 -89
  13. package/src/init/createAuthInitModule.ts +0 -15
  14. package/src/presentation/hooks/mutations/useAuthMutations.ts +3 -3
  15. package/src/presentation/hooks/useAuthBottomSheet.ts +10 -1
  16. package/src/presentation/hooks/useGoogleAuth.ts +43 -11
  17. package/src/presentation/stores/auth.selectors.ts +12 -6
  18. package/src/presentation/stores/authStore.ts +6 -9
  19. package/src/presentation/stores/initializeAuthListener.ts +44 -7
  20. package/src/application/ports/IAuthService.ts +0 -60
  21. package/src/domain/utils/migration.ts +0 -44
  22. package/src/infrastructure/adapters/UIProviderAdapter.ts +0 -43
  23. package/src/infrastructure/services/UserDocument.types.ts +0 -60
  24. package/src/infrastructure/services/UserDocumentService.ts +0 -86
  25. package/src/infrastructure/services/app-service-helpers.ts +0 -35
  26. package/src/infrastructure/types/UI.types.ts +0 -11
  27. package/src/infrastructure/utils/auth-tracker.util.ts +0 -23
  28. package/src/infrastructure/utils/userDocumentBuilder.util.ts +0 -114
  29. package/src/infrastructure/utils/validation/BaseValidators.ts +0 -35
  30. package/src/infrastructure/utils/validation/CollectionValidators.ts +0 -56
  31. package/src/infrastructure/utils/validation/DateValidators.ts +0 -71
  32. package/src/infrastructure/utils/validation/FormValidators.ts +0 -22
  33. package/src/infrastructure/utils/validation/NumberValidators.ts +0 -50
  34. package/src/infrastructure/utils/validation/StringValidators.ts +0 -55
  35. package/src/infrastructure/utils/validation/types.ts +0 -15
  36. package/src/presentation/screens/change-password/ChangePasswordScreen.tsx +0 -179
  37. package/src/presentation/screens/change-password/ChangePasswordScreen.types.ts +0 -77
  38. package/src/presentation/screens/change-password/RequirementItem.tsx +0 -47
  39. package/src/presentation/screens/change-password/index.ts +0 -6
  40. package/src/types/translations.types.ts +0 -89
@@ -1,114 +0,0 @@
1
- /**
2
- * User Document Builder Utility
3
- * Builds user document data for Firestore
4
- */
5
-
6
- import { serverTimestamp } from "firebase/firestore";
7
- import type {
8
- UserDocumentUser,
9
- UserDocumentExtras,
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
- };
23
-
24
- /**
25
- * Gets the sign-up method from user provider data
26
- * Uses extractProvider from UserMapper as single source of truth for provider detection
27
- */
28
- export function getSignUpMethod(user: UserDocumentUser): string | 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];
32
- }
33
-
34
- /**
35
- * Builds base user data from user object and extras
36
- */
37
- export function buildBaseData(
38
- user: UserDocumentUser,
39
- extras?: UserDocumentExtras,
40
- ): Record<string, unknown> {
41
- const data: Record<string, unknown> = {
42
- displayName: user.displayName,
43
- email: user.email,
44
- photoURL: user.photoURL,
45
- isAnonymous: user.isAnonymous,
46
- };
47
-
48
- if (extras) {
49
- const internalFields = ["signUpMethod", "previousAnonymousUserId"];
50
- Object.keys(extras).forEach((key) => {
51
- if (!internalFields.includes(key)) {
52
- const val = extras[key];
53
- if (val !== undefined) {
54
- data[key] = val;
55
- }
56
- }
57
- });
58
- }
59
-
60
- return data;
61
- }
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
-
77
- /**
78
- * Builds user document data for creation
79
- */
80
- export function buildCreateData(
81
- baseData: Record<string, unknown>,
82
- extraFields: Record<string, unknown> | undefined,
83
- extras?: UserDocumentExtras,
84
- ): Record<string, unknown> {
85
- const createData: Record<string, unknown> = {
86
- ...baseData,
87
- ...extraFields,
88
- createdAt: serverTimestamp(),
89
- updatedAt: serverTimestamp(),
90
- lastLoginAt: serverTimestamp(),
91
- };
92
-
93
- applyConversionFields(createData, extras);
94
-
95
- return createData;
96
- }
97
-
98
- /**
99
- * Builds user document data for update
100
- */
101
- export function buildUpdateData(
102
- baseData: Record<string, unknown>,
103
- extras?: UserDocumentExtras,
104
- ): Record<string, unknown> {
105
- const updateData: Record<string, unknown> = {
106
- ...baseData,
107
- lastLoginAt: serverTimestamp(),
108
- updatedAt: serverTimestamp(),
109
- };
110
-
111
- applyConversionFields(updateData, extras);
112
-
113
- return updateData;
114
- }
@@ -1,35 +0,0 @@
1
- import { ValidationResult } from './types';
2
-
3
- /**
4
- * Validate required field (not null, undefined, or empty string)
5
- */
6
- export const validateRequired = (
7
- value: unknown,
8
- fieldName: string = "Field",
9
- errorKey?: string
10
- ): ValidationResult => {
11
- const isValid = value !== undefined && value !== null && (typeof value === 'string' ? value.trim() !== "" : true);
12
-
13
- return {
14
- isValid,
15
- error: isValid ? undefined : (errorKey || `${fieldName} is required`)
16
- };
17
- };
18
-
19
- /**
20
- * Validate pattern (regex)
21
- */
22
- export const validatePattern = (
23
- value: string,
24
- pattern: RegExp,
25
- fieldName: string = "Field",
26
- errorKey?: string
27
- ): ValidationResult => {
28
- if (!value) return { isValid: true }; // Use validateRequired for mandatory check
29
-
30
- const isValid = pattern.test(value);
31
- return {
32
- isValid,
33
- error: isValid ? undefined : (errorKey || `${fieldName} format is invalid`)
34
- };
35
- };
@@ -1,56 +0,0 @@
1
- import { ValidationResult } from './types';
2
-
3
- /**
4
- * Custom enum validation
5
- * Generic validator for enum-like values with custom options
6
- */
7
- export const validateEnum = (
8
- value: string,
9
- validOptions: readonly string[],
10
- fieldName: string = "Field",
11
- errorKey?: string
12
- ): ValidationResult => {
13
- if (!value || value.trim() === "") {
14
- return { isValid: false, error: `${fieldName} is required` };
15
- }
16
-
17
- const isValid = validOptions.includes(value.toLowerCase());
18
- return {
19
- isValid,
20
- error: isValid ? undefined : (errorKey || `Please select a valid ${fieldName.toLowerCase()}`)
21
- };
22
- };
23
-
24
- /**
25
- * Tags validation
26
- * Generic validator for array of strings with constraints
27
- */
28
- export const validateTags = (
29
- tags: string[],
30
- maxTags: number = 10,
31
- maxTagLength: number = 20,
32
- errorKey?: string
33
- ): ValidationResult => {
34
- if (!Array.isArray(tags)) {
35
- return { isValid: false, error: errorKey || "Tags must be an array" };
36
- }
37
-
38
- if (tags.length > maxTags) {
39
- return { isValid: false, error: errorKey || `Maximum ${maxTags} tags allowed` };
40
- }
41
-
42
- for (const tag of tags) {
43
- if (typeof tag !== "string" || tag.trim().length === 0) {
44
- return { isValid: false, error: errorKey || "All tags must be non-empty strings" };
45
- }
46
-
47
- if (tag.trim().length > maxTagLength) {
48
- return {
49
- isValid: false,
50
- error: errorKey || `Each tag must be at most ${maxTagLength} characters`,
51
- };
52
- }
53
- }
54
-
55
- return { isValid: true };
56
- };
@@ -1,71 +0,0 @@
1
- import { ValidationResult } from './types';
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
-
16
- /**
17
- * Validate date of birth
18
- */
19
- export const validateDateOfBirth = (
20
- date: Date,
21
- minAge: number = 13,
22
- errorKey?: string
23
- ): ValidationResult => {
24
- if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
25
- return { isValid: false, error: "Please enter a valid date" };
26
- }
27
-
28
- const age = calculateAge(date);
29
-
30
- if (age < minAge) {
31
- return { isValid: false, error: errorKey || `You must be at least ${minAge} years old` };
32
- }
33
-
34
- if (age > 120) {
35
- return { isValid: false, error: "Please enter a valid date of birth" };
36
- }
37
-
38
- return { isValid: true };
39
- };
40
-
41
- /**
42
- * Date range validation
43
- * Validates date is within a specific range
44
- */
45
- export const validateDateRange = (
46
- date: Date,
47
- minDate?: Date,
48
- maxDate?: Date,
49
- fieldName: string = "Date",
50
- errorKey?: string
51
- ): ValidationResult => {
52
- if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
53
- return { isValid: false, error: errorKey || `Please enter a valid ${fieldName.toLowerCase()}` };
54
- }
55
-
56
- if (minDate && date < minDate) {
57
- return {
58
- isValid: false,
59
- error: errorKey || `${fieldName} cannot be before ${minDate.toLocaleDateString()}`,
60
- };
61
- }
62
-
63
- if (maxDate && date > maxDate) {
64
- return {
65
- isValid: false,
66
- error: errorKey || `${fieldName} cannot be after ${maxDate.toLocaleDateString()}`,
67
- };
68
- }
69
-
70
- return { isValid: true };
71
- };
@@ -1,22 +0,0 @@
1
- import { ValidationResult } from './types';
2
-
3
- /**
4
- * Batch validation helper
5
- * Validates multiple fields and returns all errors in a record
6
- */
7
- export const batchValidate = (
8
- validations: Array<{ field: string; validator: () => ValidationResult }>,
9
- ): { isValid: boolean; errors: Record<string, string> } => {
10
- const errors: Record<string, string> = {};
11
- let isValid = true;
12
-
13
- validations.forEach(({ field, validator }) => {
14
- const result = validator();
15
- if (!result.isValid && result.error) {
16
- errors[field] = result.error;
17
- isValid = false;
18
- }
19
- });
20
-
21
- return { isValid, errors };
22
- };
@@ -1,50 +0,0 @@
1
- import { ValidationResult } from './types';
2
- import { calculateAge } from './DateValidators';
3
-
4
- /**
5
- * Validate numeric range
6
- */
7
- export const validateNumberRange = (
8
- value: number,
9
- min: number,
10
- max: number,
11
- fieldName: string = "Number",
12
- errorKey?: string
13
- ): ValidationResult => {
14
- const isValid = value >= min && value <= max;
15
- return {
16
- isValid,
17
- error: isValid ? undefined : (errorKey || `${fieldName} must be between ${min} and ${max}`)
18
- };
19
- };
20
-
21
- /**
22
- * Validate positive number
23
- */
24
- export const validatePositiveNumber = (
25
- value: number,
26
- fieldName: string = "Number",
27
- errorKey?: string
28
- ): ValidationResult => {
29
- const isValid = value > 0;
30
- return {
31
- isValid,
32
- error: isValid ? undefined : (errorKey || `${fieldName} must be a positive number`)
33
- };
34
- };
35
-
36
- /**
37
- * Validate age based on birth date
38
- */
39
- export const validateAge = (
40
- birthDate: Date,
41
- minAge: number,
42
- errorKey?: string
43
- ): ValidationResult => {
44
- const age = calculateAge(birthDate);
45
- const isValid = age >= minAge;
46
- return {
47
- isValid,
48
- error: isValid ? undefined : (errorKey || `You must be at least ${minAge} years old`)
49
- };
50
- };
@@ -1,55 +0,0 @@
1
- import { ValidationResult } from './types';
2
-
3
- /**
4
- * Validate min length of a string
5
- */
6
- export const validateMinLength = (
7
- value: string,
8
- minLength: number,
9
- fieldName: string = "Field",
10
- errorKey?: string
11
- ): ValidationResult => {
12
- if (!value) return { isValid: false, error: `${fieldName} is required` };
13
-
14
- const isValid = value.trim().length >= minLength;
15
- return {
16
- isValid,
17
- error: isValid ? undefined : (errorKey || `${fieldName} must be at least ${minLength} characters`)
18
- };
19
- };
20
-
21
- /**
22
- * Validate max length of a string
23
- */
24
- export const validateMaxLength = (
25
- value: string,
26
- maxLength: number,
27
- fieldName: string = "Field",
28
- errorKey?: string
29
- ): ValidationResult => {
30
- if (!value) return { isValid: true };
31
-
32
- const isValid = value.trim().length <= maxLength;
33
- return {
34
- isValid,
35
- error: isValid ? undefined : (errorKey || `${fieldName} must be at most ${maxLength} characters`)
36
- };
37
- };
38
-
39
- /**
40
- * Validate phone number (E.164 format)
41
- */
42
- export const validatePhone = (
43
- phone: string,
44
- errorKey?: string
45
- ): ValidationResult => {
46
- if (!phone) return { isValid: true };
47
-
48
- const phoneRegex = /^\+[1-9]\d{1,14}$/;
49
- const isValid = phoneRegex.test(phone);
50
-
51
- return {
52
- isValid,
53
- error: isValid ? undefined : (errorKey || "Please enter a valid phone number")
54
- };
55
- };
@@ -1,15 +0,0 @@
1
- /**
2
- * Validation Result Interface
3
- * Represents the outcome of a validation operation
4
- */
5
- export interface ValidationResult {
6
- /**
7
- * Whether the validation passed
8
- */
9
- isValid: boolean;
10
-
11
- /**
12
- * Error message or translation key if validation failed
13
- */
14
- error?: string;
15
- }
@@ -1,179 +0,0 @@
1
- /**
2
- * Change Password Screen
3
- */
4
-
5
- import React, { useState } from "react";
6
- import { View, StyleSheet, Alert } from "react-native";
7
- import {
8
- ScreenLayout,
9
- ScreenHeader,
10
- AtomicInput,
11
- AtomicButton,
12
- AtomicText,
13
- useAppDesignTokens,
14
- } from "@umituz/react-native-design-system";
15
- import {
16
- updateUserPassword,
17
- reauthenticateWithPassword,
18
- getCurrentUserFromGlobal,
19
- } from "@umituz/react-native-firebase";
20
- import { RequirementItem } from "./RequirementItem";
21
- import { validatePassword } from "./ChangePasswordScreen.types";
22
- import type { ChangePasswordScreenProps } from "./ChangePasswordScreen.types";
23
-
24
- export type { ChangePasswordTranslations, ChangePasswordScreenProps } from "./ChangePasswordScreen.types";
25
-
26
- export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
27
- translations,
28
- onSuccess,
29
- onCancel,
30
- }) => {
31
- const tokens = useAppDesignTokens();
32
-
33
- const [currentPassword, setCurrentPassword] = useState("");
34
- const [newPassword, setNewPassword] = useState("");
35
- const [confirmPassword, setConfirmPassword] = useState("");
36
- const [loading, setLoading] = useState(false);
37
- const [error, setError] = useState<string | null>(null);
38
-
39
- const validation = validatePassword(newPassword, confirmPassword, currentPassword);
40
-
41
- const handleChangePassword = async () => {
42
- if (!validation.isValid) {
43
- Alert.alert("Error", translations.fillAllFields);
44
- return;
45
- }
46
-
47
- const user = getCurrentUserFromGlobal();
48
- if (!user) {
49
- setError(translations.unauthorized);
50
- return;
51
- }
52
-
53
- setLoading(true);
54
- setError(null);
55
-
56
- try {
57
- const reauthResult = await reauthenticateWithPassword(user, currentPassword);
58
- if (!reauthResult.success) {
59
- setError(reauthResult.error?.message || translations.signInFailed);
60
- setLoading(false);
61
- return;
62
- }
63
-
64
- const updateResult = await updateUserPassword(user, newPassword);
65
- if (updateResult.success) {
66
- Alert.alert("Success", translations.success, [
67
- { text: "OK", onPress: onSuccess }
68
- ]);
69
- } else {
70
- setError(updateResult.error?.message || translations.error);
71
- }
72
- } catch (e: unknown) {
73
- const errorMessage = e instanceof Error ? e.message : translations.error;
74
- setError(errorMessage);
75
- } finally {
76
- setLoading(false);
77
- }
78
- };
79
-
80
- return (
81
- <ScreenLayout
82
- scrollable
83
- header={<ScreenHeader title={translations.title} />}
84
- backgroundColor={tokens.colors.backgroundPrimary}
85
- edges={["bottom"]}
86
- >
87
- <View style={styles.content}>
88
- <AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 24 }}>
89
- {translations.description}
90
- </AtomicText>
91
-
92
- <View style={styles.form}>
93
- <AtomicInput
94
- label={translations.currentPassword}
95
- placeholder={translations.enterCurrentPassword}
96
- value={currentPassword}
97
- onChangeText={setCurrentPassword}
98
- secureTextEntry
99
- showPasswordToggle
100
- variant="filled"
101
- />
102
-
103
- <AtomicInput
104
- label={translations.newPassword}
105
- placeholder={translations.enterNewPassword}
106
- value={newPassword}
107
- onChangeText={setNewPassword}
108
- secureTextEntry
109
- showPasswordToggle
110
- variant="filled"
111
- />
112
-
113
- <AtomicInput
114
- label={translations.confirmPassword}
115
- placeholder={translations.enterConfirmPassword}
116
- value={confirmPassword}
117
- onChangeText={setConfirmPassword}
118
- secureTextEntry
119
- showPasswordToggle
120
- variant="filled"
121
- />
122
-
123
- <View style={[styles.requirementsContainer, { backgroundColor: tokens.colors.surfaceSecondary }]}>
124
- <AtomicText type="labelMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 8 }}>
125
- {translations.requirements}
126
- </AtomicText>
127
- <RequirementItem label={translations.minLength} met={validation.isLengthValid} />
128
- <RequirementItem label={translations.uppercase} met={validation.hasUppercase} />
129
- <RequirementItem label={translations.lowercase} met={validation.hasLowercase} />
130
- <RequirementItem label={translations.number} met={validation.hasNumber} />
131
- <RequirementItem label={translations.specialChar} met={validation.hasSpecialChar} />
132
- <RequirementItem label={translations.passwordsMatch} met={validation.passwordsMatch} />
133
- </View>
134
-
135
- {error && (
136
- <AtomicText style={{ color: tokens.colors.error, marginTop: 16 }}>
137
- {error}
138
- </AtomicText>
139
- )}
140
-
141
- <View style={styles.actions}>
142
- <AtomicButton
143
- title={translations.cancel}
144
- onPress={onCancel || (() => {})}
145
- variant="outline"
146
- style={{ flex: 1 }}
147
- />
148
- <AtomicButton
149
- title={loading ? translations.changing : translations.changePassword}
150
- onPress={() => { void handleChangePassword(); }}
151
- loading={loading}
152
- disabled={!validation.isValid || loading}
153
- style={{ flex: 1 }}
154
- />
155
- </View>
156
- </View>
157
- </View>
158
- </ScreenLayout>
159
- );
160
- };
161
-
162
- const styles = StyleSheet.create({
163
- content: {
164
- padding: 16,
165
- },
166
- form: {
167
- gap: 16,
168
- },
169
- requirementsContainer: {
170
- marginTop: 8,
171
- padding: 12,
172
- borderRadius: 8,
173
- },
174
- actions: {
175
- flexDirection: "row",
176
- gap: 12,
177
- marginTop: 24,
178
- },
179
- });
@@ -1,77 +0,0 @@
1
- /**
2
- * Change Password Screen Types
3
- */
4
-
5
- export interface ChangePasswordTranslations {
6
- title: string;
7
- description: string;
8
- currentPassword: string;
9
- enterCurrentPassword: string;
10
- newPassword: string;
11
- enterNewPassword: string;
12
- confirmPassword: string;
13
- enterConfirmPassword: string;
14
- requirements: string;
15
- minLength: string;
16
- uppercase: string;
17
- lowercase: string;
18
- number: string;
19
- specialChar: string;
20
- passwordsMatch: string;
21
- changePassword: string;
22
- changing: string;
23
- cancel: string;
24
- success: string;
25
- error: string;
26
- fillAllFields: string;
27
- unauthorized: string;
28
- signInFailed: string;
29
- }
30
-
31
- export interface ChangePasswordScreenProps {
32
- translations: ChangePasswordTranslations;
33
- onSuccess?: () => void;
34
- onCancel?: () => void;
35
- }
36
-
37
- export interface PasswordValidation {
38
- isLengthValid: boolean;
39
- hasUppercase: boolean;
40
- hasLowercase: boolean;
41
- hasNumber: boolean;
42
- hasSpecialChar: boolean;
43
- passwordsMatch: boolean;
44
- isValid: boolean;
45
- }
46
-
47
- export function validatePassword(
48
- newPassword: string,
49
- confirmPassword: string,
50
- currentPassword: string
51
- ): PasswordValidation {
52
- const isLengthValid = newPassword.length >= 8;
53
- const hasUppercase = /[A-Z]/.test(newPassword);
54
- const hasLowercase = /[a-z]/.test(newPassword);
55
- const hasNumber = /[0-9]/.test(newPassword);
56
- const hasSpecialChar = /[!@#$%^&*]/.test(newPassword);
57
- const passwordsMatch = newPassword === confirmPassword && newPassword !== "";
58
-
59
- const isValid =
60
- isLengthValid &&
61
- hasUppercase &&
62
- hasLowercase &&
63
- hasNumber &&
64
- hasSpecialChar &&
65
- passwordsMatch &&
66
- currentPassword.length > 0;
67
-
68
- return {
69
- isLengthValid,
70
- hasUppercase,
71
- hasLowercase,
72
- hasNumber,
73
- hasSpecialChar,
74
- passwordsMatch,
75
- isValid,
76
- };
77
- }