189 lines
5.0 KiB
Dart
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) {
|
|
final 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));
|
|
});
|