@umituz/react-native-auth 3.4.22 → 3.4.24

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.
@@ -1,38 +1,17 @@
1
- /**
2
- * Auth Validation Utilities
3
- * Single Responsibility: Email and password validation for authentication
4
- */
5
-
6
1
  import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
7
2
  import { getAuthPackage } from "../services/AuthPackage";
8
3
 
9
- export interface ValidationResult {
10
- isValid: boolean;
11
- error?: string; // This should be a localization key
12
- }
13
-
14
- export interface PasswordStrengthResult extends ValidationResult {
15
- requirements: PasswordRequirements;
16
- }
17
-
4
+ export interface ValidationResult { isValid: boolean; error?: string; }
5
+ export interface PasswordStrengthResult extends ValidationResult { requirements: PasswordRequirements; }
18
6
  export interface PasswordRequirements {
19
- hasMinLength: boolean;
20
- hasUppercase: boolean;
21
- hasLowercase: boolean;
22
- hasNumber: boolean;
23
- hasSpecialChar: boolean;
7
+ hasMinLength: boolean; hasUppercase: boolean; hasLowercase: boolean; hasNumber: boolean; hasSpecialChar: boolean;
24
8
  }
25
-
26
9
  export interface ValidationConfig {
27
- emailRegex: RegExp;
28
- uppercaseRegex: RegExp;
29
- lowercaseRegex: RegExp;
30
- numberRegex: RegExp;
31
- specialCharRegex: RegExp;
32
- displayNameMinLength: number;
10
+ emailRegex: RegExp; uppercaseRegex: RegExp; lowercaseRegex: RegExp;
11
+ numberRegex: RegExp; specialCharRegex: RegExp; displayNameMinLength: number;
33
12
  }
34
13
 
