99 lines
3.3 KiB
Dart
99 lines
3.3 KiB
Dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import 'storage_keys.dart';
|
|
import 'storage_service.dart';
|
|
|
|
abstract interface class SecureKeyValueStorage {
|
|
Future<String?> read(String key);
|
|
Future<void> write(String key, String value);
|
|
Future<void> delete(String key);
|
|
}
|
|
|
|
class FlutterSecureKeyValueStorage implements SecureKeyValueStorage {
|
|
const FlutterSecureKeyValueStorage();
|
|
|
|
static const _storage = FlutterSecureStorage();
|
|
|
|
@override
|
|
Future<String?> read(String key) => _storage.read(key: key);
|
|
@override
|
|
Future<void> write(String key, String value) =>
|
|
_storage.write(key: key, value: value);
|
|
@override
|
|
Future<void> delete(String key) => _storage.delete(key: key);
|
|
}
|
|
|
|
/// Sensitive identity values stay behind one injectable storage boundary.
|
|
/// Legacy plaintext preferences are migrated only after a secure write works.
|
|
class SecureIdentityStorage {
|
|
SecureIdentityStorage(
|
|
this._preferences, {
|
|
SecureKeyValueStorage secureStorage = const FlutterSecureKeyValueStorage(),
|
|
}) : _storage = secureStorage;
|
|
|
|
static const _accessTokenKey = 'access_token';
|
|
static const _legacySecureJwtKey = 'jwt_token';
|
|
|
|
final SharedPreferences _preferences;
|
|
final SecureKeyValueStorage _storage;
|
|
|
|
Future<String?> restoreAccessToken() async {
|
|
final current = _normalized(await _storage.read(_accessTokenKey));
|
|
final legacyPreferences = _normalized(
|
|
_preferences.getString(storageLegacyAccessToken),
|
|
);
|
|
final legacySecure = current == null
|
|
? _normalized(await _storage.read(_legacySecureJwtKey))
|
|
: null;
|
|
final token = current ?? legacyPreferences ?? legacySecure;
|
|
if (current == null && token != null) {
|
|
await _storage.write(_accessTokenKey, token);
|
|
}
|
|
await _storage.delete(_legacySecureJwtKey);
|
|
await _preferences.remove(storageLegacyAccessToken);
|
|
if (token == null) {
|
|
await _preferences.remove(storageAuthSessionActive);
|
|
} else {
|
|
await _preferences.setBool(storageAuthSessionActive, true);
|
|
}
|
|
return token;
|
|
}
|
|
|
|
Future<String?> readJwt() async {
|
|
final token = _normalized(await _storage.read(_accessTokenKey));
|
|
if (token == null || _preferences.containsKey(storageLegacyAccessToken)) {
|
|
return restoreAccessToken();
|
|
}
|
|
return token;
|
|
}
|
|
|
|
Future<void> saveJwt(String token) async {
|
|
final normalized = _normalized(token);
|
|
if (normalized == null) {
|
|
throw ArgumentError.value(token, 'token', 'must not be empty');
|
|
}
|
|
await _storage.write(_accessTokenKey, normalized);
|
|
await _storage.delete(_legacySecureJwtKey);
|
|
await _preferences.remove(storageLegacyAccessToken);
|
|
await _preferences.setBool(storageAuthSessionActive, true);
|
|
}
|
|
|
|
Future<void> clearJwt() async {
|
|
await _storage.delete(_accessTokenKey);
|
|
await _storage.delete(_legacySecureJwtKey);
|
|
await _preferences.remove(storageLegacyAccessToken);
|
|
await _preferences.remove(storageAuthSessionActive);
|
|
}
|
|
}
|
|
|
|
final secureIdentityStorageProvider = Provider<SecureIdentityStorage>(
|
|
(ref) => SecureIdentityStorage(StorageService.to.preferences),
|
|
);
|
|
|
|
String? _normalized(String? value) {
|
|
final normalized = value?.trim() ?? '';
|
|
return normalized.isEmpty ? null : normalized;
|
|
}
|