@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.
- package/package.json +1 -1
- package/src/__tests__/services/AnonymousModeService.test.ts +22 -153
- package/src/__tests__/services/AuthCoreInitialization.test.ts +45 -0
- package/src/__tests__/services/AuthCoreOperations.test.ts +71 -0
- package/src/__tests__/services/AuthPackage.test.ts +24 -171
- package/src/__tests__/utils/AuthDisplayNameValidation.test.ts +44 -0
- package/src/__tests__/utils/AuthEmailValidation.test.ts +38 -0
- package/src/__tests__/utils/AuthPasswordValidation.test.ts +90 -0
- package/src/index.ts +11 -95
- package/src/infrastructure/services/UserDocument.types.ts +44 -0
- package/src/infrastructure/services/UserDocumentService.ts +28 -179
- package/src/infrastructure/utils/AuthValidation.ts +37 -156
- package/src/infrastructure/utils/userDocumentBuilder.util.ts +107 -0
- package/src/presentation/hooks/useAccountManagement.ts +2 -106
- package/src/presentation/utils/accountDeleteHandler.util.ts +147 -0
- package/src/__tests__/services/AuthCoreService.test.ts +0 -247
- package/src/__tests__/utils/AuthValidation.test.ts +0 -270
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
45
|
-
const
|
|
23
|
+
function getValConfig(): ValidationConfig {
|
|
24
|
+
const p = getAuthPackage()?.getConfig();
|
|
46
25
|
return {
|
|
47
|
-
emailRegex:
|
|
48
|
-
uppercaseRegex:
|
|
49
|
-
lowercaseRegex:
|
|
50
|
-
numberRegex:
|
|
51
|
-
specialCharRegex:
|
|
52
|
-
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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 ||
|
|
96
|
-
hasLowercase: !config.requireLowercase ||
|
|
97
|
-
hasNumber: !config.requireNumber ||
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
+
}
|