@umituz/react-native-firebase 1.13.1 → 1.13.3

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.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Auth Utils Service
3
+ * Single Responsibility: Provide utility functions for auth state checking
4
+ *
5
+ * SOLID: Single Responsibility - Only handles auth state utilities
6
+ */
7
+
8
+ import type { Auth, User } from 'firebase/auth';
9
+ import { getFirebaseAuth } from '../config/FirebaseAuthClient';
10
+
11
+ /**
12
+ * Check if user is anonymous
13
+ */
14
+ function isAnonymousUser(user: User): boolean {
15
+ return user.isAnonymous === true;
16
+ }
17
+
18
+ /**
19
+ * Auth check result interface
20
+ */
21
+ export interface AuthCheckResult {
22
+ isAuthenticated: boolean;
23
+ isAnonymous: boolean;
24
+ isGuest: boolean;
25
+ currentUser: User | null;
26
+ userId: string | null;
27
+ }
28
+
29
+ /**
30
+ * Check authentication state
31
+ * Returns comprehensive auth state information
32
+ */
33
+ export function checkAuthState(auth: Auth | null): AuthCheckResult {
34
+ if (!auth) {
35
+ return {
36
+ isAuthenticated: false,
37
+ isAnonymous: false,
38
+ isGuest: false,
39
+ currentUser: null,
40
+ userId: null,
41
+ };
42
+ }
43
+
44
+ const currentUser = auth.currentUser;
45
+
46
+ if (!currentUser) {
47
+ return {
48
+ isAuthenticated: false,
49
+ isAnonymous: false,
50
+ isGuest: false,
51
+ currentUser: null,
52
+ userId: null,
53
+ };
54
+ }
55
+
56
+ const anonymous = isAnonymousUser(currentUser);
57
+
58
+ return {
59
+ isAuthenticated: true,
60
+ isAnonymous: anonymous,
61
+ isGuest: anonymous,
62
+ currentUser,
63
+ userId: currentUser.uid,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Check if user is authenticated (including anonymous)
69
+ */
70
+ export function isAuthenticated(auth: Auth | null): boolean {
71
+ return checkAuthState(auth).isAuthenticated;
72
+ }
73
+
74
+ /**
75
+ * Check if user is guest (anonymous)
76
+ */
77
+ export function isGuest(auth: Auth | null): boolean {
78
+ return checkAuthState(auth).isGuest;
79
+ }
80
+
81
+ /**
82
+ * Get current user ID (null if not authenticated)
83
+ */
84
+ export function getCurrentUserId(auth: Auth | null): string | null {
85
+ return checkAuthState(auth).userId;
86
+ }
87
+
88
+ /**
89
+ * Get current user (null if not authenticated)
90
+ */
91
+ export function getCurrentUser(auth: Auth | null): User | null {
92
+ return checkAuthState(auth).currentUser;
93
+ }
94
+
95
+ /**
96
+ * Get current authenticated user ID from global auth instance
97
+ * Convenience function that uses getFirebaseAuth()
98
+ */
99
+ export function getCurrentUserIdFromGlobal(): string | null {
100
+ const auth = getFirebaseAuth();
101
+ return getCurrentUserId(auth);
102
+ }
103
+
104
+ /**
105
+ * Get current user from global auth instance
106
+ * Convenience function that uses getFirebaseAuth()
107
+ */
108
+ export function getCurrentUserFromGlobal(): User | null {
109
+ const auth = getFirebaseAuth();
110
+ return getCurrentUser(auth);
111
+ }
112
+
113
+ /**
114
+ * Check if current user is authenticated (including anonymous)
115
+ * Convenience function that uses getFirebaseAuth()
116
+ */
117
+ export function isCurrentUserAuthenticated(): boolean {
118
+ const auth = getFirebaseAuth();
119
+ return isAuthenticated(auth);
120
+ }
121
+
122
+ /**
123
+ * Check if current user is guest (anonymous)
124
+ * Convenience function that uses getFirebaseAuth()
125
+ */
126
+ export function isCurrentUserGuest(): boolean {
127
+ const auth = getFirebaseAuth();
128
+ return isGuest(auth);
129
+ }
130
+
131
+ /**
132
+ * Verify userId matches current authenticated user
133
+ */
134
+ export function verifyUserId(auth: Auth | null, userId: string): boolean {
135
+ if (!auth || !userId) {
136
+ return false;
137
+ }
138
+
139
+ try {
140
+ const state = checkAuthState(auth);
141
+ return state.isAuthenticated && state.userId === userId;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Get safe user ID (null if not authenticated)
149
+ */
150
+ export function getSafeUserId(auth: Auth | null): string | null {
151
+ if (!auth) {
152
+ return null;
153
+ }
154
+
155
+ try {
156
+ return getCurrentUserId(auth);
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Check if user exists and is valid
164
+ */
165
+ export function isValidUser(user: User | null | undefined): user is User {
166
+ return user !== null && user !== undefined && typeof user.uid === 'string' && user.uid.length > 0;
167
+ }
168
+
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Firestore Utilities
3
+ * Centralized utilities for Firestore operations with anonymous user support
4
+ */
5
+
6
+ import type { Auth } from "firebase/auth";
7
+ import { checkAuthState, verifyUserId } from "./auth-utils.service";
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ export interface FirestoreQueryOptions {
12
+ /**
13
+ * Skip query if user is anonymous/guest
14
+ * Default: true (guest users don't have Firestore data)
15
+ */
16
+ readonly skipForGuest?: boolean;
17
+
18
+ /**
19
+ * Skip query if user is not authenticated
20
+ * Default: true
21
+ */
22
+ readonly skipIfNotAuthenticated?: boolean;
23
+
24
+ /**
25
+ * Verify userId matches current user
26
+ * Default: true
27
+ */
28
+ readonly verifyUserId?: boolean;
29
+
30
+ /**
31
+ * User ID to verify (required if verifyUserId is true)
32
+ */
33
+ readonly userId?: string;
34
+ }
35
+
36
+ export type FirestoreQuerySkipReason =
37
+ | "not_authenticated"
38
+ | "is_guest"
39
+ | "user_id_mismatch"
40
+ | "no_auth"
41
+ | "invalid_options";
42
+
43
+ export interface FirestoreQueryResult {
44
+ readonly shouldSkip: boolean;
45
+ readonly reason?: FirestoreQuerySkipReason;
46
+ readonly userId: string | null;
47
+ readonly isGuest: boolean;
48
+ readonly isAuthenticated: boolean;
49
+ }
50
+
51
+ /**
52
+ * Check if Firestore query should be skipped
53
+ * Centralized logic for all Firestore operations
54
+ */
55
+ export function shouldSkipFirestoreQuery(
56
+ auth: Auth | null,
57
+ options: FirestoreQueryOptions = {},
58
+ ): FirestoreQueryResult {
59
+ const {
60
+ skipForGuest = true,
61
+ skipIfNotAuthenticated = true,
62
+ verifyUserId: shouldVerify = true,
63
+ userId,
64
+ } = options;
65
+
66
+ try {
67
+ // No auth available
68
+ if (!auth) {
69
+ return {
70
+ shouldSkip: true,
71
+ reason: "no_auth",
72
+ userId: null,
73
+ isGuest: false,
74
+ isAuthenticated: false,
75
+ };
76
+ }
77
+
78
+ // Check auth state
79
+ const authState = checkAuthState(auth);
80
+
81
+ // Not authenticated
82
+ if (!authState.isAuthenticated) {
83
+ if (skipIfNotAuthenticated) {
84
+ return {
85
+ shouldSkip: true,
86
+ reason: "not_authenticated",
87
+ userId: null,
88
+ isGuest: false,
89
+ isAuthenticated: false,
90
+ };
91
+ }
92
+ }
93
+
94
+ // Guest/anonymous user
95
+ if (authState.isGuest) {
96
+ if (skipForGuest) {
97
+ return {
98
+ shouldSkip: true,
99
+ reason: "is_guest",
100
+ userId: authState.userId,
101
+ isGuest: true,
102
+ isAuthenticated: false,
103
+ };
104
+ }
105
+ }
106
+
107
+ // Verify userId if provided
108
+ if (shouldVerify && userId) {
109
+ if (!verifyUserId(auth, userId)) {
110
+ return {
111
+ shouldSkip: true,
112
+ reason: "user_id_mismatch",
113
+ userId: authState.userId,
114
+ isGuest: authState.isGuest,
115
+ isAuthenticated: authState.isAuthenticated,
116
+ };
117
+ }
118
+ }
119
+
120
+ // Don't skip
121
+ return {
122
+ shouldSkip: false,
123
+ userId: authState.userId,
124
+ isGuest: authState.isGuest,
125
+ isAuthenticated: authState.isAuthenticated,
126
+ };
127
+ } catch (error) {
128
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
129
+ // eslint-disable-next-line no-console
130
+ console.error("[FirestoreUtils] Error checking if query should be skipped", error);
131
+ }
132
+
133
+ return {
134
+ shouldSkip: true,
135
+ reason: "invalid_options",
136
+ userId: null,
137
+ isGuest: false,
138
+ isAuthenticated: false,
139
+ };
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Create safe query options with defaults
145
+ */
146
+ export function createFirestoreQueryOptions(
147
+ options: Partial<FirestoreQueryOptions> = {},
148
+ ): FirestoreQueryOptions {
149
+ return {
150
+ skipForGuest: options.skipForGuest ?? true,
151
+ skipIfNotAuthenticated: options.skipIfNotAuthenticated ?? true,
152
+ verifyUserId: options.verifyUserId ?? true,
153
+ userId: options.userId,
154
+ };
155
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Google Auth Service
3
+ * Handles Google Sign-In with Firebase Authentication
4
+ * Uses expo-auth-session for OAuth flow
5
+ */
6
+
7
+ import {
8
+ GoogleAuthProvider,
9
+ signInWithCredential,
10
+ type Auth,
11
+ type UserCredential,
12
+ } from "firebase/auth";
13
+
14
+ /**
15
+ * Google Auth configuration
16
+ */
17
+ export interface GoogleAuthConfig {
18
+ webClientId?: string;
19
+ iosClientId?: string;
20
+ androidClientId?: string;
21
+ }
22
+
23
+ /**
24
+ * Google Auth result
25
+ */
26
+ export interface GoogleAuthResult {
27
+ success: boolean;
28
+ userCredential?: UserCredential;
29
+ error?: string;
30
+ isNewUser?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Google Auth Service
35
+ * Provides Google Sign-In functionality for Firebase
36
+ */
37
+ export class GoogleAuthService {
38
+ private config: GoogleAuthConfig | null = null;
39
+
40
+ /**
41
+ * Configure Google Auth with client IDs
42
+ */
43
+ configure(config: GoogleAuthConfig): void {
44
+ this.config = config;
45
+ }
46
+
47
+ /**
48
+ * Check if Google Auth is configured
49
+ */
50
+ isConfigured(): boolean {
51
+ return (
52
+ this.config !== null &&
53
+ (!!this.config.webClientId ||
54
+ !!this.config.iosClientId ||
55
+ !!this.config.androidClientId)
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Get the current configuration
61
+ */
62
+ getConfig(): GoogleAuthConfig | null {
63
+ return this.config;
64
+ }
65
+
66
+ /**
67
+ * Sign in with Google ID token
68
+ * Called after successful Google OAuth flow
69
+ */
70
+ async signInWithIdToken(
71
+ auth: Auth,
72
+ idToken: string,
73
+ ): Promise<GoogleAuthResult> {
74
+ try {
75
+ const credential = GoogleAuthProvider.credential(idToken);
76
+ const userCredential = await signInWithCredential(auth, credential);
77
+
78
+ // Check if this is a new user
79
+ const isNewUser =
80
+ userCredential.user.metadata.creationTime ===
81
+ userCredential.user.metadata.lastSignInTime;
82
+
83
+ return {
84
+ success: true,
85
+ userCredential,
86
+ isNewUser,
87
+ };
88
+ } catch (error) {
89
+ return {
90
+ success: false,
91
+ error: error instanceof Error ? error.message : "Google sign-in failed",
92
+ };
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Singleton instance
99
+ */
100
+ export const googleAuthService = new GoogleAuthService();
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Reauthentication Service
3
+ * Handles Firebase Auth reauthentication for sensitive operations
4
+ *
5
+ * SOLID: Single Responsibility - Only handles reauthentication
6
+ */
7
+
8
+ import {
9
+ reauthenticateWithCredential,
10
+ GoogleAuthProvider,
11
+ OAuthProvider,
12
+ type User,
13
+ type AuthCredential,
14
+ } from "firebase/auth";
15
+ import * as AppleAuthentication from "expo-apple-authentication";
16
+ import * as Crypto from "expo-crypto";
17
+ import { Platform } from "react-native";
18
+
19
+ export interface ReauthenticationResult {
20
+ success: boolean;
21
+ error?: {
22
+ code: string;
23
+ message: string;
24
+ };
25
+ }
26
+
27
+ export type AuthProviderType = "google.com" | "apple.com" | "password" | "anonymous" | "unknown";
28
+
29
+ /**
30
+ * Get the primary auth provider for a user
31
+ */
32
+ export function getUserAuthProvider(user: User): AuthProviderType {
33
+ if (user.isAnonymous) {
34
+ return "anonymous";
35
+ }
36
+
37
+ const providerData = user.providerData;
38
+ if (!providerData || providerData.length === 0) {
39
+ return "unknown";
40
+ }
41
+
42
+ // Check for Google
43
+ const googleProvider = providerData.find((p) => p.providerId === "google.com");
44
+ if (googleProvider) {
45
+ return "google.com";
46
+ }
47
+
48
+ // Check for Apple
49
+ const appleProvider = providerData.find((p) => p.providerId === "apple.com");
50
+ if (appleProvider) {
51
+ return "apple.com";
52
+ }
53
+
54
+ // Check for password
55
+ const passwordProvider = providerData.find((p) => p.providerId === "password");
56
+ if (passwordProvider) {
57
+ return "password";
58
+ }
59
+
60
+ return "unknown";
61
+ }
62
+
63
+ /**
64
+ * Reauthenticate with Google
65
+ */
66
+ export async function reauthenticateWithGoogle(
67
+ user: User,
68
+ idToken: string
69
+ ): Promise<ReauthenticationResult> {
70
+ try {
71
+ const credential = GoogleAuthProvider.credential(idToken);
72
+ await reauthenticateWithCredential(user, credential);
73
+ return { success: true };
74
+ } catch (error) {
75
+ const firebaseError = error as { code?: string; message?: string };
76
+ return {
77
+ success: false,
78
+ error: {
79
+ code: firebaseError.code || "auth/reauthentication-failed",
80
+ message: firebaseError.message || "Google reauthentication failed",
81
+ },
82
+ };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Generate a random nonce for Apple Sign-In security
88
+ */
89
+ async function generateNonce(length: number = 32): Promise<string> {
90
+ const randomBytes = await Crypto.getRandomBytesAsync(length);
91
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
92
+ let result = "";
93
+
94
+ for (let i = 0; i < randomBytes.length; i++) {
95
+ result += chars.charAt(randomBytes[i] % chars.length);
96
+ }
97
+
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Reauthenticate with Apple
103
+ * Returns the credential that can be used for reauthentication
104
+ */
105
+ export async function getAppleReauthCredential(): Promise<{
106
+ success: boolean;
107
+ credential?: AuthCredential;
108
+ error?: { code: string; message: string };
109
+ }> {
110
+ if (Platform.OS !== "ios") {
111
+ return {
112
+ success: false,
113
+ error: {
114
+ code: "auth/platform-not-supported",
115
+ message: "Apple Sign-In is only available on iOS",
116
+ },
117
+ };
118
+ }
119
+
120
+ try {
121
+ const isAvailable = await AppleAuthentication.isAvailableAsync();
122
+ if (!isAvailable) {
123
+ return {
124
+ success: false,
125
+ error: {
126
+ code: "auth/apple-signin-unavailable",
127
+ message: "Apple Sign-In is not available on this device",
128
+ },
129
+ };
130
+ }
131
+
132
+ // Generate nonce
133
+ const nonce = await generateNonce();
134
+ const hashedNonce = await Crypto.digestStringAsync(
135
+ Crypto.CryptoDigestAlgorithm.SHA256,
136
+ nonce
137
+ );
138
+
139
+ // Request Apple Sign-In
140
+ const appleCredential = await AppleAuthentication.signInAsync({
141
+ requestedScopes: [
142
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
143
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
144
+ ],
145
+ nonce: hashedNonce,
146
+ });
147
+
148
+ if (!appleCredential.identityToken) {
149
+ return {
150
+ success: false,
151
+ error: {
152
+ code: "auth/no-identity-token",
153
+ message: "No identity token received from Apple",
154
+ },
155
+ };
156
+ }
157
+
158
+ // Create Firebase credential
159
+ const provider = new OAuthProvider("apple.com");
160
+ const credential = provider.credential({
161
+ idToken: appleCredential.identityToken,
162
+ rawNonce: nonce,
163
+ });
164
+
165
+ return { success: true, credential };
166
+ } catch (error) {
167
+ if (error instanceof Error && error.message.includes("ERR_CANCELED")) {
168
+ return {
169
+ success: false,
170
+ error: {
171
+ code: "auth/cancelled",
172
+ message: "Apple Sign-In was cancelled",
173
+ },
174
+ };
175
+ }
176
+
177
+ return {
178
+ success: false,
179
+ error: {
180
+ code: "auth/apple-reauthentication-failed",
181
+ message: error instanceof Error ? error.message : "Apple reauthentication failed",
182
+ },
183
+ };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Reauthenticate with Apple
189
+ */
190
+ export async function reauthenticateWithApple(user: User): Promise<ReauthenticationResult> {
191
+ const result = await getAppleReauthCredential();
192
+
193
+ if (!result.success || !result.credential) {
194
+ return {
195
+ success: false,
196
+ error: result.error || {
197
+ code: "auth/no-credential",
198
+ message: "Failed to get Apple credential",
199
+ },
200
+ };
201
+ }
202
+
203
+ try {
204
+ await reauthenticateWithCredential(user, result.credential);
205
+ return { success: true };
206
+ } catch (error) {
207
+ const firebaseError = error as { code?: string; message?: string };
208
+ return {
209
+ success: false,
210
+ error: {
211
+ code: firebaseError.code || "auth/reauthentication-failed",
212
+ message: firebaseError.message || "Apple reauthentication failed",
213
+ },
214
+ };
215
+ }
216
+ }