@umituz/react-native-auth 1.0.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ümit UZ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # @umituz/react-native-auth
2
+
3
+ Firebase Authentication wrapper for React Native apps - Secure, type-safe, and production-ready.
4
+
5
+ Built with **SOLID**, **DRY**, and **KISS** principles.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @umituz/react-native-auth
11
+ ```
12
+
13
+ ## Peer Dependencies
14
+
15
+ - `firebase` >= 11.0.0
16
+ - `react` >= 18.2.0
17
+ - `react-native` >= 0.74.0
18
+
19
+ ## Features
20
+
21
+ - ✅ Domain-Driven Design (DDD) architecture
22
+ - ✅ SOLID principles (Single Responsibility, Open/Closed, etc.)
23
+ - ✅ DRY (Don't Repeat Yourself)
24
+ - ✅ KISS (Keep It Simple, Stupid)
25
+ - ✅ **Security**: Password validation, email validation, error handling
26
+ - ✅ Type-safe operations
27
+ - ✅ Guest mode support
28
+ - ✅ React hooks for easy integration
29
+ - ✅ Works with Expo and React Native CLI
30
+
31
+ ## Important: Security First
32
+
33
+ **This package prioritizes security:**
34
+
35
+ - Password strength validation
36
+ - Email format validation
37
+ - Secure error handling (no sensitive data exposure)
38
+ - Firebase Auth best practices
39
+ - Guest mode support for offline-first apps
40
+
41
+ ## Usage
42
+
43
+ ### 1. Initialize Auth Service
44
+
45
+ Initialize the service early in your app (e.g., in `App.tsx`):
46
+
47
+ ```typescript
48
+ import { initializeAuthService } from '@umituz/react-native-auth';
49
+ import { getFirebaseAuth } from '@umituz/react-native-firebase';
50
+
51
+ // Initialize Firebase first (using @umituz/react-native-firebase)
52
+ const auth = getFirebaseAuth();
53
+
54
+ // Initialize auth service
55
+ initializeAuthService(auth, {
56
+ minPasswordLength: 6,
57
+ requireUppercase: false,
58
+ requireLowercase: false,
59
+ requireNumbers: false,
60
+ requireSpecialChars: false,
61
+ onUserCreated: async (user) => {
62
+ // Optional: Create user profile in your database
63
+ console.log('User created:', user.uid);
64
+ },
65
+ onSignOut: async () => {
66
+ // Optional: Cleanup on sign out
67
+ console.log('User signed out');
68
+ },
69
+ });
70
+ ```
71
+
72
+ ### 2. Use Auth Hook in Components
73
+
74
+ ```typescript
75
+ import { useAuth } from '@umituz/react-native-auth';
76
+
77
+ function LoginScreen() {
78
+ const { user, isAuthenticated, isGuest, loading, signIn, signUp, signOut, continueAsGuest } = useAuth();
79
+
80
+ if (loading) {
81
+ return <LoadingSpinner />;
82
+ }
83
+
84
+ if (isAuthenticated) {
85
+ return <HomeScreen user={user} />;
86
+ }
87
+
88
+ if (isGuest) {
89
+ return <GuestHomeScreen />;
90
+ }
91
+
92
+ return <LoginForm onSignIn={signIn} onSignUp={signUp} onContinueAsGuest={continueAsGuest} />;
93
+ }
94
+ ```
95
+
96
+ ### 3. Sign Up
97
+
98
+ ```typescript
99
+ import { getAuthService } from '@umituz/react-native-auth';
100
+
101
+ const authService = getAuthService();
102
+
103
+ try {
104
+ const user = await authService.signUp({
105
+ email: 'user@example.com',
106
+ password: 'securepassword123',
107
+ displayName: 'John Doe',
108
+ });
109
+ console.log('User signed up:', user.uid);
110
+ } catch (error) {
111
+ if (error instanceof AuthEmailAlreadyInUseError) {
112
+ console.error('Email already in use');
113
+ } else if (error instanceof AuthWeakPasswordError) {
114
+ console.error('Password is too weak');
115
+ } else {
116
+ console.error('Sign up failed:', error.message);
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### 4. Sign In
122
+
123
+ ```typescript
124
+ try {
125
+ const user = await authService.signIn({
126
+ email: 'user@example.com',
127
+ password: 'securepassword123',
128
+ });
129
+ console.log('User signed in:', user.uid);
130
+ } catch (error) {
131
+ if (error instanceof AuthWrongPasswordError) {
132
+ console.error('Wrong password');
133
+ } else if (error instanceof AuthUserNotFoundError) {
134
+ console.error('User not found');
135
+ } else {
136
+ console.error('Sign in failed:', error.message);
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### 5. Sign Out
142
+
143
+ ```typescript
144
+ await authService.signOut();
145
+ ```
146
+
147
+ ### 6. Guest Mode
148
+
149
+ ```typescript
150
+ await authService.setGuestMode();
151
+ ```
152
+
153
+ ## API
154
+
155
+ ### Functions
156
+
157
+ - `initializeAuthService(auth, config?)`: Initialize auth service with Firebase Auth instance
158
+ - `getAuthService()`: Get auth service instance (throws if not initialized)
159
+ - `resetAuthService()`: Reset service instance (useful for testing)
160
+
161
+ ### Hook
162
+
163
+ - `useAuth()`: React hook for authentication state management
164
+
165
+ ### Types
166
+
167
+ - `AuthConfig`: Configuration interface
168
+ - `SignUpParams`: Sign up parameters
169
+ - `SignInParams`: Sign in parameters
170
+ - `UseAuthResult`: Hook return type
171
+
172
+ ### Errors
173
+
174
+ - `AuthError`: Base error class
175
+ - `AuthInitializationError`: Initialization errors
176
+ - `AuthConfigurationError`: Configuration errors
177
+ - `AuthValidationError`: Validation errors
178
+ - `AuthNetworkError`: Network errors
179
+ - `AuthUserNotFoundError`: User not found
180
+ - `AuthWrongPasswordError`: Wrong password
181
+ - `AuthEmailAlreadyInUseError`: Email already in use
182
+ - `AuthWeakPasswordError`: Weak password
183
+ - `AuthInvalidEmailError`: Invalid email
184
+
185
+ ## Security Best Practices
186
+
187
+ 1. **Password Validation**: Configure password requirements based on your app's security needs
188
+ 2. **Error Handling**: Always handle errors gracefully without exposing sensitive information
189
+ 3. **Guest Mode**: Use guest mode for offline-first apps that don't require authentication
190
+ 4. **User Callbacks**: Use `onUserCreated` and `onSignOut` callbacks for app-specific logic
191
+
192
+ ## Integration with @umituz/react-native-firebase
193
+
194
+ This package works seamlessly with `@umituz/react-native-firebase`:
195
+
196
+ ```typescript
197
+ import { initializeFirebase, getFirebaseAuth } from '@umituz/react-native-firebase';
198
+ import { initializeAuthService } from '@umituz/react-native-auth';
199
+
200
+ // Initialize Firebase
201
+ const config = {
202
+ apiKey: 'your-api-key',
203
+ authDomain: 'your-project.firebaseapp.com',
204
+ projectId: 'your-project-id',
205
+ };
206
+ initializeFirebase(config);
207
+
208
+ // Initialize Auth Service
209
+ const auth = getFirebaseAuth();
210
+ initializeAuthService(auth);
211
+ ```
212
+
213
+ ## License
214
+
215
+ MIT
216
+
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@umituz/react-native-auth",
3
+ "version": "1.0.0",
4
+ "description": "Firebase Authentication wrapper for React Native apps - Secure, type-safe, and production-ready",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit",
9
+ "lint": "tsc --noEmit",
10
+ "version:patch": "npm version patch -m 'chore: release v%s'",
11
+ "version:minor": "npm version minor -m 'chore: release v%s'",
12
+ "version:major": "npm version major -m 'chore: release v%s'"
13
+ },
14
+ "keywords": [
15
+ "react-native",
16
+ "firebase",
17
+ "authentication",
18
+ "auth",
19
+ "security",
20
+ "ddd",
21
+ "domain-driven-design",
22
+ "type-safe",
23
+ "solid",
24
+ "dry",
25
+ "kiss"
26
+ ],
27
+ "author": "Ümit UZ <umit@umituz.com>",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/umituz/react-native-auth.git"
32
+ },
33
+ "peerDependencies": {
34
+ "firebase": ">=11.0.0",
35
+ "react": ">=18.2.0",
36
+ "react-native": ">=0.74.0"
37
+ },
38
+ "devDependencies": {
39
+ "firebase": "^11.10.0",
40
+ "@types/react": "^18.2.45",
41
+ "@types/react-native": "^0.73.0",
42
+ "react": "^18.2.0",
43
+ "react-native": "^0.74.0",
44
+ "typescript": "^5.3.3"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "files": [
50
+ "src",
51
+ "README.md",
52
+ "LICENSE"
53
+ ]
54
+ }
55
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Auth Service Interface
3
+ * Port for authentication operations
4
+ */
5
+
6
+ import type { User } from "firebase/auth";
7
+
8
+ export interface SignUpParams {
9
+ email: string;
10
+ password: string;
11
+ displayName?: string;
12
+ }
13
+
14
+ export interface SignInParams {
15
+ email: string;
16
+ password: string;
17
+ }
18
+
19
+ export interface IAuthService {
20
+ /**
21
+ * Sign up a new user
22
+ */
23
+ signUp(params: SignUpParams): Promise<User>;
24
+
25
+ /**
26
+ * Sign in an existing user
27
+ */
28
+ signIn(params: SignInParams): Promise<User>;
29
+
30
+ /**
31
+ * Sign out current user
32
+ */
33
+ signOut(): Promise<void>;
34
+
35
+ /**
36
+ * Set guest mode (no authentication)
37
+ */
38
+ setGuestMode(): Promise<void>;
39
+
40
+ /**
41
+ * Get current authenticated user
42
+ */
43
+ getCurrentUser(): User | null;
44
+
45
+ /**
46
+ * Subscribe to auth state changes
47
+ */
48
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
49
+
50
+ /**
51
+ * Check if auth is initialized
52
+ */
53
+ isInitialized(): boolean;
54
+ }
55
+
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Auth Error Types
3
+ * Domain-specific error classes for authentication operations
4
+ */
5
+
6
+ export class AuthError extends Error {
7
+ constructor(message: string, public code?: string) {
8
+ super(message);
9
+ this.name = "AuthError";
10
+ Object.setPrototypeOf(this, AuthError.prototype);
11
+ }
12
+ }
13
+
14
+ export class AuthInitializationError extends AuthError {
15
+ constructor(message: string = "Firebase Auth is not initialized") {
16
+ super(message, "AUTH_NOT_INITIALIZED");
17
+ this.name = "AuthInitializationError";
18
+ Object.setPrototypeOf(this, AuthInitializationError.prototype);
19
+ }
20
+ }
21
+
22
+ export class AuthConfigurationError extends AuthError {
23
+ constructor(message: string = "Invalid auth configuration") {
24
+ super(message, "AUTH_CONFIG_ERROR");
25
+ this.name = "AuthConfigurationError";
26
+ Object.setPrototypeOf(this, AuthConfigurationError.prototype);
27
+ }
28
+ }
29
+
30
+ export class AuthValidationError extends AuthError {
31
+ constructor(message: string, public field?: string) {
32
+ super(message, "AUTH_VALIDATION_ERROR");
33
+ this.name = "AuthValidationError";
34
+ Object.setPrototypeOf(this, AuthValidationError.prototype);
35
+ }
36
+ }
37
+
38
+ export class AuthNetworkError extends AuthError {
39
+ constructor(message: string = "Network error during authentication") {
40
+ super(message, "AUTH_NETWORK_ERROR");
41
+ this.name = "AuthNetworkError";
42
+ Object.setPrototypeOf(this, AuthNetworkError.prototype);
43
+ }
44
+ }
45
+
46
+ export class AuthUserNotFoundError extends AuthError {
47
+ constructor(message: string = "User not found") {
48
+ super(message, "AUTH_USER_NOT_FOUND");
49
+ this.name = "AuthUserNotFoundError";
50
+ Object.setPrototypeOf(this, AuthUserNotFoundError.prototype);
51
+ }
52
+ }
53
+
54
+ export class AuthWrongPasswordError extends AuthError {
55
+ constructor(message: string = "Wrong password") {
56
+ super(message, "AUTH_WRONG_PASSWORD");
57
+ this.name = "AuthWrongPasswordError";
58
+ Object.setPrototypeOf(this, AuthWrongPasswordError.prototype);
59
+ }
60
+ }
61
+
62
+ export class AuthEmailAlreadyInUseError extends AuthError {
63
+ constructor(message: string = "Email already in use") {
64
+ super(message, "AUTH_EMAIL_ALREADY_IN_USE");
65
+ this.name = "AuthEmailAlreadyInUseError";
66
+ Object.setPrototypeOf(this, AuthEmailAlreadyInUseError.prototype);
67
+ }
68
+ }
69
+
70
+ export class AuthWeakPasswordError extends AuthError {
71
+ constructor(message: string = "Password is too weak") {
72
+ super(message, "AUTH_WEAK_PASSWORD");
73
+ this.name = "AuthWeakPasswordError";
74
+ Object.setPrototypeOf(this, AuthWeakPasswordError.prototype);
75
+ }
76
+ }
77
+
78
+ export class AuthInvalidEmailError extends AuthError {
79
+ constructor(message: string = "Invalid email address") {
80
+ super(message, "AUTH_INVALID_EMAIL");
81
+ this.name = "AuthInvalidEmailError";
82
+ Object.setPrototypeOf(this, AuthInvalidEmailError.prototype);
83
+ }
84
+ }
85
+
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Auth Configuration Value Object
3
+ * Validates and stores authentication configuration
4
+ */
5
+
6
+ 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;
17
+ /** Callback for user profile creation after signup */
18
+ onUserCreated?: (user: any) => Promise<void> | void;
19
+ /** Callback for user profile update */
20
+ onUserUpdated?: (user: any) => Promise<void> | void;
21
+ /** Callback for sign out cleanup */
22
+ onSignOut?: () => Promise<void> | void;
23
+ }
24
+
25
+ export const DEFAULT_AUTH_CONFIG: Required<Omit<AuthConfig, 'onUserCreated' | 'onUserUpdated' | 'onSignOut'>> = {
26
+ minPasswordLength: 6,
27
+ requireUppercase: false,
28
+ requireLowercase: false,
29
+ requireNumbers: false,
30
+ requireSpecialChars: false,
31
+ };
32
+
package/src/index.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * React Native Auth - Public API
3
+ *
4
+ * Domain-Driven Design (DDD) Architecture
5
+ *
6
+ * This is the SINGLE SOURCE OF TRUTH for all Auth operations.
7
+ * ALL imports from the Auth package MUST go through this file.
8
+ *
9
+ * Architecture:
10
+ * - domain: Entities, value objects, errors (business logic)
11
+ * - application: Ports (interfaces)
12
+ * - infrastructure: Auth service implementation
13
+ * - presentation: Hooks (React integration)
14
+ *
15
+ * Usage:
16
+ * import { initializeAuthService, useAuth } from '@umituz/react-native-auth';
17
+ */
18
+
19
+ // =============================================================================
20
+ // DOMAIN LAYER - Business Logic
21
+ // =============================================================================
22
+
23
+ export {
24
+ AuthError,
25
+ AuthInitializationError,
26
+ AuthConfigurationError,
27
+ AuthValidationError,
28
+ AuthNetworkError,
29
+ AuthUserNotFoundError,
30
+ AuthWrongPasswordError,
31
+ AuthEmailAlreadyInUseError,
32
+ AuthWeakPasswordError,
33
+ AuthInvalidEmailError,
34
+ } from './domain/errors/AuthError';
35
+
36
+ export type { AuthConfig } from './domain/value-objects/AuthConfig';
37
+ export { DEFAULT_AUTH_CONFIG } from './domain/value-objects/AuthConfig';
38
+
39
+ // =============================================================================
40
+ // APPLICATION LAYER - Ports
41
+ // =============================================================================
42
+
43
+ export type { IAuthService, SignUpParams, SignInParams } from './application/ports/IAuthService';
44
+
45
+ // =============================================================================
46
+ // INFRASTRUCTURE LAYER - Implementation
47
+ // =============================================================================
48
+
49
+ export {
50
+ AuthService,
51
+ initializeAuthService,
52
+ getAuthService,
53
+ resetAuthService,
54
+ } from './infrastructure/services/AuthService';
55
+
56
+ // =============================================================================
57
+ // PRESENTATION LAYER - Hooks
58
+ // =============================================================================
59
+
60
+ export { useAuth } from './presentation/hooks/useAuth';
61
+ export type { UseAuthResult } from './presentation/hooks/useAuth';
62
+
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Auth Service Implementation
3
+ * Secure Firebase Authentication wrapper
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 { IAuthService, SignUpParams, SignInParams } from "../application/ports/IAuthService";
16
+ import {
17
+ AuthInitializationError,
18
+ AuthValidationError,
19
+ AuthWeakPasswordError,
20
+ AuthInvalidEmailError,
21
+ AuthEmailAlreadyInUseError,
22
+ AuthWrongPasswordError,
23
+ AuthUserNotFoundError,
24
+ AuthNetworkError,
25
+ } from "../domain/errors/AuthError";
26
+ import type { AuthConfig } from "../domain/value-objects/AuthConfig";
27
+ import { DEFAULT_AUTH_CONFIG } from "../domain/value-objects/AuthConfig";
28
+
29
+ /**
30
+ * Validate email format
31
+ */
32
+ function validateEmail(email: string): boolean {
33
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
34
+ return emailRegex.test(email.trim());
35
+ }
36
+
37
+ /**
38
+ * Validate password strength
39
+ */
40
+ function validatePassword(
41
+ password: string,
42
+ config: Required<Omit<AuthConfig, "onUserCreated" | "onUserUpdated" | "onSignOut">>
43
+ ): { valid: boolean; error?: string } {
44
+ if (password.length < config.minPasswordLength) {
45
+ return {
46
+ valid: false,
47
+ error: `Password must be at least ${config.minPasswordLength} characters long`,
48
+ };
49
+ }
50
+
51
+ if (config.requireUppercase && !/[A-Z]/.test(password)) {
52
+ return {
53
+ valid: false,
54
+ error: "Password must contain at least one uppercase letter",
55
+ };
56
+ }
57
+
58
+ if (config.requireLowercase && !/[a-z]/.test(password)) {
59
+ return {
60
+ valid: false,
61
+ error: "Password must contain at least one lowercase letter",
62
+ };
63
+ }
64
+
65
+ if (config.requireNumbers && !/[0-9]/.test(password)) {
66
+ return {
67
+ valid: false,
68
+ error: "Password must contain at least one number",
69
+ };
70
+ }
71
+
72
+ if (config.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
73
+ return {
74
+ valid: false,
75
+ error: "Password must contain at least one special character",
76
+ };
77
+ }
78
+
79
+ return { valid: true };
80
+ }
81
+
82
+ /**
83
+ * Map Firebase Auth errors to domain errors
84
+ */
85
+ function mapFirebaseAuthError(error: any): Error {
86
+ const code = error?.code || "";
87
+ const message = error?.message || "Authentication failed";
88
+
89
+ // Firebase Auth error codes
90
+ if (code === "auth/email-already-in-use") {
91
+ return new AuthEmailAlreadyInUseError();
92
+ }
93
+ if (code === "auth/invalid-email") {
94
+ return new AuthInvalidEmailError();
95
+ }
96
+ if (code === "auth/operation-not-allowed") {
97
+ return new AuthConfigurationError("Email/password authentication is not enabled");
98
+ }
99
+ if (code === "auth/weak-password") {
100
+ return new AuthWeakPasswordError();
101
+ }
102
+ if (code === "auth/user-disabled") {
103
+ return new AuthError("User account has been disabled", "AUTH_USER_DISABLED");
104
+ }
105
+ if (code === "auth/user-not-found") {
106
+ return new AuthUserNotFoundError();
107
+ }
108
+ if (code === "auth/wrong-password") {
109
+ return new AuthWrongPasswordError();
110
+ }
111
+ if (code === "auth/network-request-failed") {
112
+ return new AuthNetworkError();
113
+ }
114
+ if (code === "auth/too-many-requests") {
115
+ return new AuthError("Too many requests. Please try again later.", "AUTH_TOO_MANY_REQUESTS");
116
+ }
117
+
118
+ return new AuthError(message, code);
119
+ }
120
+
121
+ export class AuthService implements IAuthService {
122
+ private auth: Auth | null = null;
123
+ private config: AuthConfig;
124
+ private isGuestMode: boolean = false;
125
+
126
+ constructor(config: AuthConfig = {}) {
127
+ this.config = { ...DEFAULT_AUTH_CONFIG, ...config };
128
+ }
129
+
130
+ /**
131
+ * Initialize auth service with Firebase Auth instance
132
+ * Must be called before using any auth methods
133
+ */
134
+ initialize(auth: Auth): void {
135
+ if (!auth) {
136
+ throw new AuthInitializationError("Auth instance is required");
137
+ }
138
+ this.auth = auth;
139
+ this.isGuestMode = false;
140
+ }
141
+
142
+ /**
143
+ * Check if auth is initialized
144
+ */
145
+ isInitialized(): boolean {
146
+ return this.auth !== null;
147
+ }
148
+
149
+ private getAuth(): Auth {
150
+ if (!this.auth) {
151
+ throw new AuthInitializationError();
152
+ }
153
+ return this.auth;
154
+ }
155
+
156
+ /**
157
+ * Sign up a new user
158
+ */
159
+ async signUp(params: SignUpParams): Promise<User> {
160
+ const auth = this.getAuth();
161
+
162
+ // Validate email
163
+ if (!params.email || !validateEmail(params.email)) {
164
+ throw new AuthInvalidEmailError();
165
+ }
166
+
167
+ // Validate password
168
+ const passwordValidation = validatePassword(params.password, this.config as any);
169
+ if (!passwordValidation.valid) {
170
+ throw new AuthWeakPasswordError(passwordValidation.error);
171
+ }
172
+
173
+ try {
174
+ // Create user
175
+ const userCredential = await createUserWithEmailAndPassword(
176
+ auth,
177
+ params.email.trim(),
178
+ params.password
179
+ );
180
+
181
+ // Update display name if provided
182
+ if (params.displayName && userCredential.user) {
183
+ try {
184
+ await updateProfile(userCredential.user, {
185
+ displayName: params.displayName.trim(),
186
+ });
187
+ } catch (updateError) {
188
+ // Don't fail signup if display name update fails
189
+ // User can still use the app
190
+ }
191
+ }
192
+
193
+ // Call user created callback if provided
194
+ if (this.config.onUserCreated) {
195
+ try {
196
+ await this.config.onUserCreated(userCredential.user);
197
+ } catch (callbackError) {
198
+ // Don't fail signup if callback fails
199
+ }
200
+ }
201
+
202
+ return userCredential.user;
203
+ } catch (error: any) {
204
+ throw mapFirebaseAuthError(error);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Sign in an existing user
210
+ */
211
+ async signIn(params: SignInParams): Promise<User> {
212
+ const auth = this.getAuth();
213
+
214
+ // Validate email
215
+ if (!params.email || !validateEmail(params.email)) {
216
+ throw new AuthInvalidEmailError();
217
+ }
218
+
219
+ // Validate password
220
+ if (!params.password || params.password.length === 0) {
221
+ throw new AuthValidationError("Password is required", "password");
222
+ }
223
+
224
+ try {
225
+ const userCredential = await signInWithEmailAndPassword(
226
+ auth,
227
+ params.email.trim(),
228
+ params.password
229
+ );
230
+
231
+ this.isGuestMode = false;
232
+ return userCredential.user;
233
+ } catch (error: any) {
234
+ throw mapFirebaseAuthError(error);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Sign out current user
240
+ */
241
+ async signOut(): Promise<void> {
242
+ const auth = this.getAuth();
243
+
244
+ try {
245
+ await firebaseSignOut(auth);
246
+ this.isGuestMode = false;
247
+
248
+ // Call sign out callback if provided
249
+ if (this.config.onSignOut) {
250
+ try {
251
+ await this.config.onSignOut();
252
+ } catch (callbackError) {
253
+ // Don't fail signout if callback fails
254
+ }
255
+ }
256
+ } catch (error: any) {
257
+ throw mapFirebaseAuthError(error);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Set guest mode (no authentication)
263
+ */
264
+ async setGuestMode(): Promise<void> {
265
+ const auth = this.getAuth();
266
+
267
+ // Sign out from Firebase if logged in
268
+ if (auth.currentUser) {
269
+ try {
270
+ await firebaseSignOut(auth);
271
+ } catch (error) {
272
+ // Ignore sign out errors when switching to guest mode
273
+ }
274
+ }
275
+
276
+ this.isGuestMode = true;
277
+ }
278
+
279
+ /**
280
+ * Get current authenticated user
281
+ */
282
+ getCurrentUser(): User | null {
283
+ if (!this.auth) {
284
+ return null;
285
+ }
286
+ return this.auth.currentUser;
287
+ }
288
+
289
+ /**
290
+ * Check if user is in guest mode
291
+ */
292
+ getIsGuestMode(): boolean {
293
+ return this.isGuestMode;
294
+ }
295
+
296
+ /**
297
+ * Subscribe to auth state changes
298
+ */
299
+ onAuthStateChange(callback: (user: User | null) => void): () => void {
300
+ const auth = this.getAuth();
301
+
302
+ return onAuthStateChanged(auth, (user) => {
303
+ // Don't update if in guest mode
304
+ if (!this.isGuestMode) {
305
+ callback(user);
306
+ } else {
307
+ callback(null);
308
+ }
309
+ });
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Singleton instance
315
+ * Apps should use initializeAuthService() to set up with their Firebase Auth instance
316
+ */
317
+ let authServiceInstance: AuthService | null = null;
318
+
319
+ /**
320
+ * Initialize auth service with Firebase Auth instance
321
+ * Must be called before using any auth methods
322
+ */
323
+ export function initializeAuthService(
324
+ auth: Auth,
325
+ config?: AuthConfig
326
+ ): AuthService {
327
+ if (!authServiceInstance) {
328
+ authServiceInstance = new AuthService(config);
329
+ }
330
+ authServiceInstance.initialize(auth);
331
+ return authServiceInstance;
332
+ }
333
+
334
+ /**
335
+ * Get auth service instance
336
+ * @throws {AuthInitializationError} If service is not initialized
337
+ */
338
+ export function getAuthService(): AuthService {
339
+ if (!authServiceInstance || !authServiceInstance.isInitialized()) {
340
+ throw new AuthInitializationError(
341
+ "Auth service is not initialized. Call initializeAuthService() first."
342
+ );
343
+ }
344
+ return authServiceInstance;
345
+ }
346
+
347
+ /**
348
+ * Reset auth service (useful for testing)
349
+ */
350
+ export function resetAuthService(): void {
351
+ authServiceInstance = null;
352
+ }
353
+
@@ -0,0 +1,106 @@
1
+ /**
2
+ * useAuth Hook
3
+ * React hook for authentication state management
4
+ */
5
+
6
+ import { useEffect, useState, useCallback } from "react";
7
+ import type { User } from "firebase/auth";
8
+ import { getAuthService } from "../infrastructure/services/AuthService";
9
+
10
+ export interface UseAuthResult {
11
+ /** Current authenticated user */
12
+ user: User | null;
13
+ /** Whether auth state is loading */
14
+ loading: boolean;
15
+ /** Whether user is in guest mode */
16
+ isGuest: boolean;
17
+ /** Whether user is authenticated */
18
+ isAuthenticated: boolean;
19
+ /** Sign up function */
20
+ signUp: (email: string, password: string, displayName?: string) => Promise<void>;
21
+ /** Sign in function */
22
+ signIn: (email: string, password: string) => Promise<void>;
23
+ /** Sign out function */
24
+ signOut: () => Promise<void>;
25
+ /** Continue as guest function */
26
+ continueAsGuest: () => Promise<void>;
27
+ }
28
+
29
+ /**
30
+ * Hook for authentication state management
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const { user, isAuthenticated, signIn, signUp, signOut } = useAuth();
35
+ * ```
36
+ */
37
+ export function useAuth(): UseAuthResult {
38
+ const [user, setUser] = useState<User | null>(null);
39
+ const [loading, setLoading] = useState(true);
40
+ const [isGuest, setIsGuest] = useState(false);
41
+
42
+ useEffect(() => {
43
+ try {
44
+ const service = getAuthService();
45
+ const unsubscribe = service.onAuthStateChange((currentUser) => {
46
+ setUser(currentUser);
47
+ setIsGuest(service.getIsGuestMode());
48
+ setLoading(false);
49
+ });
50
+
51
+ // Set initial state
52
+ const currentUser = service.getCurrentUser();
53
+ setUser(currentUser);
54
+ setIsGuest(service.getIsGuestMode());
55
+ setLoading(false);
56
+
57
+ return () => {
58
+ unsubscribe();
59
+ };
60
+ } catch (error) {
61
+ // Auth service not initialized
62
+ setUser(null);
63
+ setIsGuest(false);
64
+ setLoading(false);
65
+ return () => {};
66
+ }
67
+ }, []);
68
+
69
+ const signUp = useCallback(async (email: string, password: string, displayName?: string) => {
70
+ const service = getAuthService();
71
+ await service.signUp({ email, password, displayName });
72
+ // State will be updated via onAuthStateChange
73
+ }, []);
74
+
75
+ const signIn = useCallback(async (email: string, password: string) => {
76
+ const service = getAuthService();
77
+ await service.signIn({ email, password });
78
+ // State will be updated via onAuthStateChange
79
+ }, []);
80
+
81
+ const signOut = useCallback(async () => {
82
+ const service = getAuthService();
83
+ await service.signOut();
84
+ setUser(null);
85
+ setIsGuest(false);
86
+ }, []);
87
+
88
+ const continueAsGuest = useCallback(async () => {
89
+ const service = getAuthService();
90
+ await service.setGuestMode();
91
+ setUser(null);
92
+ setIsGuest(true);
93
+ }, []);
94
+
95
+ return {
96
+ user,
97
+ loading,
98
+ isGuest,
99
+ isAuthenticated: !!user && !isGuest,
100
+ signUp,
101
+ signIn,
102
+ signOut,
103
+ continueAsGuest,
104
+ };
105
+ }
106
+