This commit is contained in:
Phuoc Nguyen
2025-11-10 14:21:27 +07:00
parent 2a71c65577
commit 36bdf6613b
33 changed files with 2206 additions and 252 deletions

View File

@@ -20,6 +20,7 @@ class AuthLocalDataSource {
static const String _fullNameKey = 'auth_session_full_name';
static const String _createdAtKey = 'auth_session_created_at';
static const String _appsKey = 'auth_session_apps';
static const String _rememberMeKey = 'auth_remember_me';
AuthLocalDataSource(this._secureStorage);
@@ -102,21 +103,46 @@ class AuthLocalDataSource {
return sid != null && csrfToken != null;
}
/// Save "Remember Me" preference
///
/// If true, user session will be restored on next app launch.
Future<void> saveRememberMe(bool rememberMe) async {
await _secureStorage.write(
key: _rememberMeKey,
value: rememberMe.toString(),
);
}
/// Get "Remember Me" preference
///
/// Returns true if user wants to be remembered, false otherwise.
Future<bool> getRememberMe() async {
final value = await _secureStorage.read(key: _rememberMeKey);
return value == 'true';
}
/// Clear session data
///
/// Called during logout to remove all session information.
/// Called during logout to remove all session information including rememberMe.
Future<void> clearSession() async {
// Clear all session data including rememberMe
await _secureStorage.delete(key: _sidKey);
await _secureStorage.delete(key: _csrfTokenKey);
await _secureStorage.delete(key: _fullNameKey);
await _secureStorage.delete(key: _createdAtKey);
await _secureStorage.delete(key: _appsKey);
await _secureStorage.delete(key: _rememberMeKey);
}
/// Clear all authentication data
/// Clear all authentication data including remember me
///
/// Complete cleanup of all stored auth data.
Future<void> clearAll() async {
await _secureStorage.deleteAll();
await _secureStorage.delete(key: _sidKey);
await _secureStorage.delete(key: _csrfTokenKey);
await _secureStorage.delete(key: _fullNameKey);
await _secureStorage.delete(key: _createdAtKey);
await _secureStorage.delete(key: _appsKey);
await _secureStorage.delete(key: _rememberMeKey);
}
}

View File

@@ -56,6 +56,64 @@ class AuthRemoteDataSource {
}
}
/// Login
///
/// Authenticates user with phone number.
/// Requires existing session (CSRF token and Cookie).
/// Returns new session with user credentials.
///
/// API: POST /api/method/building_material.building_material.api.auth.login
/// Body: { "username": "phone", "googleid": null, "facebookid": null, "zaloid": null }
///
/// Response includes new sid and csrf_token for authenticated user.
Future<GetSessionResponse> login({
required String phone,
required String csrfToken,
required String sid,
String? password, // Reserved for future use
}) async {
try {
final response = await _dio.post<Map<String, dynamic>>(
'/api/method/building_material.building_material.api.auth.login',
data: {
'username': phone,
'googleid': null,
'facebookid': null,
'zaloid': null,
// Password field reserved for future use
// 'password': password,
},
options: Options(
headers: {
'X-Frappe-Csrf-Token': csrfToken,
'Cookie': 'sid=$sid',
'Content-Type': 'application/json',
},
),
);
if (response.statusCode == 200 && response.data != null) {
return GetSessionResponse.fromJson(response.data!);
} else {
throw ServerException(
'Login failed: ${response.statusCode}',
);
}
} on DioException catch (e) {
if (e.response?.statusCode == 401) {
throw const UnauthorizedException('Invalid credentials');
} else if (e.response?.statusCode == 404) {
throw NotFoundException('Login endpoint not found');
} else {
throw NetworkException(
e.message ?? 'Failed to login',
);
}
} catch (e) {
throw ServerException('Unexpected error during login: $e');
}
}
/// Get Cities
///
/// Fetches list of cities/provinces for address selection.