refactor(storage): remove remember-credentials and improve error logging and handling
This commit is contained in:
@@ -244,7 +244,6 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
'api_key_user', // Special username to indicate API key auth
|
'api_key_user', // Special username to indicate API key auth
|
||||||
password: tokenStr, // Store API key in password field
|
password: tokenStr, // Store API key in password field
|
||||||
);
|
);
|
||||||
await storage.setRememberCredentials(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,8 +272,13 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
// If user fetch fails, the API key might be invalid
|
// If user fetch fails, the API key might be invalid
|
||||||
throw Exception('Invalid API key or insufficient permissions');
|
throw Exception('Invalid API key or insufficient permissions');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
DebugLogger.error('api-key-login-failed', scope: 'auth/state', error: e);
|
DebugLogger.error(
|
||||||
|
'api-key-login-failed',
|
||||||
|
scope: 'auth/state',
|
||||||
|
error: e,
|
||||||
|
stackTrace: stack,
|
||||||
|
);
|
||||||
_update(
|
_update(
|
||||||
(current) => current.copyWith(
|
(current) => current.copyWith(
|
||||||
status: AuthStatus.error,
|
status: AuthStatus.error,
|
||||||
@@ -283,7 +287,7 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
clearToken: true,
|
clearToken: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +340,6 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
);
|
);
|
||||||
await storage.setRememberCredentials(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,8 +363,13 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
|
|
||||||
DebugLogger.auth('Login successful');
|
DebugLogger.auth('Login successful');
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
DebugLogger.error('login-failed', scope: 'auth/state', error: e);
|
DebugLogger.error(
|
||||||
|
'login-failed',
|
||||||
|
scope: 'auth/state',
|
||||||
|
error: e,
|
||||||
|
stackTrace: stack,
|
||||||
|
);
|
||||||
_update(
|
_update(
|
||||||
(current) => current.copyWith(
|
(current) => current.copyWith(
|
||||||
status: AuthStatus.error,
|
status: AuthStatus.error,
|
||||||
@@ -370,7 +378,7 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
clearToken: true,
|
clearToken: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,8 +486,15 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
// Normal username/password credentials
|
// Normal username/password credentials
|
||||||
return await login(username, password, rememberCredentials: false);
|
return await login(username, password, rememberCredentials: false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
DebugLogger.error('silent-login-failed', scope: 'auth/state', error: e);
|
DebugLogger.error(
|
||||||
|
'silent-login-failed',
|
||||||
|
scope: 'auth/state',
|
||||||
|
error: e,
|
||||||
|
stackTrace: stack,
|
||||||
|
);
|
||||||
|
|
||||||
|
String errorMessage = e.toString();
|
||||||
|
|
||||||
// Clear invalid credentials on auth errors
|
// Clear invalid credentials on auth errors
|
||||||
if (e.toString().contains('401') ||
|
if (e.toString().contains('401') ||
|
||||||
@@ -487,13 +502,26 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
e.toString().contains('authentication') ||
|
e.toString().contains('authentication') ||
|
||||||
e.toString().contains('unauthorized')) {
|
e.toString().contains('unauthorized')) {
|
||||||
final storage = ref.read(optimizedStorageServiceProvider);
|
final storage = ref.read(optimizedStorageServiceProvider);
|
||||||
await storage.deleteSavedCredentials();
|
try {
|
||||||
|
await storage.deleteSavedCredentials();
|
||||||
|
} catch (deleteError, deleteStack) {
|
||||||
|
DebugLogger.error(
|
||||||
|
'silent-login-credential-clear-failed',
|
||||||
|
scope: 'auth/state',
|
||||||
|
error: deleteError,
|
||||||
|
stackTrace: deleteStack,
|
||||||
|
);
|
||||||
|
errorMessage =
|
||||||
|
'$errorMessage. Also failed to clear saved '
|
||||||
|
'credentials; please clear Conduit credentials from '
|
||||||
|
'system settings.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_update(
|
_update(
|
||||||
(current) => current.copyWith(
|
(current) => current.copyWith(
|
||||||
status: AuthStatus.unauthenticated,
|
status: AuthStatus.unauthenticated,
|
||||||
error: e.toString(),
|
error: errorMessage,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
clearToken: true,
|
clearToken: true,
|
||||||
),
|
),
|
||||||
@@ -512,8 +540,30 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
|
|
||||||
// Clear token from storage
|
// Clear token from storage
|
||||||
final storage = ref.read(optimizedStorageServiceProvider);
|
final storage = ref.read(optimizedStorageServiceProvider);
|
||||||
await storage.deleteAuthToken();
|
try {
|
||||||
_updateApiServiceToken(null);
|
await storage.deleteAuthToken();
|
||||||
|
_updateApiServiceToken(null);
|
||||||
|
} catch (error, stack) {
|
||||||
|
DebugLogger.error(
|
||||||
|
'token-delete-failed',
|
||||||
|
scope: 'auth/state',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stack,
|
||||||
|
);
|
||||||
|
_updateApiServiceToken(null);
|
||||||
|
_update(
|
||||||
|
(current) => current.copyWith(
|
||||||
|
status: AuthStatus.error,
|
||||||
|
error:
|
||||||
|
'Failed to clear secure token. Please clear Conduit '
|
||||||
|
'credentials from your device keychain and sign in again.',
|
||||||
|
clearToken: true,
|
||||||
|
clearUser: true,
|
||||||
|
clearError: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
_update(
|
_update(
|
||||||
@@ -578,9 +628,14 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
DebugLogger.auth('Logout complete');
|
DebugLogger.auth('Logout complete');
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
DebugLogger.error('logout-failed', scope: 'auth/state', error: e);
|
DebugLogger.error(
|
||||||
// Even if logout fails, clear local state
|
'logout-failed',
|
||||||
|
scope: 'auth/state',
|
||||||
|
error: e,
|
||||||
|
stackTrace: stack,
|
||||||
|
);
|
||||||
|
// Even if logout fails, clear local state where possible
|
||||||
final storage = ref.read(optimizedStorageServiceProvider);
|
final storage = ref.read(optimizedStorageServiceProvider);
|
||||||
await storage.setActiveServerId(null);
|
await storage.setActiveServerId(null);
|
||||||
ref.invalidate(activeServerProvider);
|
ref.invalidate(activeServerProvider);
|
||||||
@@ -591,7 +646,9 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
clearToken: true,
|
clearToken: true,
|
||||||
clearUser: true,
|
clearUser: true,
|
||||||
error: 'Logout error: $e',
|
error:
|
||||||
|
'Logout error: $e. Secure credentials may remain stored; '
|
||||||
|
'please clear them from your device keychain.',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_updateApiServiceToken(null);
|
_updateApiServiceToken(null);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ final class PreferenceKeys {
|
|||||||
static const String socketTransportMode = 'socket_transport_mode';
|
static const String socketTransportMode = 'socket_transport_mode';
|
||||||
static const String quickPills = 'quick_pills';
|
static const String quickPills = 'quick_pills';
|
||||||
static const String sendOnEnterKey = 'send_on_enter';
|
static const String sendOnEnterKey = 'send_on_enter';
|
||||||
static const String rememberCredentials = 'remember_credentials';
|
|
||||||
static const String activeServerId = 'active_server_id';
|
static const String activeServerId = 'active_server_id';
|
||||||
static const String themeMode = 'theme_mode';
|
static const String themeMode = 'theme_mode';
|
||||||
static const String themePalette = 'theme_palette_v1';
|
static const String themePalette = 'theme_palette_v1';
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ class PersistenceMigrator {
|
|||||||
copyString(PreferenceKeys.socketTransportMode);
|
copyString(PreferenceKeys.socketTransportMode);
|
||||||
copyStringList(PreferenceKeys.quickPills);
|
copyStringList(PreferenceKeys.quickPills);
|
||||||
copyBool(PreferenceKeys.sendOnEnterKey);
|
copyBool(PreferenceKeys.sendOnEnterKey);
|
||||||
copyBool(PreferenceKeys.rememberCredentials);
|
|
||||||
copyString(PreferenceKeys.activeServerId);
|
copyString(PreferenceKeys.activeServerId);
|
||||||
copyString(PreferenceKeys.themeMode);
|
copyString(PreferenceKeys.themeMode);
|
||||||
copyString(PreferenceKeys.themePalette);
|
copyString(PreferenceKeys.themePalette);
|
||||||
@@ -198,7 +197,6 @@ class PersistenceMigrator {
|
|||||||
PreferenceKeys.socketTransportMode,
|
PreferenceKeys.socketTransportMode,
|
||||||
PreferenceKeys.quickPills,
|
PreferenceKeys.quickPills,
|
||||||
PreferenceKeys.sendOnEnterKey,
|
PreferenceKeys.sendOnEnterKey,
|
||||||
PreferenceKeys.rememberCredentials,
|
|
||||||
PreferenceKeys.activeServerId,
|
PreferenceKeys.activeServerId,
|
||||||
PreferenceKeys.themeMode,
|
PreferenceKeys.themeMode,
|
||||||
PreferenceKeys.themePalette,
|
PreferenceKeys.themePalette,
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ class OptimizedStorageService {
|
|||||||
|
|
||||||
static const String _authTokenKey = 'auth_token_v3';
|
static const String _authTokenKey = 'auth_token_v3';
|
||||||
static const String _activeServerIdKey = PreferenceKeys.activeServerId;
|
static const String _activeServerIdKey = PreferenceKeys.activeServerId;
|
||||||
static const String _rememberCredentialsKey =
|
|
||||||
PreferenceKeys.rememberCredentials;
|
|
||||||
static const String _themeModeKey = PreferenceKeys.themeMode;
|
static const String _themeModeKey = PreferenceKeys.themeMode;
|
||||||
static const String _themePaletteKey = PreferenceKeys.themePalette;
|
static const String _themePaletteKey = PreferenceKeys.themePalette;
|
||||||
static const String _localeCodeKey = PreferenceKeys.localeCode;
|
static const String _localeCodeKey = PreferenceKeys.localeCode;
|
||||||
@@ -101,10 +99,12 @@ class OptimizedStorageService {
|
|||||||
scope: 'storage/optimized',
|
scope: 'storage/optimized',
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
DebugLogger.log(
|
DebugLogger.error(
|
||||||
'Failed to delete auth token: $error',
|
'Failed to delete auth token',
|
||||||
scope: 'storage/optimized',
|
scope: 'storage/optimized',
|
||||||
|
error: error,
|
||||||
);
|
);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +164,12 @@ class OptimizedStorageService {
|
|||||||
scope: 'storage/optimized',
|
scope: 'storage/optimized',
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
DebugLogger.log(
|
DebugLogger.error(
|
||||||
'Failed to delete credentials: $error',
|
'Failed to delete credentials',
|
||||||
scope: 'storage/optimized',
|
scope: 'storage/optimized',
|
||||||
|
error: error,
|
||||||
);
|
);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,14 +184,6 @@ class OptimizedStorageService {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Preference helpers (Hive-backed)
|
// Preference helpers (Hive-backed)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
Future<void> setRememberCredentials(bool remember) async {
|
|
||||||
await _preferencesBox.put(_rememberCredentialsKey, remember);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getRememberCredentials() {
|
|
||||||
return (_preferencesBox.get(_rememberCredentialsKey) as bool?) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveServerConfigs(List<ServerConfig> configs) async {
|
Future<void> saveServerConfigs(List<ServerConfig> configs) async {
|
||||||
try {
|
try {
|
||||||
final jsonString = jsonEncode(configs.map((c) => c.toJson()).toList());
|
final jsonString = jsonEncode(configs.map((c) => c.toJson()).toList());
|
||||||
@@ -352,37 +346,29 @@ class OptimizedStorageService {
|
|||||||
// Batch operations
|
// Batch operations
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
Future<void> clearAuthData() async {
|
Future<void> clearAuthData() async {
|
||||||
try {
|
await Future.wait([
|
||||||
await Future.wait([
|
deleteAuthToken(),
|
||||||
deleteAuthToken(),
|
deleteSavedCredentials(),
|
||||||
deleteSavedCredentials(),
|
_preferencesBox.delete(_activeServerIdKey),
|
||||||
_preferencesBox.delete(_rememberCredentialsKey),
|
]);
|
||||||
_preferencesBox.delete(_activeServerIdKey),
|
|
||||||
]);
|
|
||||||
|
|
||||||
_cache.removeWhere(
|
_cache.removeWhere(
|
||||||
(key, _) =>
|
(key, _) =>
|
||||||
key.contains('auth') ||
|
key.contains('auth') ||
|
||||||
key.contains('credentials') ||
|
key.contains('credentials') ||
|
||||||
key.contains('server'),
|
key.contains('server'),
|
||||||
);
|
);
|
||||||
_cacheTimestamps.removeWhere(
|
_cacheTimestamps.removeWhere(
|
||||||
(key, _) =>
|
(key, _) =>
|
||||||
key.contains('auth') ||
|
key.contains('auth') ||
|
||||||
key.contains('credentials') ||
|
key.contains('credentials') ||
|
||||||
key.contains('server'),
|
key.contains('server'),
|
||||||
);
|
);
|
||||||
|
|
||||||
DebugLogger.log(
|
DebugLogger.log(
|
||||||
'Auth data cleared in batch operation',
|
'Auth data cleared in batch operation',
|
||||||
scope: 'storage/optimized',
|
scope: 'storage/optimized',
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
DebugLogger.log(
|
|
||||||
'Failed to clear auth data: $error',
|
|
||||||
scope: 'storage/optimized',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAll() async {
|
Future<void> clearAll() async {
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ class SecureCredentialStorage {
|
|||||||
DebugLogger.storage('delete-ok', scope: 'credentials');
|
DebugLogger.storage('delete-ok', scope: 'credentials');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DebugLogger.error('delete-failed', scope: 'credentials', error: e);
|
DebugLogger.error('delete-failed', scope: 'credentials', error: e);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +217,7 @@ class SecureCredentialStorage {
|
|||||||
scope: 'credentials/token',
|
scope: 'credentials/token',
|
||||||
error: e,
|
error: e,
|
||||||
);
|
);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user