feat(auth): Add LDAP and SSO authentication support
This commit is contained in:
@@ -355,6 +355,46 @@ class ApiService {
|
||||
await _dio.get('/api/v1/auths/signout');
|
||||
}
|
||||
|
||||
/// LDAP authentication - uses username instead of email.
|
||||
///
|
||||
/// Returns the same response format as regular login:
|
||||
/// `{"token": "...", "token_type": "Bearer", "id": "...", ...}`
|
||||
///
|
||||
/// Throws an exception if LDAP is not enabled on the server (400 response).
|
||||
Future<Map<String, dynamic>> ldapLogin(
|
||||
String username,
|
||||
String password,
|
||||
) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/api/v1/auths/ldap',
|
||||
data: {'user': username, 'password': password},
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
if (e is DioException) {
|
||||
// Handle LDAP not enabled
|
||||
if (e.response?.statusCode == 400) {
|
||||
final data = e.response?.data;
|
||||
if (data is Map && data['detail'] != null) {
|
||||
throw Exception(data['detail']);
|
||||
}
|
||||
}
|
||||
// Handle specific redirect cases
|
||||
if (e.response?.statusCode == 307 || e.response?.statusCode == 308) {
|
||||
final location = e.response?.headers.value('location');
|
||||
if (location != null) {
|
||||
throw Exception(
|
||||
'Server redirect detected. Please check your server URL configuration. Redirect to: $location',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// User info
|
||||
Future<User> getCurrentUser() async {
|
||||
final response = await _dio.get('/api/v1/auths/');
|
||||
|
||||
@@ -101,6 +101,7 @@ class Routes {
|
||||
static const String serverConnection = '/server-connection';
|
||||
static const String connectionIssue = '/connection-issue';
|
||||
static const String authentication = '/authentication';
|
||||
static const String ssoAuth = '/sso-auth';
|
||||
static const String profile = '/profile';
|
||||
static const String appCustomization = '/profile/customization';
|
||||
static const String notes = '/notes';
|
||||
@@ -115,6 +116,7 @@ class RouteNames {
|
||||
static const String serverConnection = 'server-connection';
|
||||
static const String connectionIssue = 'connection-issue';
|
||||
static const String authentication = 'authentication';
|
||||
static const String ssoAuth = 'sso-auth';
|
||||
static const String profile = 'profile';
|
||||
static const String appCustomization = 'app-customization';
|
||||
static const String notes = 'notes';
|
||||
|
||||
@@ -132,12 +132,14 @@ class OptimizedStorageService {
|
||||
required String serverId,
|
||||
required String username,
|
||||
required String password,
|
||||
String authType = 'credentials',
|
||||
}) async {
|
||||
try {
|
||||
await _secureCredentialStorage.saveCredentials(
|
||||
serverId: serverId,
|
||||
username: username,
|
||||
password: password,
|
||||
authType: authType,
|
||||
);
|
||||
|
||||
_cacheManager.write('has_credentials', true, ttl: _credentialsFlagTtl);
|
||||
|
||||
@@ -40,11 +40,18 @@ class SecureCredentialStorage {
|
||||
);
|
||||
}
|
||||
|
||||
/// Save user credentials securely
|
||||
/// Save user credentials securely.
|
||||
///
|
||||
/// [authType] identifies the authentication method:
|
||||
/// - 'credentials': Standard email/password login (default)
|
||||
/// - 'ldap': LDAP directory authentication
|
||||
/// - 'token': Manual JWT token entry
|
||||
/// - 'sso': JWT token obtained via SSO/OAuth flow
|
||||
Future<void> saveCredentials({
|
||||
required String serverId,
|
||||
required String username,
|
||||
required String password,
|
||||
String authType = 'credentials',
|
||||
}) async {
|
||||
try {
|
||||
// First check if secure storage is available
|
||||
@@ -57,9 +64,10 @@ class SecureCredentialStorage {
|
||||
'serverId': serverId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'authType': authType,
|
||||
'savedAt': DateTime.now().toIso8601String(),
|
||||
'deviceId': await _getDeviceFingerprint(),
|
||||
'version': '2.0', // Version for migration purposes
|
||||
'version': '2.1', // Version for migration purposes
|
||||
};
|
||||
|
||||
final encryptedData = await _encryptData(jsonEncode(credentials));
|
||||
@@ -76,7 +84,7 @@ class SecureCredentialStorage {
|
||||
DebugLogger.storage(
|
||||
'save-ok',
|
||||
scope: 'credentials',
|
||||
data: {'version': '2.0'},
|
||||
data: {'version': '2.1'},
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.error('save-failed', scope: 'credentials', error: e);
|
||||
@@ -156,6 +164,7 @@ class SecureCredentialStorage {
|
||||
'username': decoded['username']?.toString() ?? '',
|
||||
'password': decoded['password']?.toString() ?? '',
|
||||
'savedAt': decoded['savedAt']?.toString() ?? '',
|
||||
'authType': decoded['authType']?.toString() ?? 'credentials',
|
||||
};
|
||||
} catch (e) {
|
||||
DebugLogger.error('read-failed', scope: 'credentials', error: e);
|
||||
@@ -355,7 +364,9 @@ class SecureCredentialStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate from old storage format if needed
|
||||
/// Migrate from old storage format if needed.
|
||||
///
|
||||
/// Preserves the [authType] if present in old credentials.
|
||||
Future<void> migrateFromOldStorage(
|
||||
Map<String, String>? oldCredentials,
|
||||
) async {
|
||||
@@ -366,6 +377,7 @@ class SecureCredentialStorage {
|
||||
serverId: oldCredentials['serverId'] ?? '',
|
||||
username: oldCredentials['username'] ?? '',
|
||||
password: oldCredentials['password'] ?? '',
|
||||
authType: oldCredentials['authType'] ?? 'credentials',
|
||||
);
|
||||
DebugLogger.storage('migrate-ok', scope: 'credentials');
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user