forked from Echo-Mirror-Butler/Echo-Mirror-Butler-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth_dev.txt
More file actions
351 lines (314 loc) · 25.9 KB
/
Copy pathauth_dev.txt
File metadata and controls
351 lines (314 loc) · 25.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
import 'package:echomirror_server_client/echomirror_server_client.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../core/services/serverpod_client_service.dart';
/// Repository for authentication operations
/// This handles all Serverpod backend calls for auth
class AuthRepository {
final Client? _injectedClient;
AuthRepository({Client? client}) : _injectedClient = client {
debugPrint(
'[AuthRepository] Initialized - client will be accessed when needed',
);
}
/// Get the client instance (lazy initialization)
Client get _client {
final injected = _injectedClient;
if (injected != null) return injected;
// Ensure client is initialized before use
if (!ServerpodClientService.instance.isInitialized) {
throw StateError(
'ServerpodClientService not initialized. Call ensureInitialized() in main() first.',
);
}
return ServerpodClientService.instance.client;
}
/// Sign in with email and password
/// Returns user ID on success, throws exception on failure
Future<String> signIn(String email, String password) async {
try {
debugPrint('[AuthRepository] signIn -> $email');
// Check if we have a key before login
final keyBefore = await _client.authenticationKeyManager?.get();
debugPrint(
'[AuthRepository] Key before login: ${keyBefore != null ? "exists" : "null"}',
);
// Login and get the response
final authResult = await _client.emailIdp.login(
email: email,
password: password,
);
// Extract the JWT token and user info from the AuthSuccess response
final dynamic result = authResult;
final String? token = result.token as String?;
// authUserId might be UuidValue or String, handle both
String? authUserId;
try {
final authUserIdValue = result.authUserId;
if (authUserIdValue != null) {
// Try to access .uuid property (for UuidValue) or use toString()
try {
// If it has a .uuid property, use it (UuidValue)
final dynamic uuidValue = authUserIdValue;
authUserId = uuidValue.uuid as String?;
} catch (_) {
// If .uuid doesn't work, try toString()
authUserId = authUserIdValue.toString();
}
}
} catch (e) {
debugPrint('[AuthRepository] Error extracting authUserId: $e');
authUserId = null;
}
debugPrint(
'[AuthRepository] Login response - token: ${token != null ? "${token.length} chars" : "null"}, authUserId: $authUserId',
);
if (token == null || token.isEmpty) {
debugPrint('[AuthRepository] ⚠️ No token found in login response');
throw Exception('Login succeeded but no authentication token received');
}
// Save the JWT token to SharedPreferences for persistence
debugPrint(
'[AuthRepository] Saving JWT authentication token (${token.length} chars)...',
);
await _client.authenticationKeyManager?.put(token);
// Save user info (email and authUserId) to SharedPreferences for persistence
final prefs = await SharedPreferences.getInstance();
await prefs.setString('user_email', email);
if (authUserId != null && authUserId.isNotEmpty) {
await prefs.setString('user_id', authUserId);
debugPrint('[AuthRepository] Saved user ID from server: $authUserId');
} else {
// Fallback to email hash if authUserId not available
final fallbackUserId = 'user_${email.hashCode}';
await prefs.setString('user_id', fallbackUserId);
debugPrint('[AuthRepository] Using fallback user ID: $fallbackUserId');
}
// Verify the token was saved
final savedToken = await _client.authenticationKeyManager?.get();
if (savedToken == null || savedToken != token) {
debugPrint('[AuthRepository] ❌ ERROR: Token was not saved correctly!');
throw Exception('Failed to save authentication token');
}
debugPrint('[AuthRepository] ✅ Authentication token and user info saved');
// Return the user ID (prefer server's authUserId, fallback to email hash)
final userId = authUserId ?? 'user_${email.hashCode}';
debugPrint('[AuthRepository] signIn success -> $email, userId: $userId');
return userId;
} catch (e) {
debugPrint('[AuthRepository] signIn error -> $e');
throw Exception('Sign in failed: ${e.toString()}');
}
}
/// Sign up with email and password
/// Note: Serverpod requires email verification, so this is a two-step process
/// Returns account request ID for verification
Future<String> signUp(String email, String password, String? name) async {
try {
debugPrint('[AuthRepository] signUp -> $email | name: $name');
// Step 1: Start registration (sends verification email)
final accountRequestId = await _client.emailIdp.startRegistration(
email: email,
);
// In a real app, you'd need to:
// 1. Show a screen for the user to enter the verification code from email
// 2. Call verifyRegistrationCode with the code
// 3. Call finishRegistration with the token and password
// Convert UuidValue to string - use the uuid property for proper formatting
final accountRequestIdString = accountRequestId.uuid;
debugPrint(
'[AuthRepository] signUp started. accountRequestId=$accountRequestIdString (original type: ${accountRequestId.runtimeType})',
);
return accountRequestIdString;
} catch (e, stackTrace) {
debugPrint('[AuthRepository] signUp error -> $e');
debugPrint('[AuthRepository] signUp stackTrace -> $stackTrace');
throw Exception('Sign up failed: ${e.toString()}');
}
}
/// Verify registration code and complete signup
Future<String> completeSignUp({
required String accountRequestId,
required String verificationCode,
required String password,
}) async {
try {
debugPrint(
'[AuthRepository] completeSignUp -> accountRequestId=$accountRequestId, verificationCode=$verificationCode',
);
// Convert string back to UuidValue
UuidValue uuidValue;
try {
uuidValue = UuidValue.fromString(accountRequestId);
debugPrint(
'[AuthRepository] Successfully parsed accountRequestId to UuidValue',
);
} catch (e) {
debugPrint('[AuthRepository] Failed to parse accountRequestId: $e');
throw Exception('Invalid accountRequestId format: $accountRequestId');
}
// Step 2: Verify the code
debugPrint('[AuthRepository] Calling verifyRegistrationCode...');
final registrationToken = await _client.emailIdp.verifyRegistrationCode(
accountRequestId: uuidValue,
verificationCode: verificationCode,
);
debugPrint(
'[AuthRepository] verifyRegistrationCode successful, got registrationToken',
);
// Step 3: Finish registration
debugPrint('[AuthRepository] Calling finishRegistration...');
await _client.emailIdp.finishRegistration(
registrationToken: registrationToken,
password: password,
);
// Registration complete - key is stored automatically
debugPrint('[AuthRepository] completeSignUp success.');
return accountRequestId;
} catch (e, stackTrace) {
debugPrint('[AuthRepository] completeSignUp error -> $e');
debugPrint('[AuthRepository] completeSignUp stackTrace -> $stackTrace');
throw Exception('Complete sign up failed: ${e.toString()}');
}
}
/// Sign out current user
Future<void> signOut() async {
try {
// Serverpod handles sign out through session management
// Clear the authentication key and user info
debugPrint('[AuthRepository] signOut');
await _client.authenticationKeyManager?.remove();
// Clear saved user info
final prefs = await SharedPreferences.getInstance();
await prefs.remove('user_email');
await prefs.remove('user_id');
debugPrint('[AuthRepository] Cleared saved user info');
} catch (e) {
debugPrint('[AuthRepository] signOut error -> $e');
throw Exception('Sign out failed: ${e.toString()}');
}
}
/// Get current user
/// Returns user data if authenticated, null otherwise
Future<Map<String, dynamic>?> getCurrentUser() async {
try {
// Check if we have an authentication key
final key = await _client.authenticationKeyManager?.get();
if (key == null) {
debugPrint('[AuthRepository] getCurrentUser: No authentication key');
return null;
}
// Retrieve saved user info from SharedPreferences
final prefs = await SharedPreferences.getInstance();
final savedEmail = prefs.getString('user_email');
final savedUserId = prefs.getString('user_id');
if (savedUserId == null) {
debugPrint('[AuthRepository] getCurrentUser: No saved user ID found');
return null;
}
debugPrint(
'[AuthRepository] getCurrentUser: Found saved user - id: $savedUserId, email: ${savedEmail ?? "not saved"}',
);
return {
'id': savedUserId,
'email': savedEmail ?? '',
'name': null, // Get from custom endpoint if needed
'createdAt': DateTime.now().toIso8601String(),
};
} catch (e) {
debugPrint('[AuthRepository] getCurrentUser error -> $e');
return null;
}
}
/// Check if user is authenticated
Future<bool> isAuthenticated() async {
try {
final key = await _client.authenticationKeyManager?.get();
return key != null;
} catch (e) {
debugPrint('[AuthRepository] isAuthenticated error -> $e');
return false;
}
}
/// Change password for authenticated user
Future<bool> changePassword(
String currentPassword,
String newPassword,
) async {
try {
debugPrint('[AuthRepository] changePassword');
// Get current user email from SharedPreferences
final prefs = await SharedPreferences.getInstance();
final email = prefs.getString('user_email');
if (email == null) {
throw Exception('User email not found. Please log in again.');
}
// Verify current password by attempting to authenticate
try {
await _client.emailIdp.login(email: email, password: currentPassword);
} catch (e) {
debugPrint('[AuthRepository] Current password verification failed: $e');
throw Exception('Current password is incorrect');
}
// Use dynamic client to call changePassword endpoint if it exists
// This follows the same pattern as password reset
final dynamic client = _client;
try {
final result =
await client.emailIdp.changePassword(
oldPassword: currentPassword,
newPassword: newPassword,
)
as bool;
debugPrint('[AuthRepository] changePassword success -> $result');
return result;
} catch (e) {
// If the endpoint doesn't exist, throw a clear error
debugPrint('[AuthRepository] changePassword endpoint error: $e');
throw Exception(
'Password change is not yet available. Please contact support.',
);
}
} catch (e) {
debugPrint('[AuthRepository] changePassword error -> $e');
rethrow;
}
}
/// Request password reset
Future<bool> requestPasswordReset(String email) async {
try {
debugPrint('[AuthRepository] requestPasswordReset -> $email');
// Call the password reset endpoint
// Note: This will be available after running 'serverpod generate'
final dynamic client = _client;
final result =
await client.passwordReset.requestPasswordReset(email) as bool;
debugPrint('[AuthRepository] requestPasswordReset success -> $result');
return result;
} catch (e) {
debugPrint('[AuthRepository] requestPasswordReset error -> $e');
// Return false on error, but don't throw to prevent email enumeration
return false;
}
}
/// Reset password with token
Future<bool> resetPassword(
String email,
String token,
String newPassword,
) async {
try {
debugPrint('[AuthRepository] resetPassword -> $email');
// Call the password reset endpoint
final dynamic client = _client;
final result =
await client.passwordReset.resetPassword(email, token, newPassword)
as bool;
debugPrint('[AuthRepository] resetPassword success -> $result');
return result;
} catch (e) {
debugPrint('[AuthRepository] resetPassword error -> $e');
throw Exception('Password reset failed: ${e.toString()}');
}
}
}