35
- const DEFAULT_VALIDATION_CONFIG: ValidationConfig = {
14
+ const DEFAULT_VAL_CONFIG: ValidationConfig = {
36
15
  emailRegex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
37
16
  uppercaseRegex: /[A-Z]/,
38
17
  lowercaseRegex: /[a-z]/,
@@ -41,155 +20,57 @@ const DEFAULT_VALIDATION_CONFIG: ValidationConfig = {
41
20
  displayNameMinLength: 2,
42
21
  };
43
22
 
44
- function getValidationConfig(): ValidationConfig {
45
- const packageConfig = getAuthPackage()?.getConfig();
23
+ function getValConfig(): ValidationConfig {
24
+ const p = getAuthPackage()?.getConfig();
46
25
  return {
47
- emailRegex: packageConfig?.validation.emailRegex || DEFAULT_VALIDATION_CONFIG.emailRegex,
48
- uppercaseRegex: DEFAULT_VALIDATION_CONFIG.uppercaseRegex,
49
- lowercaseRegex: DEFAULT_VALIDATION_CONFIG.lowercaseRegex,
50
- numberRegex: DEFAULT_VALIDATION_CONFIG.numberRegex,
51
- specialCharRegex: DEFAULT_VALIDATION_CONFIG.specialCharRegex,
52
- displayNameMinLength: DEFAULT_VALIDATION_CONFIG.displayNameMinLength,
26
+ emailRegex: p?.validation.emailRegex || DEFAULT_VAL_CONFIG.emailRegex,
27
+ uppercaseRegex: DEFAULT_VAL_CONFIG.uppercaseRegex,
28
+ lowercaseRegex: DEFAULT_VAL_CONFIG.lowercaseRegex,
29
+ numberRegex: DEFAULT_VAL_CONFIG.numberRegex,
30
+ specialCharRegex: DEFAULT_VAL_CONFIG.specialCharRegex,
31
+ displayNameMinLength: DEFAULT_VAL_CONFIG.displayNameMinLength,
53
32
  };
54
33
  }
55
34
 
56
- /**
57
- * Validate email format
58
- */
59
35
  export function validateEmail(email: string): ValidationResult {
60
- if (!email || email.trim() === "") {
61
- return { isValid: false, error: "auth.validation.emailRequired" };
62
- }
63
-
64
- const config = getValidationConfig();
65
- if (!config.emailRegex.test(email.trim())) {
66
- return { isValid: false, error: "auth.validation.invalidEmail" };
67
- }
68
-
36
+ if (!email || email.trim() === "") return { isValid: false, error: "auth.validation.emailRequired" };
37
+ if (!getValConfig().emailRegex.test(email.trim())) return { isValid: false, error: "auth.validation.invalidEmail" };
69
38
  return { isValid: true };
70
39
  }
71
40
 
72
- /**
73
- * Validate password for login - only checks if password is provided
74
- * No strength requirements for login (existing users may have old passwords)
75
- */
76
41
  export function validatePasswordForLogin(password: string): ValidationResult {
77
- if (!password || password.length === 0) {
78
- return { isValid: false, error: "auth.validation.passwordRequired" };
79
- }
80
-
42
+ if (!password || password.length === 0) return { isValid: false, error: "auth.validation.passwordRequired" };
81
43
  return { isValid: true };
82
44
  }
83
45
 
84
- /**
85
- * Validate password strength for registration
86
- * Returns detailed requirements for UI feedback
87
- */
88
- export function validatePasswordForRegister(
89
- password: string,
90
- config: PasswordConfig
91
- ): PasswordStrengthResult {
92
- const validationConfig = getValidationConfig();
93
- const requirements: PasswordRequirements = {
46
+ export function validatePasswordForRegister(password: string, config: PasswordConfig): PasswordStrengthResult {
47
+ const v = getValConfig();
48
+ const req: PasswordRequirements = {
94
49
  hasMinLength: password.length >= config.minLength,
95
- hasUppercase: !config.requireUppercase || validationConfig.uppercaseRegex.test(password),
96
- hasLowercase: !config.requireLowercase || validationConfig.lowercaseRegex.test(password),
97
- hasNumber: !config.requireNumber || validationConfig.numberRegex.test(password),
98
- hasSpecialChar:
99
- !config.requireSpecialChar || validationConfig.specialCharRegex.test(password),
50
+ hasUppercase: !config.requireUppercase || v.uppercaseRegex.test(password),
51
+ hasLowercase: !config.requireLowercase || v.lowercaseRegex.test(password),
52
+ hasNumber: !config.requireNumber || v.numberRegex.test(password),
53
+ hasSpecialChar: !config.requireSpecialChar || v.specialCharRegex.test(password),
100
54
  };
101
55
 
102
- if (!password || password.length === 0) {
103
- return {
104
- isValid: false,
105
- error: "auth.validation.passwordRequired",
106
- requirements,
107
- };
108
- }
109
-
110
- if (!requirements.hasMinLength) {
111
- return {
112
- isValid: false,
113
- error: "auth.validation.passwordTooShort",
114
- requirements,
115
- };
116
- }
117
-
118
- if (config.requireUppercase && !validationConfig.uppercaseRegex.test(password)) {
119
- return {
120
- isValid: false,
121
- error: "auth.validation.passwordRequireUppercase",
122
- requirements,
123
- };
124
- }
56
+ if (!password) return { isValid: false, error: "auth.validation.passwordRequired", requirements: req };
57
+ if (!req.hasMinLength) return { isValid: false, error: "auth.validation.passwordTooShort", requirements: req };
58
+ if (config.requireUppercase && !req.hasUppercase) return { isValid: false, error: "auth.validation.passwordRequireUppercase", requirements: req };
59
+ if (config.requireLowercase && !req.hasLowercase) return { isValid: false, error: "auth.validation.passwordRequireLowercase", requirements: req };
60
+ if (config.requireNumber && !req.hasNumber) return { isValid: false, error: "auth.validation.passwordRequireNumber", requirements: req };
61
+ if (config.requireSpecialChar && !req.hasSpecialChar) return { isValid: false, error: "auth.validation.passwordRequireSpecialChar", requirements: req };
125
62
 
126
- if (config.requireLowercase && !validationConfig.lowercaseRegex.test(password)) {
127
- return {
128
- isValid: false,
129
- error: "auth.validation.passwordRequireLowercase",
130
- requirements,
131
- };
132
- }
133
-
134
- if (config.requireNumber && !validationConfig.numberRegex.test(password)) {
135
- return {
136
- isValid: false,
137
- error: "auth.validation.passwordRequireNumber",
138
- requirements,
139
- };
140
- }
141
-
142
- if (config.requireSpecialChar && !validationConfig.specialCharRegex.test(password)) {
143
- return {
144
- isValid: false,
145
- error: "auth.validation.passwordRequireSpecialChar",
146
- requirements,
147
- };
148
- }
149
-
150
- return { isValid: true, requirements };
63
+ return { isValid: true, requirements: req };
151
64
  }
152
65
 
153
- /**
154
- * Validate password confirmation
155
- */
156
- export function validatePasswordConfirmation(
157
- password: string,
158
- confirmPassword: string
159
- ): ValidationResult {
160
- if (!confirmPassword) {
161
- return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
162
- }
163
-
164
- if (password !== confirmPassword) {
165
- return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
166
- }
167
-
66
+ export function validatePasswordConfirmation(password: string, confirm: string): ValidationResult {
67
+ if (!confirm) return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
68
+ if (password !== confirm) return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
168
69
  return { isValid: true };
169
70
  }
170
71
 
171
- /**
172
- * Validate display name
173
- */
174
- export function validateDisplayName(
175
- displayName: string,
176
- minLength?: number
177
- ): ValidationResult {
178
- if (!displayName || displayName.trim() === "") {
179
- return { isValid: false, error: "auth.validation.nameRequired" };
180
- }
181
-
182
- const config = getValidationConfig();
183
- const actualMinLength = minLength ?? config.displayNameMinLength;
184
-
185
- if (displayName.trim().length < actualMinLength) {
186
- return {
187
- isValid: false,
188
- error: "auth.validation.nameTooShort",
189
- };
190
- }
191
-
72
+ export function validateDisplayName(name: string, minLength?: number): ValidationResult {
73
+ if (!name || name.trim() === "") return { isValid: false, error: "auth.validation.nameRequired" };
74
+ if (name.trim().length < (minLength ?? getValConfig().displayNameMinLength)) return { isValid: false, error: "auth.validation.nameTooShort" };
192
75
  return { isValid: true };
193
76
  }
194
-
195
-
@@ -0,0 +1,107 @@
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
+
12
+ /**
13
+ * Gets the sign-up method from user provider data
14
+ */
15
+ export function getSignUpMethod(user: UserDocumentUser): string | undefined {
16
+ if (user.isAnonymous) return "anonymous";
17
+ if (user.email) {
18
+ const providerData = (
19
+ user as unknown as { providerData?: { providerId: string }[] }
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;
30
+ }
31
+
32
+ /**
33
+ * Builds base user data from user object and extras
34
+ */
35
+ export function buildBaseData(
36
+ user: UserDocumentUser,
37
+ extras?: UserDocumentExtras,
38
+ ): Record<string, unknown> {
39
+ const data: Record<string, unknown> = {
40
+ displayName: user.displayName,
41
+ email: user.email,
42
+ photoURL: user.photoURL,
43
+ isAnonymous: user.isAnonymous,
44
+ };
45
+
46
+ const fields: (keyof UserDocumentExtras)[] = [
47
+ 'deviceId', 'platform', 'deviceModel', 'deviceBrand',
48
+ 'osVersion', 'appVersion', 'buildNumber', 'locale', 'timezone'
49
+ ];
50
+
51
+ fields.forEach(field => {
52
+ const val = extras?.[field];
53
+ if (val) data[field] = val;
54
+ });
55
+
56
+ return data;
57
+ }
58
+
59
+ /**
60
+ * Builds user document data for creation
61
+ */
62
+ export function buildCreateData(
63
+ baseData: Record<string, unknown>,
64
+ extraFields: Record<string, unknown> | undefined,
65
+ extras?: UserDocumentExtras,
66
+ ): Record<string, unknown> {
67
+ const createData: Record<string, unknown> = {
68
+ ...baseData,
69
+ ...extraFields,
70
+ createdAt: serverTimestamp(),
71
+ updatedAt: serverTimestamp(),
72
+ lastLoginAt: serverTimestamp(),
73
+ };
74
+
75
+ if (extras?.previousAnonymousUserId) {
76
+ createData.previousAnonymousUserId = extras.previousAnonymousUserId;
77
+ createData.convertedFromAnonymous = true;
78
+ createData.convertedAt = serverTimestamp();
79
+ }
80
+
81
+ if (extras?.signUpMethod) createData.signUpMethod = extras.signUpMethod;
82
+
83
+ return createData;
84
+ }
85
+
86
+ /**
87
+ * Builds user document data for update
88
+ */
89
+ export function buildUpdateData(
90
+ baseData: Record<string, unknown>,
91
+ extras?: UserDocumentExtras,
92
+ ): Record<string, unknown> {
93
+ const updateData: Record<string, unknown> = {
94
+ ...baseData,
95
+ lastLoginAt: serverTimestamp(),
96
+ updatedAt: serverTimestamp(),
97
+ };
98
+
99
+ if (extras?.previousAnonymousUserId) {
100
+ updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
101
+ updateData.convertedFromAnonymous = true;
102
+ updateData.convertedAt = serverTimestamp();
103
+ if (extras?.signUpMethod) updateData.signUpMethod = extras.signUpMethod;
104
+ }
105
+
106
+ return updateData;
107
+ }
@@ -4,10 +4,9 @@
4
4
  * Generic hook - reauthentication is handled via callback from calling app
5
5
  */
6
6
 
7
- /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
8
7
  import { useCallback, useState } from "react";
9
8
  import { useAuth } from "./useAuth";
10
- import { deleteCurrentUser } from "@umituz/react-native-firebase";
9
+ import { handleAccountDeletion } from "../utils/accountDeleteHandler.util";
11
10
 
12
11
  export interface UseAccountManagementOptions {
13
12
  /**
@@ -60,110 +59,7 @@ export const useAccountManagement = (
60
59
  setIsDeletingAccount(true);
61
60
 
62
61
  try {
63
- if (__DEV__) {
64
- console.log("[useAccountManagement] Calling deleteCurrentUser with autoReauthenticate: true");
65
- }
66
-
67
- const result = await deleteCurrentUser({ autoReauthenticate: true });
68
-
69
- if (__DEV__) {
70
- console.log("[useAccountManagement] First delete attempt result:", result);
71
- }
72
-
73
- if (result.success) {
74
- if (__DEV__) {
75
- console.log("[useAccountManagement] ✅ Delete successful on first attempt");
76
- }
77
- return;
78
- }
79
-
80
- // If reauthentication required
81
- if (result.error?.requiresReauth) {
82
- // Handle password-based reauth
83
- if (result.error.code === "auth/password-reauth-required" && onPasswordRequired) {
84
- if (__DEV__) {
85
- console.log("[useAccountManagement] Password reauth required, prompting user");
86
- }
87
-
88
- const password = await onPasswordRequired();
89
-
90
- if (password) {
91
- if (__DEV__) {
92
- console.log("[useAccountManagement] Password provided, retrying delete");
93
- }
94
-
95
- const retryResult = await deleteCurrentUser({
96
- autoReauthenticate: false,
97
- password,
98
- });
99
-
100
- if (__DEV__) {
101
- console.log("[useAccountManagement] Retry delete with password result:", retryResult);
102
- }
103
-
104
- if (retryResult.success) {
105
- if (__DEV__) {
106
- console.log("[useAccountManagement] ✅ Delete successful after password reauth");
107
- }
108
- return;
109
- }
110
-
111
- if (retryResult.error) {
112
- throw new Error(retryResult.error.message);
113
- }
114
- } else {
115
- if (__DEV__) {
116
- console.log("[useAccountManagement] Password prompt cancelled");
117
- }
118
- throw new Error("Password required to delete account");
119
- }
120
- }
121
-
122
- // Handle Google/Apple reauth
123
- if (onReauthRequired) {
124
- if (__DEV__) {
125
- console.log("[useAccountManagement] Reauth required, calling onReauthRequired callback");
126
- }
127
-
128
- const reauthSuccess = await onReauthRequired();
129
-
130
- if (__DEV__) {
131
- console.log("[useAccountManagement] onReauthRequired result:", reauthSuccess);
132
- }
133
-
134
- if (reauthSuccess) {
135
- if (__DEV__) {
136
- console.log("[useAccountManagement] Retrying delete after reauth");
137
- }
138
-
139
- const retryResult = await deleteCurrentUser({
140
- autoReauthenticate: false,
141
- });
142
-
143
- if (__DEV__) {
144
- console.log("[useAccountManagement] Retry delete result:", retryResult);
145
- }
146
-
147
- if (retryResult.success) {
148
- if (__DEV__) {
149
- console.log("[useAccountManagement] ✅ Delete successful after reauth");
150
- }
151
- return;
152
- }
153
-
154
- if (retryResult.error) {
155
- throw new Error(retryResult.error.message);
156
- }
157
- }
158
- }
159
- }
160
-
161
- if (result.error) {
162
- if (__DEV__) {
163
- console.log("[useAccountManagement] ❌ Delete failed:", result.error);
164
- }
165
- throw new Error(result.error.message);
166
- }
62
+ await handleAccountDeletion({ onReauthRequired, onPasswordRequired });
167
63
  } finally {
168
64
  setIsDeletingAccount(false);
169
65
  }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Account Delete Handler Utility
3
+ * Handles reauthentication flow for account deletion
4
+ */
5
+
6
+ import { deleteCurrentUser } from "@umituz/react-native-firebase";
7
+
8
+ export interface DeleteAccountCallbacks {
9
+ onReauthRequired?: () => Promise<boolean>;
10
+ onPasswordRequired?: () => Promise<string | null>;
11
+ }
12
+
13
+ export interface DeleteAccountOptions {
14
+ autoReauthenticate: boolean;
15
+ password?: string;
16
+ }
17
+
18
+ /**
19
+ * Handles account deletion with reauthentication retry logic
20
+ */
21
+ export async function handleAccountDeletion(
22
+ callbacks: DeleteAccountCallbacks
23
+ ): Promise<void> {
24
+ if (__DEV__) {
25
+ console.log("[AccountDeleteHandler] Starting deletion with auto-reauthenticate");
26
+ }
27
+
28
+ const result = await deleteCurrentUser({ autoReauthenticate: true });
29
+
30
+ if (__DEV__) {
31
+ console.log("[AccountDeleteHandler] First attempt result:", result);
32
+ }
33
+
34
+ if (result.success) {
35
+ if (__DEV__) {
36
+ console.log("[AccountDeleteHandler] Delete successful");
37
+ }
38
+ return;
39
+ }
40
+
41
+ if (result.error?.requiresReauth) {
42
+ await handleReauthentication(result, callbacks);
43
+ return;
44
+ }
45
+
46
+ if (result.error) {
47
+ if (__DEV__) {
48
+ console.log("[AccountDeleteHandler] Delete failed:", result.error);
49
+ }
50
+ throw new Error(result.error.message);
51
+ }
52
+ }
53
+
54
+ async function handleReauthentication(
55
+ initialResult: Awaited<ReturnType<typeof deleteCurrentUser>>,
56
+ callbacks: DeleteAccountCallbacks
57
+ ): Promise<void> {
58
+ const { onReauthRequired, onPasswordRequired } = callbacks;
59
+
60
+ // Handle password reauth
61
+ if (initialResult.error?.code === "auth/password-reauth-required" && onPasswordRequired) {
62
+ await retryWithPassword(onPasswordRequired);
63
+ return;
64
+ }
65
+
66
+ // Handle social auth reauth
67
+ if (onReauthRequired) {
68
+ await retryWithSocialAuth(onReauthRequired);
69
+ return;
70
+ }
71
+ }
72
+
73
+ async function retryWithPassword(onPasswordRequired: () => Promise<string | null>): Promise<void> {
74
+ if (__DEV__) {
75
+ console.log("[AccountDeleteHandler] Prompting for password");
76
+ }
77
+
78
+ const password = await onPasswordRequired();
79
+
80
+ if (!password) {
81
+ if (__DEV__) {
82
+ console.log("[AccountDeleteHandler] Password prompt cancelled");
83
+ }
84
+ throw new Error("Password required to delete account");
85
+ }
86
+
87
+ if (__DEV__) {
88
+ console.log("[AccountDeleteHandler] Retrying with password");
89
+ }
90
+
91
+ const result = await deleteCurrentUser({
92
+ autoReauthenticate: false,
93
+ password,
94
+ });
95
+
96
+ if (__DEV__) {
97
+ console.log("[AccountDeleteHandler] Password retry result:", result);
98
+ }
99
+
100
+ if (result.success) {
101
+ if (__DEV__) {
102
+ console.log("[AccountDeleteHandler] Delete successful after password reauth");
103
+ }
104
+ return;
105
+ }
106
+
107
+ if (result.error) {
108
+ throw new Error(result.error.message);
109
+ }
110
+ }
111
+
112
+ async function retryWithSocialAuth(onReauthRequired: () => Promise<boolean>): Promise<void> {
113
+ if (__DEV__) {
114
+ console.log("[AccountDeleteHandler] Requesting social auth reauth");
115
+ }
116
+
117
+ const reauthSuccess = await onReauthRequired();
118
+
119
+ if (__DEV__) {
120
+ console.log("[AccountDeleteHandler] Reauth result:", reauthSuccess);
121
+ }
122
+
123
+ if (!reauthSuccess) {
124
+ throw new Error("Reauthentication required to delete account");
125
+ }
126
+
127
+ if (__DEV__) {
128
+ console.log("[AccountDeleteHandler] Retrying deletion after reauth");
129
+ }
130
+
131
+ const result = await deleteCurrentUser({ autoReauthenticate: false });
132
+
133
+ if (__DEV__) {
134
+ console.log("[AccountDeleteHandler] Reauth retry result:", result);
135
+ }
136
+
137
+ if (result.success) {
138
+ if (__DEV__) {
139
+ console.log("[AccountDeleteHandler] Delete successful after reauth");
140
+ }
141
+ return;
142
+ }
143
+
144
+ if (result.error) {
145
+ throw new Error(result.error.message);
146
+ }
147
+ }