@umituz/react-native-auth 3.6.92 → 3.7.1
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 +9 -7
- package/src/application/ports/IAuthRepository.ts +1 -1
- package/src/index.ts +1 -2
- package/src/infrastructure/repositories/AuthRepository.ts +57 -17
- package/src/infrastructure/services/AnonymousModeService.ts +1 -14
- package/src/infrastructure/services/AuthService.ts +5 -33
- package/src/infrastructure/services/initializeAuth.ts +11 -12
- package/src/infrastructure/utils/authStateHandler.ts +1 -1
- package/src/presentation/hooks/mutations/useAuthMutations.ts +1 -1
- package/src/presentation/hooks/useAccountManagement.ts +1 -1
- package/src/application/ports/IAuthProvider.ts +0 -78
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +0 -117
- package/src/infrastructure/services/UserDocument.types.ts +0 -60
- package/src/infrastructure/services/UserDocumentService.ts +0 -85
- package/src/infrastructure/utils/userDocumentBuilder.util.ts +0 -110
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.1",
|
|
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",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"url": "git+https://github.com/umituz/react-native-auth.git"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"expo": ">=54.0.0",
|
|
36
35
|
"@react-navigation/native": ">=6.0.0",
|
|
37
36
|
"@react-navigation/stack": ">=6.0.0",
|
|
38
37
|
"@tanstack/react-query": ">=5.0.0",
|
|
38
|
+
"expo": ">=54.0.0",
|
|
39
39
|
"expo-auth-session": ">=5.0.0",
|
|
40
40
|
"expo-web-browser": ">=12.0.0",
|
|
41
41
|
"firebase": ">=11.0.0",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@expo/vector-icons": "^15.0.3",
|
|
51
51
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
52
|
+
"@react-native-community/datetimepicker": "^8.2.0",
|
|
52
53
|
"@react-native-community/slider": "^5.1.1",
|
|
53
54
|
"@react-navigation/bottom-tabs": "^7.9.0",
|
|
54
55
|
"@react-navigation/native": "^7.1.26",
|
|
@@ -61,7 +62,6 @@
|
|
|
61
62
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
62
63
|
"@typescript-eslint/parser": "^7.0.0",
|
|
63
64
|
"@umituz/react-native-design-system": "latest",
|
|
64
|
-
"@umituz/react-native-firebase": "latest",
|
|
65
65
|
"@umituz/react-native-localization": "latest",
|
|
66
66
|
"eslint": "^8.57.0",
|
|
67
67
|
"expo-apple-authentication": "^6.0.0",
|
|
@@ -92,10 +92,9 @@
|
|
|
92
92
|
"react-native-gesture-handler": "^2.0.0",
|
|
93
93
|
"react-native-safe-area-context": "^5.6.2",
|
|
94
94
|
"react-native-svg": "^15.15.1",
|
|
95
|
+
"rn-emoji-keyboard": "^1.7.0",
|
|
95
96
|
"typescript": "^5.3.0",
|
|
96
|
-
"zustand": "^5.0.0"
|
|
97
|
-
"@react-native-community/datetimepicker": "^8.2.0",
|
|
98
|
-
"rn-emoji-keyboard": "^1.7.0"
|
|
97
|
+
"zustand": "^5.0.0"
|
|
99
98
|
},
|
|
100
99
|
"publishConfig": {
|
|
101
100
|
"access": "public"
|
|
@@ -104,5 +103,8 @@
|
|
|
104
103
|
"src",
|
|
105
104
|
"README.md",
|
|
106
105
|
"LICENSE"
|
|
107
|
-
]
|
|
106
|
+
],
|
|
107
|
+
"dependencies": {
|
|
108
|
+
"@umituz/react-native-firebase": "^1.13.154"
|
|
109
|
+
}
|
|
108
110
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
3
|
-
import type { AuthCredentials, SignUpCredentials } from "
|
|
3
|
+
import type { AuthCredentials, SignUpCredentials } from "../../infrastructure/repositories/AuthRepository";
|
|
4
4
|
|
|
5
5
|
export interface IAuthRepository {
|
|
6
6
|
signUp(params: SignUpCredentials): Promise<AuthUser>;
|
package/src/index.ts
CHANGED
|
@@ -19,13 +19,12 @@ export { DEFAULT_AUTH_CONFIG, DEFAULT_PASSWORD_CONFIG, DEFAULT_SOCIAL_CONFIG } f
|
|
|
19
19
|
// APPLICATION LAYER
|
|
20
20
|
// =============================================================================
|
|
21
21
|
|
|
22
|
-
export type {
|
|
22
|
+
export type { AuthCredentials, SignUpCredentials } from './infrastructure/repositories/AuthRepository';
|
|
23
23
|
|
|
24
24
|
// =============================================================================
|
|
25
25
|
// INFRASTRUCTURE LAYER
|
|
26
26
|
// =============================================================================
|
|
27
27
|
|
|
28
|
-
export { FirebaseAuthProvider } from './infrastructure/providers/FirebaseAuthProvider';
|
|
29
28
|
export { AuthService, initializeAuthService, getAuthService, resetAuthService } from './infrastructure/services/AuthService';
|
|
30
29
|
export type { IStorageProvider } from './infrastructure/types/Storage.types';
|
|
31
30
|
export { createStorageProvider, StorageProviderAdapter } from './infrastructure/adapters/StorageProviderAdapter';
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Repository
|
|
3
|
-
*
|
|
4
|
-
* Handles data access for authentication
|
|
3
|
+
* Handles authentication with validation
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import type { IAuthRepository } from "../../application/ports/IAuthRepository";
|
|
8
|
-
import type { IAuthProvider } from "../../application/ports/IAuthProvider";
|
|
9
7
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
10
|
-
import type {
|
|
8
|
+
import type { User } from "firebase/auth";
|
|
9
|
+
import {
|
|
10
|
+
signInWithEmail,
|
|
11
|
+
signUpWithEmail,
|
|
12
|
+
signOut as firebaseSignOut,
|
|
13
|
+
getCurrentUserFromGlobal,
|
|
14
|
+
setupAuthListener,
|
|
15
|
+
type EmailCredentials,
|
|
16
|
+
} from "@umituz/react-native-firebase";
|
|
11
17
|
import {
|
|
12
18
|
AuthValidationError,
|
|
13
19
|
AuthWeakPasswordError,
|
|
14
20
|
AuthInvalidEmailError,
|
|
21
|
+
AuthError,
|
|
15
22
|
} from "../../domain/errors/AuthError";
|
|
16
23
|
import {
|
|
17
24
|
validateEmail,
|
|
@@ -21,13 +28,23 @@ import {
|
|
|
21
28
|
} from "../utils/AuthValidation";
|
|
22
29
|
import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
|
|
23
30
|
import { sanitizeEmail, sanitizeName, sanitizePassword } from "../utils/validation/sanitization";
|
|
31
|
+
import { mapToAuthUser } from "../utils/UserMapper";
|
|
32
|
+
|
|
33
|
+
export interface SignUpCredentials {
|
|
34
|
+
email: string;
|
|
35
|
+
password: string;
|
|
36
|
+
displayName?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AuthCredentials {
|
|
40
|
+
email: string;
|
|
41
|
+
password: string;
|
|
42
|
+
}
|
|
24
43
|
|
|
25
44
|
export class AuthRepository implements IAuthRepository {
|
|
26
|
-
private provider: IAuthProvider;
|
|
27
45
|
private config: AuthConfig;
|
|
28
46
|
|
|
29
|
-
constructor(
|
|
30
|
-
this.provider = provider;
|
|
47
|
+
constructor(config: AuthConfig) {
|
|
31
48
|
this.config = config;
|
|
32
49
|
}
|
|
33
50
|
|
|
@@ -36,13 +53,11 @@ export class AuthRepository implements IAuthRepository {
|
|
|
36
53
|
const password = sanitizePassword(params.password);
|
|
37
54
|
const displayName = params.displayName ? sanitizeName(params.displayName) : undefined;
|
|
38
55
|
|
|
39
|
-
// Validate email
|
|
40
56
|
const emailResult = validateEmail(email);
|
|
41
57
|
if (!emailResult.isValid) {
|
|
42
58
|
throw new AuthInvalidEmailError(emailResult.error);
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
// Validate display name if provided
|
|
46
61
|
if (displayName) {
|
|
47
62
|
const nameResult = validateDisplayName(displayName);
|
|
48
63
|
if (!nameResult.isValid) {
|
|
@@ -50,43 +65,68 @@ export class AuthRepository implements IAuthRepository {
|
|
|
50
65
|
}
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
// Validate password strength for registration
|
|
54
68
|
const passwordResult = validatePasswordForRegister(password, this.config.password);
|
|
55
69
|
if (!passwordResult.isValid) {
|
|
56
70
|
throw new AuthWeakPasswordError(passwordResult.error);
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
const result = await signUpWithEmail({ email, password, displayName });
|
|
74
|
+
if (!result.success || !result.data) {
|
|
75
|
+
throw new AuthError(result.error?.message || "Sign up failed");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const authUser = mapToAuthUser(result.data);
|
|
79
|
+
if (!authUser) {
|
|
80
|
+
throw new AuthError("Failed to map user");
|
|
81
|
+
}
|
|
82
|
+
return authUser;
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
async signIn(params: AuthCredentials): Promise<AuthUser> {
|
|
63
86
|
const email = sanitizeEmail(params.email);
|
|
64
87
|
const password = sanitizePassword(params.password);
|
|
65
88
|
|
|
66
|
-
// Validate email format
|
|
67
89
|
const emailResult = validateEmail(email);
|
|
68
90
|
if (!emailResult.isValid) {
|
|
69
91
|
throw new AuthInvalidEmailError(emailResult.error);
|
|
70
92
|
}
|
|
71
93
|
|
|
72
|
-
// For login, only check if password is provided (no strength requirements)
|
|
73
94
|
const passwordResult = validatePasswordForLogin(password);
|
|
74
95
|
if (!passwordResult.isValid) {
|
|
75
96
|
throw new AuthValidationError(passwordResult.error || "Password is required", "password");
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
|
|
99
|
+
const result = await signInWithEmail(email, password);
|
|
100
|
+
if (!result.success || !result.data) {
|
|
101
|
+
throw new AuthError(result.error?.message || "Sign in failed");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const authUser = mapToAuthUser(result.data);
|
|
105
|
+
if (!authUser) {
|
|
106
|
+
throw new AuthError("Failed to map user");
|
|
107
|
+
}
|
|
108
|
+
return authUser;
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
async signOut(): Promise<void> {
|
|
82
|
-
await
|
|
112
|
+
const result = await firebaseSignOut();
|
|
113
|
+
if (!result.success) {
|
|
114
|
+
throw new AuthError(result.error?.message || "Sign out failed");
|
|
115
|
+
}
|
|
83
116
|
}
|
|
84
117
|
|
|
85
118
|
getCurrentUser(): AuthUser | null {
|
|
86
|
-
|
|
119
|
+
const user = getCurrentUserFromGlobal();
|
|
120
|
+
return user ? mapToAuthUser(user) : null;
|
|
87
121
|
}
|
|
88
122
|
|
|
89
123
|
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
|
|
90
|
-
|
|
124
|
+
const result = setupAuthListener({
|
|
125
|
+
onAuthStateChange: (user) => {
|
|
126
|
+
const authUser = user ? mapToAuthUser(user) : null;
|
|
127
|
+
callback(authUser);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
return result.success && result.unsubscribe ? result.unsubscribe : () => {};
|
|
91
131
|
}
|
|
92
132
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Handles anonymous mode functionality
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { IAuthProvider } from "../../application/ports/IAuthProvider";
|
|
7
6
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
8
7
|
import { emitAnonymousModeEnabled } from "./AuthEventService";
|
|
9
8
|
import type { IStorageProvider } from "../types/Storage.types";
|
|
@@ -46,19 +45,7 @@ export class AnonymousModeService {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
async enable(storageProvider: IStorageProvider
|
|
50
|
-
// Sign out from provider if logged in
|
|
51
|
-
if (provider?.getCurrentUser()) {
|
|
52
|
-
try {
|
|
53
|
-
await provider.signOut();
|
|
54
|
-
} catch (error) {
|
|
55
|
-
// Log error but don't throw - allow anonymous mode to be enabled
|
|
56
|
-
// even if sign-out fails (user might be in inconsistent state but
|
|
57
|
-
// subsequent auth operations will resolve it)
|
|
58
|
-
console.warn("[AnonymousModeService] Sign-out failed during anonymous mode enable:", error);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
48
|
+
async enable(storageProvider: IStorageProvider): Promise<void> {
|
|
62
49
|
this.isAnonymousMode = true;
|
|
63
50
|
await this.save(storageProvider);
|
|
64
51
|
emitAnonymousModeEnabled();
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Service
|
|
3
|
-
* Orchestrates authentication operations
|
|
3
|
+
* Orchestrates authentication operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Auth } from "firebase/auth";
|
|
7
|
-
import type { AuthCredentials, SignUpCredentials } from "../../application/ports/IAuthProvider";
|
|
8
|
-
import type { IAuthProvider } from "../../application/ports/IAuthProvider";
|
|
9
|
-
import { FirebaseAuthProvider } from "../providers/FirebaseAuthProvider";
|
|
10
6
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
11
7
|
import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
|
|
12
8
|
import { sanitizeAuthConfig } from "../../domain/value-objects/AuthConfig";
|
|
13
|
-
import { AuthRepository } from "../repositories/AuthRepository";
|
|
9
|
+
import { AuthRepository, type SignUpCredentials, type AuthCredentials } from "../repositories/AuthRepository";
|
|
14
10
|
import { AnonymousModeService } from "./AnonymousModeService";
|
|
15
11
|
import { authEventService } from "./AuthEventService";
|
|
16
12
|
import type { IStorageProvider } from "../types/Storage.types";
|
|
@@ -33,33 +29,10 @@ export class AuthService {
|
|
|
33
29
|
return this.repository;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
typeof obj === "object" &&
|
|
39
|
-
obj !== null &&
|
|
40
|
-
"currentUser" in obj &&
|
|
41
|
-
"onAuthStateChanged" in obj &&
|
|
42
|
-
"signInWithEmailAndPassword" in obj &&
|
|
43
|
-
"createUserWithEmailAndPassword" in obj &&
|
|
44
|
-
typeof obj.onAuthStateChanged === "function"
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async initialize(providerOrAuth: IAuthProvider | Auth): Promise<void> {
|
|
32
|
+
async initialize(): Promise<void> {
|
|
49
33
|
if (this.initialized) return;
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (this.isFirebaseAuth(providerOrAuth)) {
|
|
54
|
-
const firebaseProvider = new FirebaseAuthProvider(providerOrAuth);
|
|
55
|
-
await firebaseProvider.initialize();
|
|
56
|
-
provider = firebaseProvider;
|
|
57
|
-
} else {
|
|
58
|
-
provider = providerOrAuth;
|
|
59
|
-
await provider.initialize();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.repository = new AuthRepository(provider, this.config);
|
|
35
|
+
this.repository = new AuthRepository(this.config);
|
|
63
36
|
|
|
64
37
|
if (this.storageProvider) {
|
|
65
38
|
await this.anonymousModeService.load(this.storageProvider);
|
|
@@ -122,14 +95,13 @@ export class AuthService {
|
|
|
122
95
|
let authServiceInstance: AuthService | null = null;
|
|
123
96
|
|
|
124
97
|
export async function initializeAuthService(
|
|
125
|
-
providerOrAuth: IAuthProvider | Auth,
|
|
126
98
|
config?: Partial<AuthConfig>,
|
|
127
99
|
storageProvider?: IStorageProvider
|
|
128
100
|
): Promise<AuthService> {
|
|
129
101
|
if (!authServiceInstance) {
|
|
130
102
|
authServiceInstance = new AuthService(config, storageProvider);
|
|
131
103
|
}
|
|
132
|
-
await authServiceInstance.initialize(
|
|
104
|
+
await authServiceInstance.initialize();
|
|
133
105
|
return authServiceInstance;
|
|
134
106
|
}
|
|
135
107
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Auth Initialization
|
|
3
|
-
* Initializes
|
|
3
|
+
* Initializes auth with user document sync and conversion tracking
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
import { getFirebaseAuth } from "@umituz/react-native-firebase";
|
|
6
|
+
import type { User } from "firebase/auth";
|
|
7
|
+
import { getFirebaseAuth, configureUserDocumentService } from "@umituz/react-native-firebase";
|
|
8
|
+
import type { UserDocumentExtras } from "@umituz/react-native-firebase";
|
|
8
9
|
import { initializeAuthService } from "./AuthService";
|
|
9
|
-
import { configureUserDocumentService } from "./UserDocumentService";
|
|
10
|
-
import type { UserDocumentExtras } from "./UserDocumentService";
|
|
11
10
|
import { collectDeviceExtras } from "@umituz/react-native-design-system";
|
|
12
11
|
import { initializeAuthListener } from "../../presentation/stores/initializeAuthListener";
|
|
13
12
|
import { createAuthStateHandler } from "../utils/authStateHandler";
|
|
@@ -27,7 +26,7 @@ export interface InitializeAuthOptions {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
let isInitialized = false;
|
|
30
|
-
let initializationPromise: Promise<{ success: boolean
|
|
29
|
+
let initializationPromise: Promise<{ success: boolean }> | null = null;
|
|
31
30
|
const conversionState: { current: ConversionState } = {
|
|
32
31
|
current: { previousUserId: null, wasAnonymous: false },
|
|
33
32
|
};
|
|
@@ -37,9 +36,9 @@ const conversionState: { current: ConversionState } = {
|
|
|
37
36
|
*/
|
|
38
37
|
export async function initializeAuth(
|
|
39
38
|
options: InitializeAuthOptions = {}
|
|
40
|
-
): Promise<{ success: boolean
|
|
39
|
+
): Promise<{ success: boolean }> {
|
|
41
40
|
if (isInitialized) {
|
|
42
|
-
return { success: true
|
|
41
|
+
return { success: true };
|
|
43
42
|
}
|
|
44
43
|
// Prevent race condition: return existing promise if initialization is in progress
|
|
45
44
|
if (initializationPromise) {
|
|
@@ -55,7 +54,7 @@ export async function initializeAuth(
|
|
|
55
54
|
|
|
56
55
|
async function doInitializeAuth(
|
|
57
56
|
options: InitializeAuthOptions
|
|
58
|
-
): Promise<{ success: boolean
|
|
57
|
+
): Promise<{ success: boolean }> {
|
|
59
58
|
|
|
60
59
|
const {
|
|
61
60
|
userCollection = "users",
|
|
@@ -69,7 +68,7 @@ async function doInitializeAuth(
|
|
|
69
68
|
} = options;
|
|
70
69
|
|
|
71
70
|
const auth = getFirebaseAuth();
|
|
72
|
-
if (!auth) return { success: false
|
|
71
|
+
if (!auth) return { success: false };
|
|
73
72
|
|
|
74
73
|
configureUserDocumentService({
|
|
75
74
|
collectionName: userCollection,
|
|
@@ -79,7 +78,7 @@ async function doInitializeAuth(
|
|
|
79
78
|
|
|
80
79
|
let authServiceInitFailed = false;
|
|
81
80
|
try {
|
|
82
|
-
await initializeAuthService(
|
|
81
|
+
await initializeAuthService(authConfig, storageProvider);
|
|
83
82
|
} catch {
|
|
84
83
|
authServiceInitFailed = true;
|
|
85
84
|
}
|
|
@@ -97,7 +96,7 @@ async function doInitializeAuth(
|
|
|
97
96
|
});
|
|
98
97
|
|
|
99
98
|
isInitialized = true;
|
|
100
|
-
return { success: !authServiceInitFailed
|
|
99
|
+
return { success: !authServiceInitFailed };
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
export function isAuthInitialized(): boolean {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { User } from "firebase/auth";
|
|
7
|
-
import { ensureUserDocument } from "
|
|
7
|
+
import { ensureUserDocument } from "@umituz/react-native-firebase";
|
|
8
8
|
import { detectConversion, type ConversionState } from "./authConversionDetector";
|
|
9
9
|
|
|
10
10
|
export interface AuthStateHandlerOptions {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useMutation } from "@umituz/react-native-design-system";
|
|
7
7
|
import { getAuthService } from "../../../infrastructure/services/AuthService";
|
|
8
|
-
import type { AuthCredentials, SignUpCredentials } from "../../../
|
|
8
|
+
import type { AuthCredentials, SignUpCredentials } from "../../../infrastructure/repositories/AuthRepository";
|
|
9
9
|
import type { AuthUser } from "../../../domain/entities/AuthUser";
|
|
10
10
|
|
|
11
11
|
export const useSignUpMutation = () => {
|
|
@@ -139,7 +139,7 @@ export const useAccountManagement = (
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Handle social auth reauthentication
|
|
142
|
-
if (result.
|
|
142
|
+
if (result.requiresReauth && onReauthRequired) {
|
|
143
143
|
const reauthSuccess = await onReauthRequired();
|
|
144
144
|
if (!reauthSuccess) {
|
|
145
145
|
throw new Error("Reauthentication required to delete account");
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Provider Interface
|
|
3
|
-
* Port for provider-agnostic authentication (Firebase, Supabase, etc.)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
7
|
-
import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
|
|
8
|
-
|
|
9
|
-
export interface AuthCredentials {
|
|
10
|
-
email: string;
|
|
11
|
-
password: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface SignUpCredentials extends AuthCredentials {
|
|
15
|
-
displayName?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Result from social sign-in operations
|
|
20
|
-
*/
|
|
21
|
-
export interface SocialSignInResult {
|
|
22
|
-
user: AuthUser;
|
|
23
|
-
isNewUser: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface IAuthProvider {
|
|
27
|
-
/**
|
|
28
|
-
* Initialize the auth provider
|
|
29
|
-
*/
|
|
30
|
-
initialize(): Promise<void>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Check if provider is initialized
|
|
34
|
-
*/
|
|
35
|
-
isInitialized(): boolean;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Sign in with email and password
|
|
39
|
-
*/
|
|
40
|
-
signIn(credentials: AuthCredentials): Promise<AuthUser>;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Sign up with email, password, and optional display name
|
|
44
|
-
*/
|
|
45
|
-
signUp(credentials: SignUpCredentials): Promise<AuthUser>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Sign in with Google
|
|
49
|
-
* @returns User and whether this is a new user
|
|
50
|
-
*/
|
|
51
|
-
signInWithGoogle?(): Promise<SocialSignInResult>;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Sign in with Apple
|
|
55
|
-
* @returns User and whether this is a new user
|
|
56
|
-
*/
|
|
57
|
-
signInWithApple?(): Promise<SocialSignInResult>;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if a social provider is supported
|
|
61
|
-
*/
|
|
62
|
-
isSocialProviderSupported?(provider: SocialAuthProvider): boolean;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Sign out current user
|
|
66
|
-
*/
|
|
67
|
-
signOut(): Promise<void>;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get current authenticated user
|
|
71
|
-
*/
|
|
72
|
-
getCurrentUser(): AuthUser | null;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Subscribe to auth state changes
|
|
76
|
-
*/
|
|
77
|
-
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void;
|
|
78
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Firebase Auth Provider
|
|
3
|
-
* IAuthProvider implementation for Firebase Authentication
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
createUserWithEmailAndPassword,
|
|
8
|
-
signInWithEmailAndPassword,
|
|
9
|
-
signOut as firebaseSignOut,
|
|
10
|
-
onAuthStateChanged,
|
|
11
|
-
updateProfile,
|
|
12
|
-
EmailAuthProvider,
|
|
13
|
-
linkWithCredential,
|
|
14
|
-
type Auth,
|
|
15
|
-
} from "firebase/auth";
|
|
16
|
-
import type { IAuthProvider, AuthCredentials, SignUpCredentials } from "../../application/ports/IAuthProvider";
|
|
17
|
-
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
18
|
-
import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
|
|
19
|
-
import { mapToAuthUser } from "../utils/UserMapper";
|
|
20
|
-
|
|
21
|
-
export class FirebaseAuthProvider implements IAuthProvider {
|
|
22
|
-
private auth: Auth | null = null;
|
|
23
|
-
|
|
24
|
-
constructor(auth?: Auth) {
|
|
25
|
-
if (auth) this.auth = auth;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
initialize(): Promise<void> {
|
|
29
|
-
if (!this.auth) throw new Error("Firebase Auth instance must be provided");
|
|
30
|
-
return Promise.resolve();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
setAuth(auth: Auth): void {
|
|
34
|
-
this.auth = auth;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
isInitialized(): boolean {
|
|
38
|
-
return this.auth !== null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async signIn(credentials: AuthCredentials): Promise<AuthUser> {
|
|
42
|
-
if (!this.auth) throw new Error("Firebase Auth is not initialized");
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const userCredential = await signInWithEmailAndPassword(this.auth, credentials.email.trim(), credentials.password);
|
|
46
|
-
const user = mapToAuthUser(userCredential.user);
|
|
47
|
-
if (!user) throw new Error("Failed to sign in");
|
|
48
|
-
return user;
|
|
49
|
-
} catch (error: unknown) {
|
|
50
|
-
throw mapFirebaseAuthError(error);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
|
|
55
|
-
if (!this.auth) throw new Error("Firebase Auth is not initialized");
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const currentUser = this.auth.currentUser;
|
|
59
|
-
const isAnonymous = currentUser?.isAnonymous ?? false;
|
|
60
|
-
let userCredential;
|
|
61
|
-
|
|
62
|
-
if (currentUser && isAnonymous) {
|
|
63
|
-
// Try to reload to get fresh user data, but don't fail if it doesn't work
|
|
64
|
-
try {
|
|
65
|
-
await currentUser.reload();
|
|
66
|
-
} catch (reloadError) {
|
|
67
|
-
// Log the reload error but proceed - the user might still be valid
|
|
68
|
-
console.warn("[FirebaseAuthProvider] User reload failed during anonymous upgrade:", reloadError);
|
|
69
|
-
}
|
|
70
|
-
const credential = EmailAuthProvider.credential(credentials.email.trim(), credentials.password);
|
|
71
|
-
userCredential = await linkWithCredential(currentUser, credential);
|
|
72
|
-
} else {
|
|
73
|
-
userCredential = await createUserWithEmailAndPassword(this.auth, credentials.email.trim(), credentials.password);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (credentials.displayName && userCredential.user) {
|
|
77
|
-
try {
|
|
78
|
-
await updateProfile(userCredential.user, { displayName: credentials.displayName.trim() });
|
|
79
|
-
} catch (profileError) {
|
|
80
|
-
// Display name update failed, but account was created successfully
|
|
81
|
-
// Log this for debugging but don't fail the entire operation
|
|
82
|
-
console.warn("[FirebaseAuthProvider] Display name update failed:", profileError);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const user = mapToAuthUser(userCredential.user);
|
|
87
|
-
if (!user) throw new Error("Failed to create user account");
|
|
88
|
-
return user;
|
|
89
|
-
} catch (error: unknown) {
|
|
90
|
-
throw mapFirebaseAuthError(error);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async signOut(): Promise<void> {
|
|
95
|
-
if (!this.auth) return;
|
|
96
|
-
try {
|
|
97
|
-
await firebaseSignOut(this.auth);
|
|
98
|
-
} catch (error: unknown) {
|
|
99
|
-
throw mapFirebaseAuthError(error);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
getCurrentUser(): AuthUser | null {
|
|
104
|
-
if (!this.auth) return null;
|
|
105
|
-
const currentUser = this.auth.currentUser;
|
|
106
|
-
if (!currentUser) return null;
|
|
107
|
-
return mapToAuthUser(currentUser);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
|
|
111
|
-
if (!this.auth) {
|
|
112
|
-
callback(null);
|
|
113
|
-
return () => {};
|
|
114
|
-
}
|
|
115
|
-
return onAuthStateChanged(this.auth, (user) => callback(mapToAuthUser(user)));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Document Types and Configuration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Minimal user interface for document creation
|
|
7
|
-
* Compatible with both Firebase User and AuthUser
|
|
8
|
-
*/
|
|
9
|
-
export interface UserDocumentUser {
|
|
10
|
-
uid: string;
|
|
11
|
-
displayName?: string | null;
|
|
12
|
-
email?: string | null;
|
|
13
|
-
photoURL?: string | null;
|
|
14
|
-
isAnonymous?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Configuration for user document service
|
|
19
|
-
*/
|
|
20
|
-
export interface UserDocumentConfig {
|
|
21
|
-
/** Firestore collection name (default: "users") */
|
|
22
|
-
collectionName?: string;
|
|
23
|
-
/** Additional fields to store with user document */
|
|
24
|
-
extraFields?: Record<string, unknown>;
|
|
25
|
-
/** Callback to collect device/app info */
|
|
26
|
-
collectExtras?: () => Promise<UserDocumentExtras>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* User document extras from device/app
|
|
31
|
-
*/
|
|
32
|
-
export interface UserDocumentExtras {
|
|
33
|
-
[key: string]: string | number | boolean | null | undefined;
|
|
34
|
-
deviceId?: string;
|
|
35
|
-
persistentDeviceId?: string;
|
|
36
|
-
nativeDeviceId?: string;
|
|
37
|
-
platform?: string;
|
|
38
|
-
deviceModel?: string;
|
|
39
|
-
deviceBrand?: string;
|
|
40
|
-
deviceName?: string;
|
|
41
|
-
deviceType?: number | string;
|
|
42
|
-
deviceYearClass?: number | string;
|
|
43
|
-
isDevice?: boolean;
|
|
44
|
-
osName?: string;
|
|
45
|
-
osVersion?: string;
|
|
46
|
-
osBuildId?: string;
|
|
47
|
-
totalMemory?: number | string;
|
|
48
|
-
appVersion?: string;
|
|
49
|
-
buildNumber?: string;
|
|
50
|
-
locale?: string;
|
|
51
|
-
region?: string;
|
|
52
|
-
timezone?: string;
|
|
53
|
-
screenWidth?: number;
|
|
54
|
-
screenHeight?: number;
|
|
55
|
-
screenScale?: number;
|
|
56
|
-
fontScale?: number;
|
|
57
|
-
isLandscape?: boolean;
|
|
58
|
-
previousAnonymousUserId?: string;
|
|
59
|
-
signUpMethod?: string;
|
|
60
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Document Service
|
|
3
|
-
* Generic service for creating/updating user documents in Firestore
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { doc, getDoc, setDoc, serverTimestamp } from "firebase/firestore";
|
|
7
|
-
import type { User } from "firebase/auth";
|
|
8
|
-
import { getFirestore } from "@umituz/react-native-firebase";
|
|
9
|
-
import type {
|
|
10
|
-
UserDocumentUser,
|
|
11
|
-
UserDocumentConfig,
|
|
12
|
-
UserDocumentExtras,
|
|
13
|
-
} from "./UserDocument.types";
|
|
14
|
-
import {
|
|
15
|
-
getSignUpMethod,
|
|
16
|
-
buildBaseData,
|
|
17
|
-
buildCreateData,
|
|
18
|
-
buildUpdateData,
|
|
19
|
-
} from "../utils/userDocumentBuilder.util";
|
|
20
|
-
|
|
21
|
-
export type {
|
|
22
|
-
UserDocumentUser,
|
|
23
|
-
UserDocumentConfig,
|
|
24
|
-
UserDocumentExtras,
|
|
25
|
-
} from "./UserDocument.types";
|
|
26
|
-
|
|
27
|
-
declare const __DEV__: boolean;
|
|
28
|
-
|
|
29
|
-
let userDocumentConfig: UserDocumentConfig = {};
|
|
30
|
-
|
|
31
|
-
export function configureUserDocumentService(config: UserDocumentConfig): void {
|
|
32
|
-
userDocumentConfig = { ...config };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export async function ensureUserDocument(
|
|
36
|
-
user: UserDocumentUser | User,
|
|
37
|
-
extras?: UserDocumentExtras,
|
|
38
|
-
): Promise<boolean> {
|
|
39
|
-
const db = getFirestore();
|
|
40
|
-
if (!db || !user.uid) return false;
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
let allExtras = extras || {};
|
|
44
|
-
if (userDocumentConfig.collectExtras) {
|
|
45
|
-
const collectedExtras = await userDocumentConfig.collectExtras();
|
|
46
|
-
allExtras = { ...collectedExtras, ...allExtras };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!allExtras.signUpMethod) allExtras.signUpMethod = getSignUpMethod(user);
|
|
50
|
-
|
|
51
|
-
const collectionName = userDocumentConfig.collectionName || "users";
|
|
52
|
-
const userRef = doc(db, collectionName, user.uid);
|
|
53
|
-
const userDoc = await getDoc(userRef);
|
|
54
|
-
const baseData = buildBaseData(user, allExtras);
|
|
55
|
-
|
|
56
|
-
const docData = !userDoc.exists()
|
|
57
|
-
? buildCreateData(baseData, userDocumentConfig.extraFields, allExtras)
|
|
58
|
-
: buildUpdateData(baseData, allExtras);
|
|
59
|
-
|
|
60
|
-
await setDoc(userRef, docData, { merge: true });
|
|
61
|
-
return true;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
64
|
-
console.error("[UserDocumentService] Failed:", error);
|
|
65
|
-
}
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function markUserDeleted(userId: string): Promise<boolean> {
|
|
71
|
-
const db = getFirestore();
|
|
72
|
-
if (!db || !userId) return false;
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const userRef = doc(db, userDocumentConfig.collectionName || "users", userId);
|
|
76
|
-
await setDoc(userRef, {
|
|
77
|
-
isDeleted: true,
|
|
78
|
-
deletedAt: serverTimestamp(),
|
|
79
|
-
updatedAt: serverTimestamp(),
|
|
80
|
-
}, { merge: true });
|
|
81
|
-
return true;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Document Builder Utility
|
|
3
|
-
* Builds user document data for Firestore (max 100 lines)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { serverTimestamp } from "firebase/firestore";
|
|
7
|
-
import type {
|
|
8
|
-
UserDocumentUser,
|
|
9
|
-
UserDocumentExtras,
|
|
10
|
-
} from "../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
|
-
uid: user.uid,
|
|
41
|
-
displayName: user.displayName,
|
|
42
|
-
email: user.email,
|
|
43
|
-
photoURL: user.photoURL,
|
|
44
|
-
isAnonymous: user.isAnonymous,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
if (extras) {
|
|
48
|
-
const internalFields = ["signUpMethod", "previousAnonymousUserId"];
|
|
49
|
-
Object.keys(extras).forEach((key) => {
|
|
50
|
-
if (!internalFields.includes(key)) {
|
|
51
|
-
const val = extras[key];
|
|
52
|
-
if (val !== undefined) {
|
|
53
|
-
data[key] = val;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return data;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Builds user document data for creation
|
|
64
|
-
*/
|
|
65
|
-
export function buildCreateData(
|
|
66
|
-
baseData: Record<string, unknown>,
|
|
67
|
-
extraFields: Record<string, unknown> | undefined,
|
|
68
|
-
extras?: UserDocumentExtras,
|
|
69
|
-
): Record<string, unknown> {
|
|
70
|
-
const createData: Record<string, unknown> = {
|
|
71
|
-
...baseData,
|
|
72
|
-
...extraFields,
|
|
73
|
-
createdAt: serverTimestamp(),
|
|
74
|
-
updatedAt: serverTimestamp(),
|
|
75
|
-
lastLoginAt: serverTimestamp(),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
if (extras?.previousAnonymousUserId) {
|
|
79
|
-
createData.previousAnonymousUserId = extras.previousAnonymousUserId;
|
|
80
|
-
createData.convertedFromAnonymous = true;
|
|
81
|
-
createData.convertedAt = serverTimestamp();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (extras?.signUpMethod) createData.signUpMethod = extras.signUpMethod;
|
|
85
|
-
|
|
86
|
-
return createData;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Builds user document data for update
|
|
91
|
-
*/
|
|
92
|
-
export function buildUpdateData(
|
|
93
|
-
baseData: Record<string, unknown>,
|
|
94
|
-
extras?: UserDocumentExtras,
|
|
95
|
-
): Record<string, unknown> {
|
|
96
|
-
const updateData: Record<string, unknown> = {
|
|
97
|
-
...baseData,
|
|
98
|
-
lastLoginAt: serverTimestamp(),
|
|
99
|
-
updatedAt: serverTimestamp(),
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if (extras?.previousAnonymousUserId) {
|
|
103
|
-
updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
|
|
104
|
-
updateData.convertedFromAnonymous = true;
|
|
105
|
-
updateData.convertedAt = serverTimestamp();
|
|
106
|
-
if (extras?.signUpMethod) updateData.signUpMethod = extras.signUpMethod;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return updateData;
|
|
110
|
-
}
|