@umituz/react-native-auth 1.7.0 → 1.8.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design supports Firebase Auth and can be adapted for Supabase or other providers.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,52 @@
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
+
8
+ export interface AuthCredentials {
9
+ email: string;
10
+ password: string;
11
+ }
12
+
13
+ export interface SignUpCredentials extends AuthCredentials {
14
+ displayName?: string;
15
+ }
16
+
17
+ export interface IAuthProvider {
18
+ /**
19
+ * Initialize the auth provider
20
+ */
21
+ initialize(): Promise<void>;
22
+
23
+ /**
24
+ * Check if provider is initialized
25
+ */
26
+ isInitialized(): boolean;
27
+
28
+ /**
29
+ * Sign in with email and password
30
+ */
31
+ signIn(credentials: AuthCredentials): Promise<AuthUser>;
32
+
33
+ /**
34
+ * Sign up with email, password, and optional display name
35
+ */
36
+ signUp(credentials: SignUpCredentials): Promise<AuthUser>;
37
+
38
+ /**
39
+ * Sign out current user
40
+ */
41
+ signOut(): Promise<void>;
42
+
43
+ /**
44
+ * Get current authenticated user
45
+ */
46
+ getCurrentUser(): AuthUser | null;
47
+
48
+ /**
49
+ * Subscribe to auth state changes
50
+ */
51
+ onAuthStateChange(callback: (user: AuthUser | null) => void): () => void;
52
+ }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Auth Service Interface
3
- * Port for authentication operations
3
+ * Port for authentication operations (provider-agnostic)
4
4
  */
5
5
 
6
- import type { User } from "firebase/auth";
6
+ import type { AuthUser } from "../../domain/entities/AuthUser";
7
7
 
