Files
mobile/lib/core/auth/auth_provider.dart
bastien a4c4423678
Laverie/mobile/pipeline/head This commit looks good
maj tests
2026-07-05 00:01:55 +02:00

189 lines
5.0 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../api/api_client.dart';
import '../config/app_config.dart';
import '../config/secure_storage_config.dart';
import '../../features/auth/domain/auth_user.dart';
import 'auth_repository.dart';
/// État d'authentification de l'application.
class AuthState {
const AuthState({
this.user,
this.accessToken,
this.refreshToken,
this.isLoading = false,
this.error,
});
final AuthUser? user;
final String? accessToken;
final String? refreshToken;
final bool isLoading;
final String? error;
bool get isAuthenticated => accessToken != null && accessToken!.isNotEmpty;
AuthState copyWith({
AuthUser? user,
String? accessToken,
String? refreshToken,
bool? isLoading,
String? error,
bool clearError = false,
bool clearUser = false,
}) {
return AuthState(
user: clearUser ? null : (user ?? this.user),
accessToken: accessToken ?? this.accessToken,
refreshToken: refreshToken ?? this.refreshToken,
isLoading: isLoading ?? this.isLoading,
error: clearError ? null : (error ?? this.error),
);
}
}
/// Gestionnaire d'état Riverpod pour l'authentification.
class AuthNotifier extends StateNotifier<AuthState> {
AuthNotifier(this._repository) : super(const AuthState(isLoading: true)) {
_restoreSession();
}
final AuthRepository _repository;
/// Tente de renouveler le token d'accès (appelé par le client API sur 401).
Future<String?> refreshAccessToken() async {
final refreshed = await _repository.refresh();
if (refreshed == null) {
return null;
}
state = state.copyWith(
accessToken: refreshed.accessToken,
refreshToken: refreshed.refreshToken,
user: refreshed.user ?? state.user,
);
return refreshed.accessToken;
}
/// Session expirée après échec du refresh.
Future<void> sessionExpired() async {
await logout();
}
Future<void> _restoreSession() async {
state = state.copyWith(isLoading: true, clearError: true);
try {
final stored = await _repository.loadStoredTokens();
if (stored == null) {
state = state.copyWith(isLoading: false);
return;
}
state = state.copyWith(
accessToken: stored.accessToken,
refreshToken: stored.refreshToken,
);
final user = await _repository.fetchCurrentUser();
state = state.copyWith(user: user, isLoading: false);
} catch (_) {
final refreshed = await _repository.refresh();
if (refreshed != null) {
final user = await _repository.fetchCurrentUser();
state = state.copyWith(
accessToken: refreshed.accessToken,
refreshToken: refreshed.refreshToken,
user: user,
isLoading: false,
);
} else {
await _repository.clearStoredTokens();
state = const AuthState(isLoading: false);
}
}
}
Future<bool> login(String email, String password) async {
state = state.copyWith(isLoading: true, clearError: true);
try {
final tokens = await _repository.login(email: email, password: password);
final user = tokens.user ?? await _repository.fetchCurrentUser();
state = state.copyWith(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
user: user,
isLoading: false,
);
return true;
} catch (e) {
state = state.copyWith(
isLoading: false,
error: 'Connexion impossible. Vérifiez vos identifiants.',
);
return false;
}
}
Future<bool> register({
required String firstName,
required String lastName,
required String email,
required String password,
String? phone,
}) async {
state = state.copyWith(isLoading: true, clearError: true);
try {
final tokens = await _repository.register(
firstName: firstName,
lastName: lastName,
email: email,
password: password,
phone: phone,
);
final user = tokens.user ?? await _repository.fetchCurrentUser();
state = state.copyWith(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
user: user,
isLoading: false,
);
return true;
} catch (e) {
state = state.copyWith(
isLoading: false,
error: 'Inscription impossible. L\'email est peut-être déjà utilisé.',
);
return false;
}
}
Future<void> logout() async {
await _repository.logout();
state = const AuthState();
}
}
final authRepositoryProvider = Provider<AuthRepository>((ref) {
const storage = laverieSecureStorage;
// Token lu depuis le stockage sécurisé (évite la dépendance circulaire avec authProvider).
return AuthRepository(
apiClient: ApiClient(
baseUrl: kApiBaseUrl,
getAccessToken: () => storage.read(key: AuthStorageKeys.accessToken),
),
secureStorage: storage,
);
});
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref.watch(authRepositoryProvider));
});