first commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import 'dart:convert';
|
||||
import '../models/login_response.dart';
|
||||
import '../models/user_info.dart';
|
||||
|
||||
/// Service de stockage en mémoire des tokens (temporaire pour contourner Java 21)
|
||||
class MemoryTokenStorage {
|
||||
static final MemoryTokenStorage _instance = MemoryTokenStorage._internal();
|
||||
factory MemoryTokenStorage() => _instance;
|
||||
MemoryTokenStorage._internal();
|
||||
|
||||
// Stockage en mémoire
|
||||
final Map<String, String> _storage = {};
|
||||
|
||||
static const String _accessTokenKey = 'access_token';
|
||||
static const String _refreshTokenKey = 'refresh_token';
|
||||
static const String _userInfoKey = 'user_info';
|
||||
static const String _expiresAtKey = 'expires_at';
|
||||
static const String _refreshExpiresAtKey = 'refresh_expires_at';
|
||||
|
||||
/// Sauvegarde les données d'authentification
|
||||
Future<void> saveAuthData(LoginResponse loginResponse) async {
|
||||
try {
|
||||
_storage[_accessTokenKey] = loginResponse.accessToken;
|
||||
_storage[_refreshTokenKey] = loginResponse.refreshToken;
|
||||
_storage[_userInfoKey] = jsonEncode(loginResponse.user.toJson());
|
||||
_storage[_expiresAtKey] = loginResponse.expiresAt.toIso8601String();
|
||||
_storage[_refreshExpiresAtKey] = loginResponse.refreshExpiresAt.toIso8601String();
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la sauvegarde des données d\'authentification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère le token d'accès
|
||||
Future<String?> getAccessToken() async {
|
||||
return _storage[_accessTokenKey];
|
||||
}
|
||||
|
||||
/// Récupère le refresh token
|
||||
Future<String?> getRefreshToken() async {
|
||||
return _storage[_refreshTokenKey];
|
||||
}
|
||||
|
||||
/// Récupère les informations utilisateur
|
||||
Future<UserInfo?> getUserInfo() async {
|
||||
try {
|
||||
final userJson = _storage[_userInfoKey];
|
||||
if (userJson == null) return null;
|
||||
|
||||
final userMap = jsonDecode(userJson) as Map<String, dynamic>;
|
||||
return UserInfo.fromJson(userMap);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération des informations utilisateur: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère la date d'expiration du token d'accès
|
||||
Future<DateTime?> getTokenExpirationDate() async {
|
||||
try {
|
||||
final expiresAtString = _storage[_expiresAtKey];
|
||||
if (expiresAtString == null) return null;
|
||||
|
||||
return DateTime.parse(expiresAtString);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération de la date d\'expiration: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère la date d'expiration du refresh token
|
||||
Future<DateTime?> getRefreshTokenExpirationDate() async {
|
||||
try {
|
||||
final expiresAtString = _storage[_refreshExpiresAtKey];
|
||||
if (expiresAtString == null) return null;
|
||||
|
||||
return DateTime.parse(expiresAtString);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération de la date d\'expiration du refresh token: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'utilisateur est authentifié
|
||||
Future<bool> hasValidToken() async {
|
||||
final token = await getAccessToken();
|
||||
if (token == null) return false;
|
||||
|
||||
final expirationDate = await getTokenExpirationDate();
|
||||
if (expirationDate == null) return false;
|
||||
|
||||
return DateTime.now().isBefore(expirationDate);
|
||||
}
|
||||
|
||||
/// Efface toutes les données d'authentification
|
||||
Future<void> clearAll() async {
|
||||
_storage.clear();
|
||||
}
|
||||
|
||||
/// Met à jour uniquement les tokens
|
||||
Future<void> updateTokens({
|
||||
required String accessToken,
|
||||
required String refreshToken,
|
||||
required DateTime expiresAt,
|
||||
required DateTime refreshExpiresAt,
|
||||
}) async {
|
||||
_storage[_accessTokenKey] = accessToken;
|
||||
_storage[_refreshTokenKey] = refreshToken;
|
||||
_storage[_expiresAtKey] = expiresAt.toIso8601String();
|
||||
_storage[_refreshExpiresAtKey] = refreshExpiresAt.toIso8601String();
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception personnalisée pour les erreurs de stockage
|
||||
class StorageException implements Exception {
|
||||
final String message;
|
||||
StorageException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'StorageException: $message';
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../models/login_response.dart';
|
||||
import '../models/user_info.dart';
|
||||
|
||||
/// Service de stockage sécurisé des tokens d'authentification
|
||||
@singleton
|
||||
class SecureTokenStorage {
|
||||
static const String _accessTokenKey = 'access_token';
|
||||
static const String _refreshTokenKey = 'refresh_token';
|
||||
static const String _userInfoKey = 'user_info';
|
||||
static const String _expiresAtKey = 'expires_at';
|
||||
static const String _refreshExpiresAtKey = 'refresh_expires_at';
|
||||
static const String _biometricEnabledKey = 'biometric_enabled';
|
||||
|
||||
// Utilise SharedPreferences temporairement pour Android
|
||||
Future<SharedPreferences> get _prefs => SharedPreferences.getInstance();
|
||||
|
||||
/// Sauvegarde les données d'authentification
|
||||
Future<void> saveAuthData(LoginResponse loginResponse) async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
await Future.wait([
|
||||
prefs.setString(_accessTokenKey, loginResponse.accessToken),
|
||||
prefs.setString(_refreshTokenKey, loginResponse.refreshToken),
|
||||
prefs.setString(_userInfoKey, jsonEncode(loginResponse.user.toJson())),
|
||||
prefs.setString(_expiresAtKey, loginResponse.expiresAt.toIso8601String()),
|
||||
prefs.setString(_refreshExpiresAtKey, loginResponse.refreshExpiresAt.toIso8601String()),
|
||||
]);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la sauvegarde des données d\'authentification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère le token d'accès
|
||||
Future<String?> getAccessToken() async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
return prefs.getString(_accessTokenKey);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération du token d\'accès: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère le refresh token
|
||||
Future<String?> getRefreshToken() async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
return prefs.getString(_refreshTokenKey);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération du refresh token: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les informations utilisateur
|
||||
Future<UserInfo?> getUserInfo() async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
final userJson = prefs.getString(_userInfoKey);
|
||||
if (userJson == null) return null;
|
||||
|
||||
final userMap = jsonDecode(userJson) as Map<String, dynamic>;
|
||||
return UserInfo.fromJson(userMap);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération des informations utilisateur: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère la date d'expiration du token d'accès
|
||||
Future<DateTime?> getTokenExpirationDate() async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
final expiresAtString = prefs.getString(_expiresAtKey);
|
||||
if (expiresAtString == null) return null;
|
||||
|
||||
return DateTime.parse(expiresAtString);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération de la date d\'expiration: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère la date d'expiration du refresh token
|
||||
Future<DateTime?> getRefreshTokenExpirationDate() async {
|
||||
try {
|
||||
final expiresAtString = await _storage.read(key: _refreshExpiresAtKey);
|
||||
if (expiresAtString == null) return null;
|
||||
|
||||
return DateTime.parse(expiresAtString);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération de la date d\'expiration du refresh token: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère toutes les données d'authentification
|
||||
Future<LoginResponse?> getAuthData() async {
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
getAccessToken(),
|
||||
getRefreshToken(),
|
||||
getUserInfo(),
|
||||
getTokenExpirationDate(),
|
||||
getRefreshTokenExpirationDate(),
|
||||
]);
|
||||
|
||||
final accessToken = results[0] as String?;
|
||||
final refreshToken = results[1] as String?;
|
||||
final userInfo = results[2] as UserInfo?;
|
||||
final expiresAt = results[3] as DateTime?;
|
||||
final refreshExpiresAt = results[4] as DateTime?;
|
||||
|
||||
if (accessToken == null ||
|
||||
refreshToken == null ||
|
||||
userInfo == null ||
|
||||
expiresAt == null ||
|
||||
refreshExpiresAt == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LoginResponse(
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: expiresAt,
|
||||
refreshExpiresAt: refreshExpiresAt,
|
||||
user: userInfo,
|
||||
);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la récupération des données d\'authentification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Met à jour le token d'accès
|
||||
Future<void> updateAccessToken(String accessToken, DateTime expiresAt) async {
|
||||
try {
|
||||
await Future.wait([
|
||||
_storage.write(key: _accessTokenKey, value: accessToken),
|
||||
_storage.write(key: _expiresAtKey, value: expiresAt.toIso8601String()),
|
||||
]);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la mise à jour du token d\'accès: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si les données d'authentification existent
|
||||
Future<bool> hasAuthData() async {
|
||||
try {
|
||||
final accessToken = await _storage.read(key: _accessTokenKey);
|
||||
final refreshToken = await _storage.read(key: _refreshTokenKey);
|
||||
return accessToken != null && refreshToken != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si les tokens sont expirés
|
||||
Future<bool> areTokensExpired() async {
|
||||
try {
|
||||
final expiresAt = await getTokenExpirationDate();
|
||||
final refreshExpiresAt = await getRefreshTokenExpirationDate();
|
||||
|
||||
if (expiresAt == null || refreshExpiresAt == null) return true;
|
||||
|
||||
final now = DateTime.now();
|
||||
return refreshExpiresAt.isBefore(now);
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si le token d'accès expire bientôt
|
||||
Future<bool> isAccessTokenExpiringSoon({int minutes = 5}) async {
|
||||
try {
|
||||
final expiresAt = await getTokenExpirationDate();
|
||||
if (expiresAt == null) return true;
|
||||
|
||||
final threshold = DateTime.now().add(Duration(minutes: minutes));
|
||||
return expiresAt.isBefore(threshold);
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Efface toutes les données d'authentification
|
||||
Future<void> clearAuthData() async {
|
||||
try {
|
||||
await Future.wait([
|
||||
_storage.delete(key: _accessTokenKey),
|
||||
_storage.delete(key: _refreshTokenKey),
|
||||
_storage.delete(key: _userInfoKey),
|
||||
_storage.delete(key: _expiresAtKey),
|
||||
_storage.delete(key: _refreshExpiresAtKey),
|
||||
]);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de l\'effacement des données d\'authentification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Active/désactive l'authentification biométrique
|
||||
Future<void> setBiometricEnabled(bool enabled) async {
|
||||
try {
|
||||
await _storage.write(key: _biometricEnabledKey, value: enabled.toString());
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la configuration biométrique: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'authentification biométrique est activée
|
||||
Future<bool> isBiometricEnabled() async {
|
||||
try {
|
||||
final enabled = await _storage.read(key: _biometricEnabledKey);
|
||||
return enabled == 'true';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Efface toutes les données stockées
|
||||
Future<void> clearAll() async {
|
||||
try {
|
||||
await _storage.deleteAll();
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de l\'effacement de toutes les données: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si le stockage sécurisé est disponible
|
||||
Future<bool> isAvailable() async {
|
||||
try {
|
||||
await _storage.containsKey(key: 'test');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception liée au stockage
|
||||
class StorageException implements Exception {
|
||||
final String message;
|
||||
|
||||
const StorageException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'StorageException: $message';
|
||||
}
|
||||
Reference in New Issue
Block a user