refactor: auth
This commit is contained in:
@@ -86,6 +86,8 @@ class AuthStateManager extends StateNotifier<AuthState> {
|
|||||||
|
|
||||||
final Ref _ref;
|
final Ref _ref;
|
||||||
final AuthCacheManager _cacheManager = AuthCacheManager();
|
final AuthCacheManager _cacheManager = AuthCacheManager();
|
||||||
|
// Prevent overlapping silent-login attempts from multiple triggers
|
||||||
|
Future<bool>? _silentLoginFuture;
|
||||||
|
|
||||||
/// Initialize auth state from storage
|
/// Initialize auth state from storage
|
||||||
Future<void> _initialize() async {
|
Future<void> _initialize() async {
|
||||||
@@ -330,6 +332,22 @@ class AuthStateManager extends StateNotifier<AuthState> {
|
|||||||
|
|
||||||
/// Perform silent auto-login with saved credentials
|
/// Perform silent auto-login with saved credentials
|
||||||
Future<bool> silentLogin() async {
|
Future<bool> silentLogin() async {
|
||||||
|
// Coalesce concurrent calls (e.g., UI + interceptor retry)
|
||||||
|
if (_silentLoginFuture != null) {
|
||||||
|
return await _silentLoginFuture!;
|
||||||
|
}
|
||||||
|
final thisAttempt = _performSilentLogin();
|
||||||
|
_silentLoginFuture = thisAttempt;
|
||||||
|
try {
|
||||||
|
return await thisAttempt;
|
||||||
|
} finally {
|
||||||
|
if (identical(_silentLoginFuture, thisAttempt)) {
|
||||||
|
_silentLoginFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _performSilentLogin() async {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
status: AuthStatus.loading,
|
status: AuthStatus.loading,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@@ -401,7 +419,11 @@ class AuthStateManager extends StateNotifier<AuthState> {
|
|||||||
|
|
||||||
/// Handle token invalidation (called by API service)
|
/// Handle token invalidation (called by API service)
|
||||||
Future<void> onTokenInvalidated() async {
|
Future<void> onTokenInvalidated() async {
|
||||||
DebugLogger.auth('Auth token invalidated');
|
// Avoid spamming logs if multiple requests invalidate at once
|
||||||
|
final reloginInProgress = _silentLoginFuture != null;
|
||||||
|
if (!reloginInProgress) {
|
||||||
|
DebugLogger.auth('Auth token invalidated');
|
||||||
|
}
|
||||||
|
|
||||||
// Clear token from storage
|
// Clear token from storage
|
||||||
final storage = _ref.read(optimizedStorageServiceProvider);
|
final storage = _ref.read(optimizedStorageServiceProvider);
|
||||||
@@ -418,7 +440,9 @@ class AuthStateManager extends StateNotifier<AuthState> {
|
|||||||
// Attempt silent re-login if credentials are available
|
// Attempt silent re-login if credentials are available
|
||||||
final hasCredentials = await storage.getSavedCredentials() != null;
|
final hasCredentials = await storage.getSavedCredentials() != null;
|
||||||
if (hasCredentials) {
|
if (hasCredentials) {
|
||||||
DebugLogger.auth('Attempting silent re-login after token invalidation');
|
if (!reloginInProgress) {
|
||||||
|
DebugLogger.auth('Attempting silent re-login after token invalidation');
|
||||||
|
}
|
||||||
await silentLogin();
|
await silentLogin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ final currentUserProvider = FutureProvider<User?>((ref) async {
|
|||||||
// Helper provider to force refresh auth state - now using unified system
|
// Helper provider to force refresh auth state - now using unified system
|
||||||
final refreshAuthStateProvider = Provider<void>((ref) {
|
final refreshAuthStateProvider = Provider<void>((ref) {
|
||||||
// This provider can be invalidated to force refresh the unified auth system
|
// This provider can be invalidated to force refresh the unified auth system
|
||||||
ref.read(refreshAuthProvider)();
|
Future.microtask(() => ref.read(authActionsProvider).refresh());
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,35 +5,53 @@ import '../../../core/providers/app_providers.dart';
|
|||||||
/// Unified auth providers using the new auth state manager
|
/// Unified auth providers using the new auth state manager
|
||||||
/// These replace the old auth providers for better efficiency
|
/// These replace the old auth providers for better efficiency
|
||||||
|
|
||||||
/// Login action provider (schedules side-effect outside provider build)
|
/// Imperative auth actions wrapper to avoid side-effects during provider build
|
||||||
final loginActionProvider = Provider.family<Future<bool>, Map<String, String>>(
|
class AuthActions {
|
||||||
(ref, credentials) async {
|
final Ref _ref;
|
||||||
final authManager = ref.read(authStateManagerProvider.notifier);
|
AuthActions(this._ref);
|
||||||
|
|
||||||
final username = credentials['username']!;
|
AuthStateManager get _auth =>
|
||||||
final password = credentials['password']!;
|
_ref.read(authStateManagerProvider.notifier);
|
||||||
final rememberCredentials = credentials['remember'] == 'true';
|
|
||||||
|
|
||||||
// Defer the mutation to avoid changing other providers during build
|
Future<bool> login(
|
||||||
return Future(() => authManager.login(
|
String username,
|
||||||
|
String password, {
|
||||||
|
bool rememberCredentials = false,
|
||||||
|
}) {
|
||||||
|
// Defer mutation to a microtask to avoid provider-build side-effects
|
||||||
|
return Future(() => _auth.login(
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
rememberCredentials: rememberCredentials,
|
rememberCredentials: rememberCredentials,
|
||||||
));
|
));
|
||||||
},
|
}
|
||||||
);
|
|
||||||
|
|
||||||
/// Silent login action provider - returns a callback to run later
|
Future<bool> loginWithApiKey(
|
||||||
final silentLoginActionProvider = Provider<Future<bool> Function()>((ref) {
|
String apiKey, {
|
||||||
final authManager = ref.read(authStateManagerProvider.notifier);
|
bool rememberCredentials = false,
|
||||||
return () => authManager.silentLogin();
|
}) {
|
||||||
});
|
return Future(() => _auth.loginWithApiKey(
|
||||||
|
apiKey,
|
||||||
|
rememberCredentials: rememberCredentials,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/// Logout action provider - returns a callback to run later
|
Future<bool> silentLogin() {
|
||||||
final logoutActionProvider = Provider<Future<void> Function()>((ref) {
|
return Future(() => _auth.silentLogin());
|
||||||
final authManager = ref.read(authStateManagerProvider.notifier);
|
}
|
||||||
return () => authManager.logout();
|
|
||||||
});
|
Future<void> logout() {
|
||||||
|
return Future(() => _auth.logout());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() {
|
||||||
|
return Future(() => _auth.refresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final authActionsProvider = Provider<AuthActions>((ref) => AuthActions(ref));
|
||||||
|
|
||||||
|
// Legacy action providers have been replaced by `authActionsProvider`
|
||||||
|
|
||||||
/// Check if saved credentials exist
|
/// Check if saved credentials exist
|
||||||
final hasSavedCredentialsProvider2 = FutureProvider<bool>((ref) async {
|
final hasSavedCredentialsProvider2 = FutureProvider<bool>((ref) async {
|
||||||
@@ -70,11 +88,7 @@ final authStatusProvider = Provider<AuthStatus>((ref) {
|
|||||||
return ref.watch(authStateManagerProvider.select((state) => state.status));
|
return ref.watch(authStateManagerProvider.select((state) => state.status));
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Helper provider to trigger auth refresh - returns a callback
|
// Use `ref.read(authActionsProvider).refresh()` instead of refresh providers
|
||||||
final refreshAuthProvider = Provider<Future<void> Function()>((ref) {
|
|
||||||
final authManager = ref.read(authStateManagerProvider.notifier);
|
|
||||||
return () => authManager.refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Provider to watch for auth state changes and update API service
|
/// Provider to watch for auth state changes and update API service
|
||||||
final authApiIntegrationProvider = Provider<void>((ref) {
|
final authApiIntegrationProvider = Provider<void>((ref) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import '../../../shared/widgets/conduit_components.dart';
|
|||||||
import '../../../core/auth/auth_state_manager.dart';
|
import '../../../core/auth/auth_state_manager.dart';
|
||||||
import '../../../core/utils/debug_logger.dart';
|
import '../../../core/utils/debug_logger.dart';
|
||||||
import 'package:conduit/l10n/app_localizations.dart';
|
import 'package:conduit/l10n/app_localizations.dart';
|
||||||
|
import '../providers/unified_auth_providers.dart';
|
||||||
|
|
||||||
class AuthenticationPage extends ConsumerStatefulWidget {
|
class AuthenticationPage extends ConsumerStatefulWidget {
|
||||||
final ServerConfig serverConfig;
|
final ServerConfig serverConfig;
|
||||||
@@ -71,16 +72,16 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final authManager = ref.read(authStateManagerProvider.notifier);
|
final actions = ref.read(authActionsProvider);
|
||||||
bool success;
|
bool success;
|
||||||
|
|
||||||
if (_useApiKey) {
|
if (_useApiKey) {
|
||||||
success = await authManager.loginWithApiKey(
|
success = await actions.loginWithApiKey(
|
||||||
_apiKeyController.text.trim(),
|
_apiKeyController.text.trim(),
|
||||||
rememberCredentials: true, // Consistent with credentials method
|
rememberCredentials: true, // Consistent with credentials method
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
success = await authManager.login(
|
success = await actions.login(
|
||||||
_usernameController.text.trim(),
|
_usernameController.text.trim(),
|
||||||
_passwordController.text,
|
_passwordController.text,
|
||||||
rememberCredentials: true,
|
rememberCredentials: true,
|
||||||
|
|||||||
@@ -762,7 +762,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
await ref.read(logoutActionProvider)();
|
await ref.read(authActionsProvider).logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
|
|||||||
hasSavedCredentialsProvider2.future,
|
hasSavedCredentialsProvider2.future,
|
||||||
);
|
);
|
||||||
if (hasCreds) {
|
if (hasCreds) {
|
||||||
await ref.read(silentLoginActionProvider)();
|
await ref.read(authActionsProvider).silentLogin();
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Ignore errors, fallback to showing unified page
|
// Ignore errors, fallback to showing unified page
|
||||||
|
|||||||
Reference in New Issue
Block a user