@umituz/react-native-auth 3.4.33 → 3.4.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/application/README.md +323 -442
- package/src/domain/ConfigAndErrors.md +296 -431
- package/src/domain/README.md +361 -210
- package/src/domain/entities/AuthUser.md +231 -372
- package/src/domain/entities/UserProfile.md +271 -441
- package/src/infrastructure/README.md +388 -444
- package/src/infrastructure/services/README.md +386 -312
- package/src/presentation/README.md +631 -563
- package/src/presentation/components/ProfileSection.tsx +3 -1
- package/src/presentation/components/README.md +254 -92
- package/src/presentation/hooks/README.md +247 -83
- package/src/presentation/hooks/useUserProfile.ts +1 -0
- package/src/presentation/screens/README.md +151 -153
|
@@ -1,513 +1,394 @@
|
|
|
1
1
|
# Application Layer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Defines ports and interfaces for authentication operations following Hexagonal Architecture (Ports and Adapters).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
application/
|
|
9
|
-
└── ports/
|
|
10
|
-
├── IAuthService.ts # Authentication service interface
|
|
11
|
-
└── IAuthProvider.ts # Auth provider interface
|
|
12
|
-
```
|
|
7
|
+
## Strategy
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
**Purpose**: Establishes contracts between domain logic and external implementations. Defines what operations the application can perform without specifying how.
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
**When to Use**:
|
|
12
|
+
- Implementing custom auth providers
|
|
13
|
+
- Creating test doubles/mocks
|
|
14
|
+
- Understanding available auth operations
|
|
15
|
+
- Designing alternative implementations
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
**Location**: `src/application/`
|
|
18
|
+
|
|
19
|
+
**Structure**:
|
|
20
|
+
- `ports/` - Interface definitions
|
|
21
|
+
- `IAuthService.ts` - Authentication service interface
|
|
22
|
+
- `IAuthProvider.ts` - Auth provider interface
|
|
20
23
|
|
|
21
24
|
---
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
## Architecture Pattern
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
### Hexagonal Architecture (Ports and Adapters)
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
**PURPOSE**: Decouple business logic from external dependencies
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
**PORTS**:
|
|
33
|
+
- Interfaces defining operations
|
|
34
|
+
- Define what the application does
|
|
35
|
+
- Independent of implementations
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
**ADAPTERS**:
|
|
38
|
+
- Implementations of ports
|
|
39
|
+
- Connect to external services
|
|
40
|
+
- Firebase, custom backend, mocks
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
**Rules**:
|
|
43
|
+
- MUST depend on interfaces, not implementations
|
|
44
|
+
- MUST define clear operation contracts
|
|
45
|
+
- MUST support multiple implementations
|
|
46
|
+
- MUST not leak implementation details
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Ports
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
getCurrentUser(): AuthUser | null;
|
|
44
|
-
}
|
|
52
|
+
### IAuthService
|
|
45
53
|
|
|
46
|
-
interface
|
|
47
|
-
email: string;
|
|
48
|
-
password: string;
|
|
49
|
-
displayName?: string;
|
|
50
|
-
}
|
|
54
|
+
**PURPOSE**: Authentication service interface defining core auth operations
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
+
**IMPORT PATH**:
|
|
57
|
+
```typescript
|
|
58
|
+
import type { IAuthService } from '@umituz/react-native-auth';
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
**OPERATIONS**:
|
|
62
|
+
- `signUp(params)` - Create new user account
|
|
63
|
+
- `signIn(params)` - Authenticate existing user
|
|
64
|
+
- `signOut()` - End user session
|
|
65
|
+
- `getCurrentUser()` - Retrieve current user
|
|
66
|
+
- `resetPassword(email)` - Initiate password reset
|
|
67
|
+
- `updateEmail(email)` - Change user email
|
|
68
|
+
- `updatePassword(password)` - Change user password
|
|
69
|
+
|
|
70
|
+
**Rules**:
|
|
71
|
+
- MUST implement all defined methods
|
|
72
|
+
- MUST return domain entities (AuthUser)
|
|
73
|
+
- MUST throw domain errors (AuthError)
|
|
74
|
+
- MUST handle async operations
|
|
75
|
+
- MUST validate inputs
|
|
76
|
+
|
|
77
|
+
**MUST NOT**:
|
|
78
|
+
- Expose Firebase-specific types
|
|
79
|
+
- Return raw provider responses
|
|
80
|
+
- Skip error handling
|
|
81
|
+
- Use synchronous operations
|
|
82
|
+
|
|
83
|
+
**PARAMS**:
|
|
84
|
+
- `SignUpParams` - email, password, displayName?
|
|
85
|
+
- `SignInParams` - email, password
|
|
59
86
|
|
|
60
|
-
|
|
87
|
+
---
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
import { IAuthService, SignUpParams, SignInParams } from '@umituz/react-native-auth';
|
|
64
|
-
|
|
65
|
-
class FirebaseAuthService implements IAuthService {
|
|
66
|
-
constructor(private firebaseAuth: Auth) {}
|
|
67
|
-
|
|
68
|
-
async signUp(params: SignUpParams): Promise<AuthUser> {
|
|
69
|
-
const userCredential = await createUserWithEmailAndPassword(
|
|
70
|
-
this.firebaseAuth,
|
|
71
|
-
params.email,
|
|
72
|
-
params.password
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// Update profile if displayName provided
|
|
76
|
-
if (params.displayName) {
|
|
77
|
-
await updateProfile(userCredential.user, {
|
|
78
|
-
displayName: params.displayName,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return this.mapToAuthUser(userCredential.user);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async signIn(params: SignInParams): Promise<AuthUser> {
|
|
86
|
-
const userCredential = await signInWithEmailAndPassword(
|
|
87
|
-
this.firebaseAuth,
|
|
88
|
-
params.email,
|
|
89
|
-
params.password
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
return this.mapToAuthUser(userCredential.user);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async signOut(): Promise<void> {
|
|
96
|
-
await signOut(this.firebaseAuth);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
getCurrentUser(): AuthUser | null {
|
|
100
|
-
const user = this.firebaseAuth.currentUser;
|
|
101
|
-
return user ? this.mapToAuthUser(user) : null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private mapToAuthUser(firebaseUser: FirebaseUser): AuthUser {
|
|
105
|
-
return {
|
|
106
|
-
uid: firebaseUser.uid,
|
|
107
|
-
email: firebaseUser.email,
|
|
108
|
-
displayName: firebaseUser.displayName,
|
|
109
|
-
isAnonymous: firebaseUser.isAnonymous,
|
|
110
|
-
emailVerified: firebaseUser.emailVerified,
|
|
111
|
-
photoURL: firebaseUser.photoURL,
|
|
112
|
-
provider: firebaseUser.providerData[0]?.providerId || 'unknown',
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
89
|
+
### IAuthProvider
|
|
117
90
|
|
|
118
|
-
|
|
91
|
+
**PURPOSE**: Provider interface for different authentication backends
|
|
119
92
|
|
|
93
|
+
**IMPORT PATH**:
|
|
120
94
|
```typescript
|
|
121
|
-
|
|
122
|
-
const [email, setEmail] = useState('');
|
|
123
|
-
const [password, setPassword] = useState('');
|
|
124
|
-
|
|
125
|
-
const handleSignIn = async () => {
|
|
126
|
-
try {
|
|
127
|
-
const user = await authService.signIn({ email, password });
|
|
128
|
-
console.log('Signed in:', user.displayName);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error('Sign in failed:', error);
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<View>
|
|
136
|
-
<TextInput value={email} onChangeText={setEmail} placeholder="Email" />
|
|
137
|
-
<TextInput
|
|
138
|
-
value={password}
|
|
139
|
-
onChangeText={setPassword}
|
|
140
|
-
placeholder="Password"
|
|
141
|
-
secureTextEntry
|
|
142
|
-
/>
|
|
143
|
-
<Button onPress={handleSignIn}>Sign In</Button>
|
|
144
|
-
</View>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
95
|
+
import type { IAuthProvider } from '@umituz/react-native-auth';
|
|
147
96
|
```
|
|
148
97
|
|
|
98
|
+
**OPERATIONS**:
|
|
99
|
+
- `signUp(credentials)` - Register new user
|
|
100
|
+
- `signIn(credentials)` - Authenticate user
|
|
101
|
+
- `signInWithSocial(provider)` - Social authentication
|
|
102
|
+
- `signOut()` - Sign out user
|
|
103
|
+
- `getCurrentUser()` - Get current user
|
|
104
|
+
|
|
105
|
+
**SOCIAL PROVIDERS**:
|
|
106
|
+
- `google` - Google OAuth
|
|
107
|
+
- `apple` - Apple Sign-In
|
|
108
|
+
- `anonymous` - Anonymous session
|
|
109
|
+
|
|
110
|
+
**Rules**:
|
|
111
|
+
- MUST support defined providers
|
|
112
|
+
- MUST return consistent AuthUser format
|
|
113
|
+
- MUST handle provider-specific errors
|
|
114
|
+
- MUST normalize user data
|
|
115
|
+
- MUST support multiple providers
|
|
116
|
+
|
|
117
|
+
**MUST NOT**:
|
|
118
|
+
- Mix provider implementations
|
|
119
|
+
- Return inconsistent user shapes
|
|
120
|
+
- Ignore provider constraints
|
|
121
|
+
- Skip error normalization
|
|
122
|
+
|
|
123
|
+
**CREDENTIALS**:
|
|
124
|
+
- `AuthCredentials` - email, password
|
|
125
|
+
- `SignUpCredentials` - email, password, displayName?
|
|
126
|
+
- `SocialSignInResult` - success, user?, error?
|
|
127
|
+
|
|
149
128
|
---
|
|
150
129
|
|
|
151
|
-
|
|
130
|
+
## Implementation Guidelines
|
|
152
131
|
|
|
153
|
-
|
|
132
|
+
### Creating Custom Provider
|
|
154
133
|
|
|
155
|
-
|
|
134
|
+
**RULES**:
|
|
135
|
+
- MUST implement IAuthService or IAuthProvider
|
|
136
|
+
- MUST map to domain AuthUser entity
|
|
137
|
+
- MUST convert errors to domain errors
|
|
138
|
+
- MUST follow interface contract
|
|
139
|
+
- MUST handle edge cases
|
|
156
140
|
|
|
157
|
-
|
|
158
|
-
|
|
141
|
+
**MUST NOT**:
|
|
142
|
+
- Change method signatures
|
|
143
|
+
- Return provider-specific types
|
|
144
|
+
- Skip input validation
|
|
145
|
+
- Ignore error handling
|
|
159
146
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
147
|
+
**Constraints**:
|
|
148
|
+
- Async operations only
|
|
149
|
+
- Consistent return types
|
|
150
|
+
- Proper error mapping
|
|
151
|
+
- User data normalization
|
|
163
152
|
|
|
164
|
-
|
|
165
|
-
signIn(credentials: AuthCredentials): Promise<AuthUser>;
|
|
153
|
+
---
|
|
166
154
|
|
|
167
|
-
|
|
168
|
-
signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult>;
|
|
155
|
+
### Firebase Implementation
|
|
169
156
|
|
|
170
|
-
|
|
171
|
-
signOut(): Promise<void>;
|
|
157
|
+
**LOCATION**: `src/infrastructure/services/AuthService.ts`
|
|
172
158
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
159
|
+
**Rules**:
|
|
160
|
+
- MUST implement IAuthService
|
|
161
|
+
- MUST wrap Firebase SDK
|
|
162
|
+
- MUST map Firebase errors to domain
|
|
163
|
+
- MUST convert Firebase User to AuthUser
|
|
164
|
+
- MUST handle Firebase lifecycle
|
|
176
165
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
166
|
+
**MUST NOT**:
|
|
167
|
+
- Expose Firebase types publicly
|
|
168
|
+
- Bypass domain layer
|
|
169
|
+
- Skip error mapping
|
|
170
|
+
- Ignore Firebase events
|
|
181
171
|
|
|
182
|
-
|
|
183
|
-
displayName?: string;
|
|
184
|
-
}
|
|
172
|
+
---
|
|
185
173
|
|
|
186
|
-
|
|
187
|
-
success: boolean;
|
|
188
|
-
user?: AuthUser;
|
|
189
|
-
error?: string;
|
|
190
|
-
}
|
|
191
|
-
```
|
|
174
|
+
### Custom Backend Implementation
|
|
192
175
|
|
|
193
|
-
|
|
176
|
+
**USE CASES**:
|
|
177
|
+
- Custom authentication server
|
|
178
|
+
- Additional business logic
|
|
179
|
+
- Enhanced security requirements
|
|
180
|
+
- Legacy system integration
|
|
194
181
|
|
|
195
|
-
|
|
182
|
+
**Rules**:
|
|
183
|
+
- MUST implement IAuthProvider
|
|
184
|
+
- MUST handle HTTP errors properly
|
|
185
|
+
- MUST normalize API responses
|
|
186
|
+
- MUST implement retry logic
|
|
187
|
+
- MUST handle network failures
|
|
196
188
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
constructor(private firebaseAuth: Auth) {}
|
|
203
|
-
|
|
204
|
-
async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
|
|
205
|
-
const userCredential = await createUserWithEmailAndPassword(
|
|
206
|
-
this.firebaseAuth,
|
|
207
|
-
credentials.email,
|
|
208
|
-
credentials.password
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
if (credentials.displayName) {
|
|
212
|
-
await updateProfile(userCredential.user, {
|
|
213
|
-
displayName: credentials.displayName,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return this.mapUser(userCredential.user);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async signIn(credentials: AuthCredentials): Promise<AuthUser> {
|
|
221
|
-
const userCredential = await signInWithEmailAndPassword(
|
|
222
|
-
this.firebaseAuth,
|
|
223
|
-
credentials.email,
|
|
224
|
-
credentials.password
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
return this.mapUser(userCredential.user);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult> {
|
|
231
|
-
try {
|
|
232
|
-
let authProvider: GoogleAuthProvider | OAuthProvider;
|
|
233
|
-
|
|
234
|
-
if (provider === 'google') {
|
|
235
|
-
authProvider = new GoogleAuthProvider();
|
|
236
|
-
} else {
|
|
237
|
-
authProvider = new OAuthProvider('apple.com');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const result = await signInWithPopup(this.firebaseAuth, authProvider);
|
|
241
|
-
const user = this.mapUser(result.user);
|
|
242
|
-
|
|
243
|
-
return { success: true, user };
|
|
244
|
-
} catch (error) {
|
|
245
|
-
return {
|
|
246
|
-
success: false,
|
|
247
|
-
error: error instanceof Error ? error.message : 'Social sign in failed',
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async signOut(): Promise<void> {
|
|
253
|
-
await signOut(this.firebaseAuth);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
getCurrentUser(): AuthUser | null {
|
|
257
|
-
const user = this.firebaseAuth.currentUser;
|
|
258
|
-
return user ? this.mapUser(user) : null;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private mapUser(firebaseUser: FirebaseUser): AuthUser {
|
|
262
|
-
return {
|
|
263
|
-
uid: firebaseUser.uid,
|
|
264
|
-
email: firebaseUser.email,
|
|
265
|
-
displayName: firebaseUser.displayName,
|
|
266
|
-
isAnonymous: firebaseUser.isAnonymous,
|
|
267
|
-
emailVerified: firebaseUser.emailVerified,
|
|
268
|
-
photoURL: firebaseUser.photoURL,
|
|
269
|
-
provider: firebaseUser.providerData[0]?.providerId || 'unknown',
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
```
|
|
189
|
+
**MUST NOT**:
|
|
190
|
+
- Expose API details to domain
|
|
191
|
+
- Skip authentication tokens
|
|
192
|
+
- Ignore server errors
|
|
193
|
+
- Hardcode endpoints
|
|
274
194
|
|
|
275
|
-
|
|
195
|
+
---
|
|
276
196
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return data.user; // Assuming API returns { user: AuthUser }
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
async signIn(credentials: AuthCredentials): Promise<AuthUser> {
|
|
299
|
-
const response = await fetch(`${this.apiBaseUrl}/auth/signin`, {
|
|
300
|
-
method: 'POST',
|
|
301
|
-
headers: { 'Content-Type': 'application/json' },
|
|
302
|
-
body: JSON.stringify(credentials),
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
if (!response.ok) {
|
|
306
|
-
throw new Error('Sign in failed');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const data = await response.json();
|
|
310
|
-
return data.user;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult> {
|
|
314
|
-
// Implement social sign-in with your backend
|
|
315
|
-
const response = await fetch(`${this.apiBaseUrl}/auth/social/${provider}`, {
|
|
316
|
-
method: 'POST',
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const data = await response.json();
|
|
320
|
-
return data;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async signOut(): Promise<void> {
|
|
324
|
-
await fetch(`${this.apiBaseUrl}/auth/signout`, { method: 'POST' });
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
getCurrentUser(): AuthUser | null {
|
|
328
|
-
// Return cached user or fetch from backend
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
```
|
|
197
|
+
## Dependency Injection
|
|
198
|
+
|
|
199
|
+
### Provider Pattern
|
|
200
|
+
|
|
201
|
+
**PURPOSE**: Enable swapping implementations without changing application code
|
|
202
|
+
|
|
203
|
+
**RULES**:
|
|
204
|
+
- MUST inject interfaces, not implementations
|
|
205
|
+
- MUST configure at app root
|
|
206
|
+
- MUST use single instance
|
|
207
|
+
- MUST not create multiple instances
|
|
208
|
+
- MUST handle initialization properly
|
|
209
|
+
|
|
210
|
+
**MUST NOT**:
|
|
211
|
+
- Instantiate providers in components
|
|
212
|
+
- Mix provider types
|
|
213
|
+
- Skip initialization
|
|
214
|
+
- Create circular dependencies
|
|
333
215
|
|
|
334
216
|
---
|
|
335
217
|
|
|
336
|
-
|
|
218
|
+
## Testing Strategy
|
|
337
219
|
|
|
338
|
-
|
|
220
|
+
### Mock Implementations
|
|
339
221
|
|
|
340
|
-
|
|
222
|
+
**PURPOSE**: Enable testing without real backend
|
|
341
223
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const AuthProviderContext = createContext<AuthProviderContextType | null>(null);
|
|
350
|
-
|
|
351
|
-
export function AuthProvider({ children, provider }: { children: ReactNode; provider: IAuthProvider }) {
|
|
352
|
-
return (
|
|
353
|
-
<AuthProviderContext.Provider value={{ authProvider: provider }}>
|
|
354
|
-
{children}
|
|
355
|
-
</AuthProviderContext.Provider>
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
export function useAuthProvider(): IAuthProvider {
|
|
360
|
-
const context = useContext(AuthProviderContext);
|
|
361
|
-
if (!context) {
|
|
362
|
-
throw new Error('useAuthProvider must be used within AuthProvider');
|
|
363
|
-
}
|
|
364
|
-
return context.authProvider;
|
|
365
|
-
}
|
|
366
|
-
```
|
|
224
|
+
**RULES**:
|
|
225
|
+
- MUST implement IAuthProvider interface
|
|
226
|
+
- MUST return valid AuthUser objects
|
|
227
|
+
- MUST simulate realistic behavior
|
|
228
|
+
- MUST support test scenarios
|
|
229
|
+
- MUST be deterministic
|
|
367
230
|
|
|
368
|
-
|
|
231
|
+
**MUST NOT**:
|
|
232
|
+
- Return invalid data
|
|
233
|
+
- Have undefined behavior
|
|
234
|
+
- Skip error scenarios
|
|
235
|
+
- Break interface contract
|
|
369
236
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const authProvider = __DEV__
|
|
376
|
-
? new BackendAuthProvider('https://dev-api.example.com')
|
|
377
|
-
: new FirebaseAuthProvider(getAuth());
|
|
378
|
-
|
|
379
|
-
function App() {
|
|
380
|
-
return (
|
|
381
|
-
<AuthProvider provider={authProvider}>
|
|
382
|
-
<AppNavigator />
|
|
383
|
-
</AuthProvider>
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
```
|
|
237
|
+
**USE CASES**:
|
|
238
|
+
- Unit testing
|
|
239
|
+
- Integration testing
|
|
240
|
+
- Storybook development
|
|
241
|
+
- CI/CD pipelines
|
|
387
242
|
|
|
388
243
|
---
|
|
389
244
|
|
|
390
|
-
|
|
245
|
+
## Error Handling
|
|
391
246
|
|
|
392
|
-
|
|
247
|
+
### Error Mapping
|
|
393
248
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (credentials.email === 'test@example.com' && credentials.password === 'password') {
|
|
415
|
-
this.mockUser = {
|
|
416
|
-
uid: 'mock-123',
|
|
417
|
-
email: credentials.email,
|
|
418
|
-
displayName: 'Test User',
|
|
419
|
-
isAnonymous: false,
|
|
420
|
-
emailVerified: true,
|
|
421
|
-
photoURL: null,
|
|
422
|
-
provider: 'password',
|
|
423
|
-
};
|
|
424
|
-
return this.mockUser;
|
|
425
|
-
}
|
|
426
|
-
throw new Error('Invalid credentials');
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
async signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult> {
|
|
430
|
-
this.mockUser = {
|
|
431
|
-
uid: `mock-${provider}-123`,
|
|
432
|
-
email: `${provider}@example.com`,
|
|
433
|
-
displayName: `${provider} User`,
|
|
434
|
-
isAnonymous: false,
|
|
435
|
-
emailVerified: true,
|
|
436
|
-
photoURL: null,
|
|
437
|
-
provider: provider === 'google' ? 'google.com' : 'apple.com',
|
|
438
|
-
};
|
|
439
|
-
return { success: true, user: this.mockUser };
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
async signOut(): Promise<void> {
|
|
443
|
-
this.mockUser = null;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
getCurrentUser(): AuthUser | null {
|
|
447
|
-
return this.mockUser;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Usage in tests
|
|
452
|
-
const mockProvider = new MockAuthProvider();
|
|
453
|
-
render(
|
|
454
|
-
<AuthProvider provider={mockProvider}>
|
|
455
|
-
<LoginComponent />
|
|
456
|
-
</AuthProvider>
|
|
457
|
-
);
|
|
458
|
-
```
|
|
249
|
+
**RULES**:
|
|
250
|
+
- MUST map provider errors to domain errors
|
|
251
|
+
- MUST preserve error context
|
|
252
|
+
- MUST use domain error types
|
|
253
|
+
- MUST include helpful messages
|
|
254
|
+
- MUST not expose implementation details
|
|
255
|
+
|
|
256
|
+
**MUST NOT**:
|
|
257
|
+
- Throw raw provider errors
|
|
258
|
+
- Expose stack traces
|
|
259
|
+
- Lose error context
|
|
260
|
+
- Use generic error types
|
|
261
|
+
|
|
262
|
+
**DOMAIN ERRORS**:
|
|
263
|
+
- AuthUserNotFoundError
|
|
264
|
+
- AuthWrongPasswordError
|
|
265
|
+
- AuthEmailAlreadyInUseError
|
|
266
|
+
- AuthWeakPasswordError
|
|
267
|
+
- AuthInvalidEmailError
|
|
268
|
+
- AuthNetworkError
|
|
459
269
|
|
|
460
270
|
---
|
|
461
271
|
|
|
462
|
-
|
|
272
|
+
## Best Practices
|
|
463
273
|
|
|
464
|
-
|
|
274
|
+
### Interface Compliance
|
|
465
275
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// ❌ Bad - couples to Firebase
|
|
473
|
-
function authenticateUser(auth: Auth, credentials: AuthCredentials) {
|
|
474
|
-
return signInWithEmailAndPassword(auth, credentials.email, credentials.password);
|
|
475
|
-
}
|
|
476
|
-
```
|
|
276
|
+
**MUST**:
|
|
277
|
+
- Implement all interface methods
|
|
278
|
+
- Match exact signatures
|
|
279
|
+
- Return promised types
|
|
280
|
+
- Handle all error cases
|
|
281
|
+
- Follow async patterns
|
|
477
282
|
|
|
478
|
-
|
|
283
|
+
**MUST NOT**:
|
|
284
|
+
- Modify interface definitions
|
|
285
|
+
- Skip optional methods
|
|
286
|
+
- Change parameter types
|
|
287
|
+
- Break backward compatibility
|
|
479
288
|
|
|
480
|
-
|
|
481
|
-
// ✅ Good - injectable
|
|
482
|
-
class UserService {
|
|
483
|
-
constructor(private authProvider: IAuthProvider) {}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// ❌ Bad - tight coupling
|
|
487
|
-
import { getAuth } from 'firebase/auth';
|
|
488
|
-
class UserService {
|
|
489
|
-
private auth = getAuth();
|
|
490
|
-
}
|
|
491
|
-
```
|
|
289
|
+
---
|
|
492
290
|
|
|
493
|
-
|
|
291
|
+
### Error Boundaries
|
|
494
292
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
293
|
+
**MUST**:
|
|
294
|
+
- Wrap provider calls in try-catch
|
|
295
|
+
- Map all errors to domain
|
|
296
|
+
- Provide context
|
|
297
|
+
- Allow retry where appropriate
|
|
298
|
+
- Log appropriately
|
|
299
|
+
|
|
300
|
+
**MUST NOT**:
|
|
301
|
+
- Let provider errors escape
|
|
302
|
+
- Expose implementation details
|
|
303
|
+
- Skip error handling
|
|
304
|
+
- Suppress errors silently
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### User Data Normalization
|
|
309
|
+
|
|
310
|
+
**MUST**:
|
|
311
|
+
- Convert to AuthUser entity
|
|
312
|
+
- Normalize provider data
|
|
313
|
+
- Handle missing fields
|
|
314
|
+
- Validate required fields
|
|
315
|
+
- Preserve important metadata
|
|
316
|
+
|
|
317
|
+
**MUST NOT**:
|
|
318
|
+
- Return raw provider data
|
|
319
|
+
- Skip null handling
|
|
320
|
+
- Assume field presence
|
|
321
|
+
- Lose user context
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Constraints
|
|
326
|
+
|
|
327
|
+
### Platform Limitations
|
|
328
|
+
|
|
329
|
+
**PROVIDER AVAILABILITY**:
|
|
330
|
+
- Google: All platforms
|
|
331
|
+
- Apple: iOS only
|
|
332
|
+
- Anonymous: All platforms
|
|
333
|
+
|
|
334
|
+
**Rules**:
|
|
335
|
+
- MUST check platform before use
|
|
336
|
+
- MUST provide fallback for unavailable
|
|
337
|
+
- MUST not crash on unsupported
|
|
338
|
+
- MUST document platform restrictions
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
### Security Requirements
|
|
343
|
+
|
|
344
|
+
**MUST**:
|
|
345
|
+
- Validate all inputs
|
|
346
|
+
- Use HTTPS for remote calls
|
|
347
|
+
- Securely store credentials
|
|
348
|
+
- Handle tokens properly
|
|
349
|
+
- Implement proper error handling
|
|
350
|
+
|
|
351
|
+
**MUST NOT**:
|
|
352
|
+
- Log sensitive data
|
|
353
|
+
- Expose credentials
|
|
354
|
+
- Skip validation
|
|
355
|
+
- Use insecure protocols
|
|
356
|
+
|
|
357
|
+
---
|
|
508
358
|
|
|
509
359
|
## Related Modules
|
|
510
360
|
|
|
511
|
-
- **
|
|
512
|
-
- **
|
|
513
|
-
- **
|
|
361
|
+
- **Domain** (`../domain/README.md`) - AuthUser entity, AuthConfig, AuthError
|
|
362
|
+
- **Infrastructure** (`../infrastructure/README.md`) - Firebase implementation
|
|
363
|
+
- **Presentation** (`../presentation/README.md`) - UI components and hooks
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Port Documentation
|
|
368
|
+
|
|
369
|
+
### IAuthService Documentation
|
|
370
|
+
|
|
371
|
+
**File**: `ports/IAuthService.ts`
|
|
372
|
+
|
|
373
|
+
**Purpose**: Core authentication service interface
|
|
374
|
+
|
|
375
|
+
**Implementations**:
|
|
376
|
+
- `FirebaseAuthService` - Firebase implementation
|
|
377
|
+
- Custom implementations allowed
|
|
378
|
+
|
|
379
|
+
**See Also**: Infrastructure services documentation
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
### IAuthProvider Documentation
|
|
384
|
+
|
|
385
|
+
**File**: `ports/IAuthProvider.ts`
|
|
386
|
+
|
|
387
|
+
**Purpose**: Provider abstraction for different auth backends
|
|
388
|
+
|
|
389
|
+
**Implementations**:
|
|
390
|
+
- Firebase provider
|
|
391
|
+
- Custom backend provider
|
|
392
|
+
- Mock provider (testing)
|
|
393
|
+
|
|
394
|
+
**See Also**: Infrastructure services documentation
|