8
8
  export interface SignUpParams {
9
9
  email: string;
@@ -20,12 +20,12 @@ export interface IAuthService {
20
20
  /**
21
21
  * Sign up a new user
22
22
  */
23
- signUp(params: SignUpParams): Promise<User>;
23
+ signUp(params: SignUpParams): Promise<AuthUser>;
24
24
 
25
25
  /**
26
26
  * Sign in an existing user
27
27
  */
28
- signIn(params: SignInParams): Promise<User>;
28
+ signIn(params: SignInParams): Promise<AuthUser>;
29
29
 
30
30
  /**
31
31
  * Sign out current user
@@ -40,12 +40,12 @@ export interface IAuthService {
40
40
  /**
41
41
  * Get current authenticated user
42
42
  */
43
- getCurrentUser(): User | null;
43
+ getCurrentUser(): AuthUser | null;
44
44
 
45
45
  /**
46
46
  * Subscribe to auth state changes
47
47
  */
48
- onAuthStateChange(callback: (user: User | null) => void): () => void;
48
+ onAuthStateChange(callback: (user: AuthUser | null) => void): () => void;
49
49
 
50
50
  /**
51
51
  * Check if auth is initialized
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Auth User Entity
3
+ * Provider-agnostic user representation
4
+ */
5
+
6
+ export interface AuthUser {
7
+ uid: string;
8
+ email: string | null;
9
+ displayName: string | null;
10
+ isAnonymous: boolean;
11
+ emailVerified: boolean;
12
+ photoURL: string | null;
13
+ }
@@ -3,24 +3,27 @@
3
3
  * Validates and stores authentication configuration
4
4
  */
5
5
 
6
+ export interface PasswordConfig {
7
+ minLength: number;
8
+ requireUppercase: boolean;
9
+ requireLowercase: boolean;
10
+ requireNumber: boolean;
11
+ requireSpecialChar: boolean;
12
+ }
13
+
6
14
  export interface AuthConfig {
7
- /** Minimum password length (default: 6) */
8
- minPasswordLength?: number;
9
- /** Require uppercase letters in password */
10
- requireUppercase?: boolean;
11
- /** Require lowercase letters in password */
12
- requireLowercase?: boolean;
13
- /** Require numbers in password */
14
- requireNumbers?: boolean;
15
- /** Require special characters in password */
16
- requireSpecialChars?: boolean;
15
+ password: PasswordConfig;
17
16
  }
18
17
 
19
- export const DEFAULT_AUTH_CONFIG: Required<AuthConfig> = {
20
- minPasswordLength: 6,
18
+ export const DEFAULT_PASSWORD_CONFIG: PasswordConfig = {
19
+ minLength: 6,
21
20
  requireUppercase: false,
22
21
  requireLowercase: false,
23
- requireNumbers: false,
24
- requireSpecialChars: false,
22
+ requireNumber: false,
23
+ requireSpecialChar: false,
24
+ };
25
+
26
+ export const DEFAULT_AUTH_CONFIG: AuthConfig = {
27
+ password: DEFAULT_PASSWORD_CONFIG,
25
28
  };
26
29
 
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * Architecture:
10
10
  * - domain: Entities, value objects, errors (business logic)
11
11
  * - application: Ports (interfaces)
12
- * - infrastructure: Auth service implementation
12
+ * - infrastructure: Auth providers and service implementation
13
13
  * - presentation: Hooks (React integration)
14
14
  *
15
15
  * Usage:
@@ -17,7 +17,13 @@
17
17
  */
18
18
 
19
19
  // =============================================================================
20
- // DOMAIN LAYER - Business Logic
20
+ // DOMAIN LAYER - Entities
21
+ // =============================================================================
22
+
23
+ export type { AuthUser } from './domain/entities/AuthUser';
24
+
25
+ // =============================================================================
26
+ // DOMAIN LAYER - Errors
21
27
  // =============================================================================
22
28
 
23
29
  export {
@@ -33,17 +39,32 @@ export {
33
39
  AuthInvalidEmailError,
34
40
  } from './domain/errors/AuthError';
35
41
 
36
- export type { AuthConfig } from './domain/value-objects/AuthConfig';
37
- export { DEFAULT_AUTH_CONFIG } from './domain/value-objects/AuthConfig';
42
+ // =============================================================================
43
+ // DOMAIN LAYER - Value Objects
44
+ // =============================================================================
45
+
46
+ export type { AuthConfig, PasswordConfig } from './domain/value-objects/AuthConfig';
47
+ export { DEFAULT_AUTH_CONFIG, DEFAULT_PASSWORD_CONFIG } from './domain/value-objects/AuthConfig';
38
48
 
39
49
  // =============================================================================
40
50
  // APPLICATION LAYER - Ports
41
51
  // =============================================================================
42
52
 
43
53
  export type { IAuthService, SignUpParams, SignInParams } from './application/ports/IAuthService';
54
+ export type {
55
+ IAuthProvider,
56
+ AuthCredentials,
57
+ SignUpCredentials,
58
+ } from './application/ports/IAuthProvider';
59
+
60
+ // =============================================================================
61
+ // INFRASTRUCTURE LAYER - Providers
62
+ // =============================================================================
63
+
64
+ export { FirebaseAuthProvider } from './infrastructure/providers/FirebaseAuthProvider';
44
65
 
45
66
  // =============================================================================
46
- // INFRASTRUCTURE LAYER - Implementation
67
+ // INFRASTRUCTURE LAYER - Services
47
68
  // =============================================================================
48
69
 
49
70
  export {
@@ -53,6 +74,24 @@ export {
53
74
  resetAuthService,
54
75
  } from './infrastructure/services/AuthService';
55
76
 
77
+ // =============================================================================
78
+ // INFRASTRUCTURE LAYER - Validation
79
+ // =============================================================================
80
+
81
+ export {
82
+ validateEmail,
83
+ validatePasswordForLogin,
84
+ validatePasswordForRegister,
85
+ validatePasswordConfirmation,
86
+ validateDisplayName,
87
+ } from './infrastructure/utils/AuthValidation';
88
+
89
+ export type {
90
+ ValidationResult,
91
+ PasswordStrengthResult,
92
+ PasswordRequirements,
93
+ } from './infrastructure/utils/AuthValidation';
94
+
56
95
  // =============================================================================
57
96
  // PRESENTATION LAYER - Hooks
58
97
  // =============================================================================
@@ -72,6 +111,7 @@ export type {
72
111
  AuthNavigatorProps,
73
112
  } from './presentation/navigation/AuthNavigator';
74
113
 
114
+ // =============================================================================
75
115
  // PRESENTATION LAYER - Components
76
116
  // =============================================================================
77
117
 
@@ -0,0 +1,131 @@
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
+ type User,
13
+ type Auth,
14
+ } from "firebase/auth";
15
+ import type {
16
+ IAuthProvider,
17
+ AuthCredentials,
18
+ SignUpCredentials,
19
+ } from "../../application/ports/IAuthProvider";
20
+ import type { AuthUser } from "../../domain/entities/AuthUser";
21
+ import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
22
+
23
+ export class FirebaseAuthProvider implements IAuthProvider {
24
+ private auth: Auth | null = null;
25
+
26
+ constructor(auth?: Auth) {
27
+ if (auth) {
28
+ this.auth = auth;
29
+ }
30
+ }
31
+
32
+ async initialize(): Promise<void> {
33
+ if (!this.auth) {
34
+ throw new Error("Firebase Auth instance must be provided");
35
+ }
36
+ }
37
+
38
+ setAuth(auth: Auth): void {
39
+ this.auth = auth;
40
+ }
41
+
42
+ isInitialized(): boolean {
43
+ return this.auth !== null;
44
+ }
45
+
46
+ async signIn(credentials: AuthCredentials): Promise<AuthUser> {
47
+ if (!this.auth) {
48
+ throw new Error("Firebase Auth is not initialized");
49
+ }
50
+
51
+ try {
52
+ const userCredential = await signInWithEmailAndPassword(
53
+ this.auth,
54
+ credentials.email.trim(),
55
+ credentials.password
56
+ );
57
+ return this.mapFirebaseUser(userCredential.user);
58
+ } catch (error: unknown) {
59
+ throw mapFirebaseAuthError(error);
60
+ }
61
+ }
62
+
63
+ async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
64
+ if (!this.auth) {
65
+ throw new Error("Firebase Auth is not initialized");
66
+ }
67
+
68
+ try {
69
+ const userCredential = await createUserWithEmailAndPassword(
70
+ this.auth,
71
+ credentials.email.trim(),
72
+ credentials.password
73
+ );
74
+
75
+ if (credentials.displayName && userCredential.user) {
76
+ try {
77
+ await updateProfile(userCredential.user, {
78
+ displayName: credentials.displayName.trim(),
79
+ });
80
+ } catch {
81
+ // Don't fail signup if display name update fails
82
+ }
83
+ }
84
+
85
+ return this.mapFirebaseUser(userCredential.user);
86
+ } catch (error: unknown) {
87
+ throw mapFirebaseAuthError(error);
88
+ }
89
+ }
90
+
91
+ async signOut(): Promise<void> {
92
+ if (!this.auth) {
93
+ return;
94
+ }
95
+
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?.currentUser) {
105
+ return null;
106
+ }
107
+ return this.mapFirebaseUser(this.auth.currentUser);
108
+ }
109
+
110
+ onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
111
+ if (!this.auth) {
112
+ callback(null);
113
+ return () => {};
114
+ }
115
+
116
+ return onAuthStateChanged(this.auth, (user) => {
117
+ callback(user ? this.mapFirebaseUser(user) : null);
118
+ });
119
+ }
120
+
121
+ private mapFirebaseUser(user: User): AuthUser {
122
+ return {
123
+ uid: user.uid,
124
+ email: user.email,
125
+ displayName: user.displayName,
126
+ isAnonymous: user.isAnonymous,
127
+ emailVerified: user.emailVerified,
128
+ photoURL: user.photoURL,
129
+ };
130
+ }
131
+ }
@@ -1,213 +1,159 @@
1
1
  /**
2
- * Auth Service Implementation
3
- * Secure Firebase Authentication wrapper
2
+ * Auth Service
3
+ * Provider-agnostic authentication orchestrator
4
4
  */
5
5
 
6
- import {
7
- createUserWithEmailAndPassword,
8
- signInWithEmailAndPassword,
9
- signOut as firebaseSignOut,
10
- onAuthStateChanged,
11
- updateProfile,
12
- type User,
13
- type Auth,
14
- } from "firebase/auth";
6
+ import type { Auth } from "firebase/auth";
15
7
  import type { IAuthService, SignUpParams, SignInParams } from "../../application/ports/IAuthService";
8
+ import type { IAuthProvider } from "../../application/ports/IAuthProvider";
9
+ import type { AuthUser } from "../../domain/entities/AuthUser";
10
+ import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
11
+ import { DEFAULT_AUTH_CONFIG } from "../../domain/value-objects/AuthConfig";
16
12
  import {
17
13
  AuthInitializationError,
18
14
  AuthValidationError,
19
15
  AuthWeakPasswordError,
20
16
  AuthInvalidEmailError,
21
17
  } from "../../domain/errors/AuthError";
22
- import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
23
- import { DEFAULT_AUTH_CONFIG } from "../../domain/value-objects/AuthConfig";
24
- import { validateEmail, validatePassword } from "../utils/AuthValidation";
25
- import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
18
+ import {
19
+ validateEmail,
20
+ validatePasswordForLogin,
21
+ validatePasswordForRegister,
22
+ validateDisplayName,
23
+ } from "../utils/AuthValidation";
24
+ import { FirebaseAuthProvider } from "../providers/FirebaseAuthProvider";
26
25
  import { emitUserAuthenticated, emitGuestModeEnabled } from "../utils/AuthEventEmitter";
27
26
  import { loadGuestMode, saveGuestMode, clearGuestMode } from "../storage/GuestModeStorage";
28
27
 
29
28
  export class AuthService implements IAuthService {
30
- private auth: Auth | null = null;
29
+ private provider: IAuthProvider | null = null;
31
30
  private config: AuthConfig;
32
31
  private isGuestMode: boolean = false;
33
32
 
34
- constructor(config: AuthConfig = {}) {
35
- this.config = { ...DEFAULT_AUTH_CONFIG, ...config };
33
+ constructor(config: Partial<AuthConfig> = {}) {
34
+ this.config = {
35
+ ...DEFAULT_AUTH_CONFIG,
36
+ ...config,
37
+ password: {
38
+ ...DEFAULT_AUTH_CONFIG.password,
39
+ ...config.password,
40
+ },
41
+ };
36
42
  }
37
43
 
38
- /**
39
- * Initialize auth service with Firebase Auth instance
40
- * Must be called before using any auth methods
41
- */
42
- async initialize(auth: Auth): Promise<void> {
43
- if (!auth) {
44
- throw new AuthInitializationError("Auth instance is required");
44
+ async initialize(providerOrAuth: IAuthProvider | Auth): Promise<void> {
45
+ if (!providerOrAuth) {
46
+ throw new AuthInitializationError("Auth provider or Firebase Auth instance is required");
45
47
  }
46
- this.auth = auth;
48
+
49
+ // Check if it's a Firebase Auth instance (has currentUser property)
50
+ if ("currentUser" in providerOrAuth) {
51
+ const firebaseProvider = new FirebaseAuthProvider(providerOrAuth as Auth);
52
+ await firebaseProvider.initialize();
53
+ this.provider = firebaseProvider;
54
+ } else {
55
+ this.provider = providerOrAuth as IAuthProvider;
56
+ await this.provider.initialize();
57
+ }
58
+
47
59
  this.isGuestMode = await loadGuestMode();
48
60
  }
49
61
 
50
- /**
51
- * Check if auth is initialized
52
- */
53
62
  isInitialized(): boolean {
54
- return this.auth !== null;
63
+ return this.provider !== null && this.provider.isInitialized();
55
64
  }
56
65
 
57
- private getAuth(): Auth | null {
58
- if (!this.auth) {
59
- /* eslint-disable-next-line no-console */
60
- if (__DEV__) {
61
- console.warn("Auth service is not initialized. Call initialize() first.");
62
- }
63
- return null;
66
+ private getProvider(): IAuthProvider {
67
+ if (!this.provider || !this.provider.isInitialized()) {
68
+ throw new AuthInitializationError("Auth service is not initialized");
64
69
  }
65
- return this.auth;
70
+ return this.provider;
66
71
  }
67
72
 
68
- /**
69
- * Sign up a new user
70
- */
71
- async signUp(params: SignUpParams): Promise<User> {
72
- const auth = this.getAuth();
73
- if (!auth) {
74
- throw new AuthInitializationError("Auth service is not initialized");
75
- }
73
+ async signUp(params: SignUpParams): Promise<AuthUser> {
74
+ const provider = this.getProvider();
76
75
 
77
76
  // Validate email
78
- if (!params.email || !validateEmail(params.email)) {
79
- throw new AuthInvalidEmailError();
80
- }
81
-
82
- // Validate password
83
- const passwordValidation = validatePassword(params.password, this.config as any);
84
- if (!passwordValidation.valid) {
85
- throw new AuthWeakPasswordError(passwordValidation.error);
77
+ const emailResult = validateEmail(params.email);
78
+ if (!emailResult.isValid) {
79
+ throw new AuthInvalidEmailError(emailResult.error);
86
80
  }
87
81
 
88
- try {
89
- // Create user
90
- const userCredential = await createUserWithEmailAndPassword(
91
- auth,
92
- params.email.trim(),
93
- params.password
94
- );
95
-
96
- // Update display name if provided
97
- if (params.displayName && userCredential.user) {
98
- try {
99
- await updateProfile(userCredential.user, {
100
- displayName: params.displayName.trim(),
101
- });
102
- } catch (updateError) {
103
- // Don't fail signup if display name update fails
104
- // User can still use the app
105
- }
82
+ // Validate display name if provided
83
+ if (params.displayName) {
84
+ const nameResult = validateDisplayName(params.displayName);
85
+ if (!nameResult.isValid) {
86
+ throw new AuthValidationError(nameResult.error || "Invalid name", "displayName");
106
87
  }
88
+ }
107
89
 
108
- // Clear guest mode when user signs up
109
- if (this.isGuestMode) {
110
- this.isGuestMode = false;
111
- await clearGuestMode();
112
- }
90
+ // Validate password strength for registration
91
+ const passwordResult = validatePasswordForRegister(params.password, this.config.password);
92
+ if (!passwordResult.isValid) {
93
+ throw new AuthWeakPasswordError(passwordResult.error);
94
+ }
113
95
 
114
- emitUserAuthenticated(userCredential.user.uid);
96
+ const user = await provider.signUp({
97
+ email: params.email,
98
+ password: params.password,
99
+ displayName: params.displayName,
100
+ });
115
101
 
116
- return userCredential.user;
117
- } catch (error: any) {
118
- throw mapFirebaseAuthError(error);
102
+ // Clear guest mode when user signs up
103
+ if (this.isGuestMode) {
104
+ this.isGuestMode = false;
105
+ await clearGuestMode();
119
106
  }
107
+
108
+ emitUserAuthenticated(user.uid);
109
+ return user;
120
110
  }
121
111
 
122
- /**
123
- * Sign in an existing user
124
- */
125
- async signIn(params: SignInParams): Promise<User> {
126
- /* eslint-disable-next-line no-console */
127
- if (__DEV__) {
128
- console.log("[AuthService] signIn called with email:", params.email);
129
- }
130
- const auth = this.getAuth();
131
- if (!auth) {
132
- throw new AuthInitializationError("Auth service is not initialized");
133
- }
112
+ async signIn(params: SignInParams): Promise<AuthUser> {
113
+ const provider = this.getProvider();
134
114
 
135
- // Validate email
136
- if (!params.email || !validateEmail(params.email)) {
137
- throw new AuthInvalidEmailError();
115
+ // Validate email format
116
+ const emailResult = validateEmail(params.email);
117
+ if (!emailResult.isValid) {
118
+ throw new AuthInvalidEmailError(emailResult.error);
138
119
  }
139
120
 
140
- // Validate password
141
- if (!params.password || params.password.length === 0) {
142
- throw new AuthValidationError("Password is required", "password");
121
+ // For login, only check if password is provided (no strength requirements)
122
+ const passwordResult = validatePasswordForLogin(params.password);
123
+ if (!passwordResult.isValid) {
124
+ throw new AuthValidationError(passwordResult.error || "Password is required", "password");
143
125
  }
144
126
 
145
- try {
146
- /* eslint-disable-next-line no-console */
147
- if (__DEV__) {
148
- console.log("[AuthService] Calling signInWithEmailAndPassword");
149
- }
150
- const userCredential = await signInWithEmailAndPassword(
151
- auth,
152
- params.email.trim(),
153
- params.password
154
- );
155
-
156
- /* eslint-disable-next-line no-console */
157
- if (__DEV__) {
158
- console.log("[AuthService] signInWithEmailAndPassword successful, user:", userCredential.user.uid);
159
- }
160
-
161
- // Clear guest mode when user signs in
162
- if (this.isGuestMode) {
163
- this.isGuestMode = false;
164
- await clearGuestMode();
165
- }
166
-
167
- emitUserAuthenticated(userCredential.user.uid);
127
+ const user = await provider.signIn({
128
+ email: params.email,
129
+ password: params.password,
130
+ });
168
131
 
169
- return userCredential.user;
170
- } catch (error: any) {
171
- /* eslint-disable-next-line no-console */
172
- if (__DEV__) {
173
- console.error("[AuthService] signIn error:", error);
174
- }
175
- throw mapFirebaseAuthError(error);
132
+ // Clear guest mode when user signs in
133
+ if (this.isGuestMode) {
134
+ this.isGuestMode = false;
135
+ await clearGuestMode();
176
136
  }
137
+
138
+ emitUserAuthenticated(user.uid);
139
+ return user;
177
140
  }
178
141
 
179
- /**
180
- * Sign out current user
181
- */
182
142
  async signOut(): Promise<void> {
183
- const auth = this.getAuth();
184
- if (!auth) {
143
+ if (!this.provider) {
185
144
  this.isGuestMode = false;
186
145
  await clearGuestMode();
187
146
  return;
188
147
  }
189
148
 
190
- try {
191
- await firebaseSignOut(auth);
192
- } catch (error: any) {
193
- throw mapFirebaseAuthError(error);
194
- }
149
+ await this.provider.signOut();
195
150
  }
196
151
 
197
- /**
198
- * Set guest mode (no authentication)
199
- */
200
152
  async setGuestMode(): Promise<void> {
201
- /* eslint-disable-next-line no-console */
202
- if (__DEV__) {
203
- console.log("[AuthService] setGuestMode called");
204
- }
205
- const auth = this.getAuth();
206
-
207
- // Sign out from Firebase if logged in
208
- if (auth?.currentUser) {
153
+ // Sign out from provider if logged in
154
+ if (this.provider?.getCurrentUser()) {
209
155
  try {
210
- await firebaseSignOut(auth);
156
+ await this.provider.signOut();
211
157
  } catch {
212
158
  // Ignore sign out errors when switching to guest mode
213
159
  }
@@ -218,35 +164,24 @@ export class AuthService implements IAuthService {
218
164
  emitGuestModeEnabled();
219
165
  }
220
166
 
221
- /**
222
- * Get current authenticated user
223
- */
224
- getCurrentUser(): User | null {
225
- if (!this.auth) {
167
+ getCurrentUser(): AuthUser | null {
168
+ if (!this.provider) {
226
169
  return null;
227
170
  }
228
- return this.auth.currentUser;
171
+ return this.provider.getCurrentUser();
229
172
  }
230
173
 
231
- /**
232
- * Check if user is in guest mode
233
- */
234
174
  getIsGuestMode(): boolean {
235
175
  return this.isGuestMode;
236
176
  }
237
177
 
238
- /**
239
- * Subscribe to auth state changes
240
- */
241
- onAuthStateChange(callback: (user: User | null) => void): () => void {
242
- const auth = this.getAuth();
243
- if (!auth) {
244
- // Return no-op unsubscribe if auth is not initialized
178
+ onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
179
+ if (!this.provider) {
245
180
  callback(null);
246
181
  return () => {};
247
182
  }
248
183
 
249
- return onAuthStateChanged(auth, (user) => {
184
+ return this.provider.onAuthStateChange((user) => {
250
185
  // Don't update if in guest mode
251
186
  if (!this.isGuestMode) {
252
187
  callback(user);
@@ -255,54 +190,34 @@ export class AuthService implements IAuthService {
255
190
  }
256
191
  });
257
192
  }
193
+
194
+ getConfig(): AuthConfig {
195
+ return this.config;
196
+ }
258
197
  }
259
198
 
260
- /**
261
- * Singleton instance
262
- * Apps should use initializeAuthService() to set up with their Firebase Auth instance
263
- */
199
+ // Singleton instance
264
200
  let authServiceInstance: AuthService | null = null;
265
201
 
266
202
  /**
267
- * Initialize auth service with Firebase Auth instance
268
- * Must be called before using any auth methods
269
- *
270
- * Uses DEFAULT_AUTH_CONFIG if no config is provided:
271
- * - minPasswordLength: 6
272
- * - requireUppercase: false
273
- * - requireLowercase: false
274
- * - requireNumbers: false
275
- * - requireSpecialChars: false
276
- *
277
- * @param auth - Firebase Auth instance
278
- * @param config - Optional auth configuration (defaults to permissive settings)
203
+ * Initialize auth service with provider or Firebase Auth instance
279
204
  */
280
205
  export async function initializeAuthService(
281
- auth: Auth,
282
- config?: AuthConfig
206
+ providerOrAuth: IAuthProvider | Auth,
207
+ config?: Partial<AuthConfig>
283
208
  ): Promise<AuthService> {
284
- // Use default config if not provided (permissive settings for better UX)
285
- const finalConfig = config || DEFAULT_AUTH_CONFIG;
286
-
287
209
  if (!authServiceInstance) {
288
- authServiceInstance = new AuthService(finalConfig);
210
+ authServiceInstance = new AuthService(config);
289
211
  }
290
- await authServiceInstance.initialize(auth);
212
+ await authServiceInstance.initialize(providerOrAuth);
291
213
  return authServiceInstance;
292
214
  }
293
215
 
294
216
  /**
295
217
  * Get auth service instance
296
- * Returns null if service is not initialized (graceful degradation)
297
218
  */
298
219
  export function getAuthService(): AuthService | null {
299
220
  if (!authServiceInstance || !authServiceInstance.isInitialized()) {
300
- /* eslint-disable-next-line no-console */
301
- if (__DEV__) {
302
- console.warn(
303
- "Auth service is not initialized. Call initializeAuthService() first."
304
- );
305
- }
306
221
  return null;
307
222
  }
308
223
  return authServiceInstance;
@@ -314,4 +229,3 @@ export function getAuthService(): AuthService | null {
314
229
  export function resetAuthService(): void {
315
230
  authServiceInstance = null;
316
231
  }
317
-
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Manage guest mode persistence
4
4
  */
5
5
 
6
- import { storageRepository } from "@umituz/react-native-storage";
6
+ import { storageRepository, unwrap } from "@umituz/react-native-storage";
7
7
 
8
8
  const GUEST_MODE_KEY = "@auth_guest_mode";
9
9
 
@@ -12,7 +12,8 @@ const GUEST_MODE_KEY = "@auth_guest_mode";
12
12
  */
13
13
  export async function loadGuestMode(): Promise<boolean> {
14
14
  try {
15
- const guestModeValue = await storageRepository.getString(GUEST_MODE_KEY, "false");
15
+ const result = await storageRepository.getString(GUEST_MODE_KEY, "false");
16
+ const guestModeValue = unwrap(result, "false");
16
17
  return guestModeValue === "true";
17
18
  } catch {
18
19
  return false;
@@ -17,41 +17,85 @@ import {
17
17
  /**
18
18
  * Map Firebase Auth errors to domain errors
19
19
  */
20
- export function mapFirebaseAuthError(error: any): Error {
21
- const code = error?.code || "";
22
- const message = error?.message || "Authentication failed";
20
+ export function mapFirebaseAuthError(error: unknown): Error {
21
+ const errorObj = error as { code?: string; message?: string } | undefined;
22
+ const code = errorObj?.code || "";
23
+ const message = errorObj?.message || "Authentication failed";
23
24
 
24
- if (code === "auth/email-already-in-use") {
25
- return new AuthEmailAlreadyInUseError();
26
- }
27
- if (code === "auth/invalid-email") {
28
- return new AuthInvalidEmailError();
29
- }
30
- if (code === "auth/weak-password") {
31
- return new AuthWeakPasswordError();
32
- }
33
- if (code === "auth/user-disabled") {
34
- return new AuthError("User account has been disabled", "AUTH_USER_DISABLED");
35
- }
36
- if (code === "auth/user-not-found") {
37
- return new AuthUserNotFoundError();
38
- }
39
- if (code === "auth/wrong-password") {
40
- return new AuthWrongPasswordError();
41
- }
42
- if (code === "auth/network-request-failed") {
43
- return new AuthNetworkError();
44
- }
45
- if (code === "auth/too-many-requests") {
46
- return new AuthError("Too many requests. Please try again later.", "AUTH_TOO_MANY_REQUESTS");
47
- }
48
- if (code === "auth/configuration-not-found" || code === "auth/app-not-authorized") {
49
- return new AuthConfigurationError("Authentication is not properly configured. Please contact support.");
50
- }
51
- if (code === "auth/operation-not-allowed") {
52
- return new AuthConfigurationError("Email/password authentication is not enabled. Please contact support.");
53
- }
25
+ switch (code) {
26
+ case "auth/email-already-in-use":
27
+ return new AuthEmailAlreadyInUseError();
28
+
29
+ case "auth/invalid-email":
30
+ return new AuthInvalidEmailError();
31
+
32
+ case "auth/weak-password":
33
+ return new AuthWeakPasswordError();
34
+
35
+ case "auth/user-disabled":
36
+ return new AuthError(
37
+ "Your account has been disabled. Please contact support.",
38
+ "AUTH_USER_DISABLED"
39
+ );
54
40
 
55
- return new AuthError(message, code);
41
+ case "auth/user-not-found":
42
+ return new AuthUserNotFoundError();
43
+
44
+ case "auth/wrong-password":
45
+ return new AuthWrongPasswordError();
46
+
47
+ case "auth/invalid-credential":
48
+ return new AuthError(
49
+ "Invalid email or password. Please check your credentials.",
50
+ "AUTH_INVALID_CREDENTIAL"
51
+ );
52
+
53
+ case "auth/invalid-login-credentials":
54
+ return new AuthError(
55
+ "Invalid email or password. Please check your credentials.",
56
+ "AUTH_INVALID_CREDENTIAL"
57
+ );
58
+
59
+ case "auth/network-request-failed":
60
+ return new AuthNetworkError();
61
+
62
+ case "auth/too-many-requests":
63
+ return new AuthError(
64
+ "Too many failed attempts. Please wait a few minutes and try again.",
65
+ "AUTH_TOO_MANY_REQUESTS"
66
+ );
67
+
68
+ case "auth/configuration-not-found":
69
+ case "auth/app-not-authorized":
70
+ return new AuthConfigurationError(
71
+ "Authentication is not properly configured. Please contact support."
72
+ );
73
+
74
+ case "auth/operation-not-allowed":
75
+ return new AuthConfigurationError(
76
+ "Email/password authentication is not enabled. Please contact support."
77
+ );
78
+
79
+ case "auth/requires-recent-login":
80
+ return new AuthError(
81
+ "Please sign in again to complete this action.",
82
+ "AUTH_REQUIRES_RECENT_LOGIN"
83
+ );
84
+
85
+ case "auth/expired-action-code":
86
+ return new AuthError(
87
+ "This link has expired. Please request a new one.",
88
+ "AUTH_EXPIRED_ACTION_CODE"
89
+ );
90
+
91
+ case "auth/invalid-action-code":
92
+ return new AuthError(
93
+ "This link is invalid. Please request a new one.",
94
+ "AUTH_INVALID_ACTION_CODE"
95
+ );
96
+
97
+ default:
98
+ return new AuthError(message, code || "AUTH_UNKNOWN_ERROR");
99
+ }
56
100
  }
57
101
 
@@ -1,60 +1,164 @@
1
1
  /**
2
2
  * Auth Validation Utilities
3
- * Single Responsibility: Email and password validation
3
+ * Single Responsibility: Email and password validation for authentication
4
4
  */
5
5
 
6
- import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
6
+ import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
7
+
8
+ export interface ValidationResult {
9
+ isValid: boolean;
10
+ error?: string;
11
+ }
12
+
13
+ export interface PasswordStrengthResult extends ValidationResult {
14
+ requirements: PasswordRequirements;
15
+ }
16
+
17
+ export interface PasswordRequirements {
18
+ hasMinLength: boolean;
19
+ hasUppercase: boolean;
20
+ hasLowercase: boolean;
21
+ hasNumber: boolean;
22
+ hasSpecialChar: boolean;
23
+ }
24
+
25
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
26
+ const UPPERCASE_REGEX = /[A-Z]/;
27
+ const LOWERCASE_REGEX = /[a-z]/;
28
+ const NUMBER_REGEX = /[0-9]/;
29
+ const SPECIAL_CHAR_REGEX = /[!@#$%^&*(),.?":{}|<>]/;
7
30
 
8
31
  /**
9
32
  * Validate email format
10
33
  */
11
- export function validateEmail(email: string): boolean {
12
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
13
- return emailRegex.test(email.trim());
34
+ export function validateEmail(email: string): ValidationResult {
35
+ if (!email || email.trim() === "") {
36
+ return { isValid: false, error: "Email is required" };
37
+ }
38
+
39
+ if (!EMAIL_REGEX.test(email.trim())) {
40
+ return { isValid: false, error: "Please enter a valid email address" };
41
+ }
42
+
43
+ return { isValid: true };
14
44
  }
15
45
 
16
46
  /**
17
- * Validate password strength
47
+ * Validate password for login - only checks if password is provided
48
+ * No strength requirements for login (existing users may have old passwords)
18
49
  */
19
- export function validatePassword(
50
+ export function validatePasswordForLogin(password: string): ValidationResult {
51
+ if (!password || password.length === 0) {
52
+ return { isValid: false, error: "Password is required" };
53
+ }
54
+
55
+ return { isValid: true };
56
+ }
57
+
58
+ /**
59
+ * Validate password strength for registration
60
+ * Returns detailed requirements for UI feedback
61
+ */
62
+ export function validatePasswordForRegister(
20
63
  password: string,
21
- config: Required<Omit<AuthConfig, "onUserCreated" | "onUserUpdated" | "onSignOut">>
22
- ): { valid: boolean; error?: string } {
23
- if (password.length < config.minPasswordLength) {
64
+ config: PasswordConfig
65
+ ): PasswordStrengthResult {
66
+ const requirements: PasswordRequirements = {
67
+ hasMinLength: password.length >= config.minLength,
68
+ hasUppercase: !config.requireUppercase || UPPERCASE_REGEX.test(password),
69
+ hasLowercase: !config.requireLowercase || LOWERCASE_REGEX.test(password),
70
+ hasNumber: !config.requireNumber || NUMBER_REGEX.test(password),
71
+ hasSpecialChar:
72
+ !config.requireSpecialChar || SPECIAL_CHAR_REGEX.test(password),
73
+ };
74
+
75
+ if (!password || password.length === 0) {
24
76
  return {
25
- valid: false,
26
- error: `Password must be at least ${config.minPasswordLength} characters long`,
77
+ isValid: false,
78
+ error: "Password is required",
79
+ requirements,
27
80
  };
28
81
  }
29
82
 
30
- if (config.requireUppercase && !/[A-Z]/.test(password)) {
83
+ if (!requirements.hasMinLength) {
31
84
  return {
32
- valid: false,
85
+ isValid: false,
86
+ error: `Password must be at least ${config.minLength} characters`,
87
+ requirements,
88
+ };
89
+ }
90
+
91
+ if (config.requireUppercase && !UPPERCASE_REGEX.test(password)) {
92
+ return {
93
+ isValid: false,
33
94
  error: "Password must contain at least one uppercase letter",
95
+ requirements,
34
96
  };
35
97
  }
36
98
 
37
- if (config.requireLowercase && !/[a-z]/.test(password)) {
99
+ if (config.requireLowercase && !LOWERCASE_REGEX.test(password)) {
38
100
  return {
39
- valid: false,
101
+ isValid: false,
40
102
  error: "Password must contain at least one lowercase letter",
103
+ requirements,
41
104
  };
42
105
  }
43
106
 
44
- if (config.requireNumbers && !/[0-9]/.test(password)) {
107
+ if (config.requireNumber && !NUMBER_REGEX.test(password)) {
45
108
  return {
46
- valid: false,
109
+ isValid: false,
47
110
  error: "Password must contain at least one number",
111
+ requirements,
48
112
  };
49
113
  }
50
114
 
51
- if (config.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
115
+ if (config.requireSpecialChar && !SPECIAL_CHAR_REGEX.test(password)) {
52
116
  return {
53
- valid: false,
117
+ isValid: false,
54
118
  error: "Password must contain at least one special character",
119
+ requirements,
120
+ };
121
+ }
122
+
123
+ return { isValid: true, requirements };
124
+ }
125
+
126
+ /**
127
+ * Validate password confirmation
128
+ */
129
+ export function validatePasswordConfirmation(
130
+ password: string,
131
+ confirmPassword: string
132
+ ): ValidationResult {
133
+ if (!confirmPassword) {
134
+ return { isValid: false, error: "Please confirm your password" };
135
+ }
136
+
137
+ if (password !== confirmPassword) {
138
+ return { isValid: false, error: "Passwords do not match" };
139
+ }
140
+
141
+ return { isValid: true };
142
+ }
143
+
144
+ /**
145
+ * Validate display name
146
+ */
147
+ export function validateDisplayName(
148
+ displayName: string,
149
+ minLength: number = 2
150
+ ): ValidationResult {
151
+ if (!displayName || displayName.trim() === "") {
152
+ return { isValid: false, error: "Name is required" };
153
+ }
154
+
155
+ if (displayName.trim().length < minLength) {
156
+ return {
157
+ isValid: false,
158
+ error: `Name must be at least ${minLength} characters`,
55
159
  };
56
160
  }
57
161
 
58
- return { valid: true };
162
+ return { isValid: true };
59
163
  }
60
164
 
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * useAuth Hook
3
3
  * React hook for authentication state management
4
- *
5
- * Uses Firebase Auth's built-in state management via useFirebaseAuth hook.
6
- * Adds app-specific state (guest mode, error handling) on top of Firebase Auth.
4
+ *
5
+ * Uses provider-agnostic AuthUser type.
6
+ * Adds app-specific state (guest mode, error handling) on top of provider auth.
7
7
  */
8
8
 
9
- import type { User } from "firebase/auth";
10
9
  import { useAuthState } from "./useAuthState";
11
10
  import { useAuthActions } from "./useAuthActions";
11
+ import type { AuthUser } from "../../domain/entities/AuthUser";
12
12
 
13
13
  export interface UseAuthResult {
14
14
  /** Current authenticated user */
15
- user: User | null;
15
+ user: AuthUser | null;
16
16
  /** Whether auth state is loading */
17
17
  loading: boolean;
18
18
  /** Whether user is in guest mode */
@@ -4,13 +4,27 @@
4
4
  */
5
5
 
6
6
  import { useState, useEffect } from "react";
7
- import type { User } from "firebase/auth";
8
7
  import { DeviceEventEmitter } from "react-native";
9
8
  import { getAuthService } from "../../infrastructure/services/AuthService";
10
9
  import { useFirebaseAuth } from "@umituz/react-native-firebase-auth";
10
+ import type { AuthUser } from "../../domain/entities/AuthUser";
11
+
12
+ type FirebaseUser = ReturnType<typeof useFirebaseAuth>["user"];
13
+
14
+ function mapToAuthUser(user: FirebaseUser): AuthUser | null {
15
+ if (!user) return null;
16
+ return {
17
+ uid: user.uid,
18
+ email: user.email,
19
+ displayName: user.displayName,
20
+ isAnonymous: user.isAnonymous,
21
+ emailVerified: user.emailVerified,
22
+ photoURL: user.photoURL,
23
+ };
24
+ }
11
25
 
12
26
  export interface UseAuthStateResult {
13
- user: User | null;
27
+ user: AuthUser | null;
14
28
  isAuthenticated: boolean;
15
29
  isGuest: boolean;
16
30
  loading: boolean;
@@ -32,7 +46,7 @@ export function useAuthState(): UseAuthStateResult {
32
46
  const [error, setError] = useState<string | null>(null);
33
47
  const [loading, setLoading] = useState(false);
34
48
 
35
- const user = isGuest ? null : firebaseUser;
49
+ const user = isGuest ? null : mapToAuthUser(firebaseUser);
36
50
  const isAuthenticated = !!user && !isGuest;
37
51
 
38
52
  // Reset guest mode when user signs in