fix:参考金值,研听,研值工程通用通用工程公共能力,重点对比网络请求、登录认证、状态管理、本地存储、WebView、推送、版本更新、一键登录、主题/UI 等模块,确定后续统一整改方案
This commit is contained in:
1 parent
6997a65c96
commit
d364c6d908
46 files changed
+2060
-395
No files matched your search
@@ -1,9 +1,8 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'preferences_storage.dart';
|
||||
import 'storage_keys.dart';
|
||||
import 'storage_service.dart';
|
||||
|
||||
abstract interface class SecureKeyValueStorage {
|
||||
Future<String?> read(String key);
|
||||
@@ -33,25 +32,33 @@ class SecureIdentityStorage {
|
||||
SecureKeyValueStorage secureStorage = const FlutterSecureKeyValueStorage(),
|
||||
}) : _storage = secureStorage;
|
||||
|
||||
static const _accessTokenKey = 'access_token';
|
||||
static const _legacySecureJwtKey = 'jwt_token';
|
||||
|
||||
final SharedPreferences _preferences;
|
||||
final PreferencesStorage _preferences;
|
||||
final SecureKeyValueStorage _storage;
|
||||
|
||||
Future<String?> restoreAccessToken() async {
|
||||
final current = _normalized(await _storage.read(_accessTokenKey));
|
||||
final legacyPreferences = _normalized(
|
||||
_preferences.getString(storageLegacyAccessToken),
|
||||
);
|
||||
final hasInstallSession = _preferences.getBool(storageAuthSessionActive);
|
||||
|
||||
// A plaintext token proves that this installation predates secure storage
|
||||
// migration. A Keychain token by itself does not: iOS may retain it after
|
||||
// uninstall while SharedPreferences (including this marker) is reset.
|
||||
if (!hasInstallSession && legacyPreferences == null) {
|
||||
await _deleteAllAuthCredentials();
|
||||
await _preferences.remove(storageAuthSessionActive);
|
||||
return null;
|
||||
}
|
||||
|
||||
final current = _normalized(await _storage.read(secureAccessToken));
|
||||
final legacySecure = current == null
|
||||
? _normalized(await _storage.read(_legacySecureJwtKey))
|
||||
? _normalized(await _storage.read(secureLegacyJwtToken))
|
||||
: null;
|
||||
final token = current ?? legacyPreferences ?? legacySecure;
|
||||
if (current == null && token != null) {
|
||||
await _storage.write(_accessTokenKey, token);
|
||||
await _storage.write(secureAccessToken, token);
|
||||
}
|
||||
await _storage.delete(_legacySecureJwtKey);
|
||||
await _storage.delete(secureLegacyJwtToken);
|
||||
await _preferences.remove(storageLegacyAccessToken);
|
||||
if (token == null) {
|
||||
await _preferences.remove(storageAuthSessionActive);
|
||||
@@ -62,10 +69,12 @@ class SecureIdentityStorage {
|
||||
}
|
||||
|
||||
Future<String?> readJwt() async {
|
||||
final token = _normalized(await _storage.read(_accessTokenKey));
|
||||
if (token == null || _preferences.containsKey(storageLegacyAccessToken)) {
|
||||
if (!_preferences.getBool(storageAuthSessionActive) ||
|
||||
_preferences.containsKey(storageLegacyAccessToken)) {
|
||||
return restoreAccessToken();
|
||||
}
|
||||
final token = _normalized(await _storage.read(secureAccessToken));
|
||||
if (token == null) await _preferences.remove(storageAuthSessionActive);
|
||||
return token;
|
||||
}
|
||||
|
||||
@@ -74,22 +83,100 @@ class SecureIdentityStorage {
|
||||
if (normalized == null) {
|
||||
throw ArgumentError.value(token, 'token', 'must not be empty');
|
||||
}
|
||||
await _storage.write(_accessTokenKey, normalized);
|
||||
await _storage.delete(_legacySecureJwtKey);
|
||||
await _storage.write(secureAccessToken, normalized);
|
||||
await _storage.delete(secureLegacyJwtToken);
|
||||
await _preferences.remove(storageLegacyAccessToken);
|
||||
await _preferences.setBool(storageAuthSessionActive, true);
|
||||
}
|
||||
|
||||
Future<void> clearJwt() async {
|
||||
await _storage.delete(_accessTokenKey);
|
||||
await _storage.delete(_legacySecureJwtKey);
|
||||
await _deleteAllAuthCredentials();
|
||||
await _preferences.remove(storageLegacyAccessToken);
|
||||
await _preferences.remove(storageAuthSessionActive);
|
||||
}
|
||||
|
||||
Future<void> clearSessionCredentials() async {
|
||||
await clearJwt();
|
||||
await _storage.delete(secureMallCredential);
|
||||
await _storage.delete(secureRefreshToken);
|
||||
}
|
||||
|
||||
Future<void> clearAccountCredentials() async {
|
||||
await clearSessionCredentials();
|
||||
await _storage.delete(secureBiometricCredential);
|
||||
}
|
||||
|
||||
Future<String?> readDeviceId() => _storage.read(secureDeviceId);
|
||||
|
||||
Future<void> saveDeviceId(String deviceId) =>
|
||||
_storage.write(secureDeviceId, deviceId);
|
||||
|
||||
Future<void> _deleteAllAuthCredentials() async {
|
||||
await _storage.delete(secureAccessToken);
|
||||
await _storage.delete(secureLegacyJwtToken);
|
||||
}
|
||||
}
|
||||
|
||||
final secureIdentityStorageProvider = Provider<SecureIdentityStorage>(
|
||||
(ref) => SecureIdentityStorage(StorageService.to.preferences),
|
||||
(ref) => SecureIdentityStorage(ref.watch(preferencesStorageProvider)),
|
||||
);
|
||||
|
||||
/// Process-memory session snapshot. Interceptors read this synchronously;
|
||||
/// secure storage is touched only when a session starts, restores, or ends.
|
||||
class AuthSessionStorage {
|
||||
AuthSessionStorage(this._identityStorage);
|
||||
|
||||
final SecureIdentityStorage _identityStorage;
|
||||
String? _accessToken;
|
||||
int _generation = 0;
|
||||
Future<String?>? _restoreFuture;
|
||||
Future<void> _operationTail = Future<void>.value();
|
||||
|
||||
String? get accessToken => _accessToken;
|
||||
int get generation => _generation;
|
||||
|
||||
Future<String?> restore() => _restoreFuture ??= _serialize(_restore);
|
||||
|
||||
Future<String?> _restore() async {
|
||||
_accessToken = await _identityStorage.restoreAccessToken();
|
||||
_generation++;
|
||||
return _accessToken;
|
||||
}
|
||||
|
||||
Future<void> save(String token) async {
|
||||
await _serialize(() async {
|
||||
await _identityStorage.saveJwt(token);
|
||||
_accessToken = token.trim();
|
||||
_generation++;
|
||||
_restoreFuture = Future<String?>.value(_accessToken);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> clear({bool accountDeleted = false}) async {
|
||||
await _serialize(() async {
|
||||
if (accountDeleted) {
|
||||
await _identityStorage.clearAccountCredentials();
|
||||
} else {
|
||||
await _identityStorage.clearSessionCredentials();
|
||||
}
|
||||
_accessToken = null;
|
||||
_generation++;
|
||||
_restoreFuture = Future<String?>.value();
|
||||
});
|
||||
}
|
||||
|
||||
Future<T> _serialize<T>(Future<T> Function() operation) {
|
||||
final result = _operationTail.then((_) => operation());
|
||||
_operationTail = result.then<void>(
|
||||
(_) {},
|
||||
onError: (Object _, StackTrace _) {},
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
final authSessionStorageProvider = Provider<AuthSessionStorage>(
|
||||
(ref) => AuthSessionStorage(ref.watch(secureIdentityStorageProvider)),
|
||||
);
|
||||
|
||||
String? _normalized(String? value) {
|
||||
|
||||
Reference in new issue
Block a user