diff --git a/packages/komodo_defi_framework/app_build/build_config.json b/packages/komodo_defi_framework/app_build/build_config.json index 9ce61923..d8184253 100644 --- a/packages/komodo_defi_framework/app_build/build_config.json +++ b/packages/komodo_defi_framework/app_build/build_config.json @@ -65,10 +65,10 @@ "coins": { "fetch_at_build_enabled": true, "update_commit_on_build": true, - "bundled_coins_repo_commit": "8fab732731df59a2c1ccc694d565e8535cd51c93", + "bundled_coins_repo_commit": "aceacfb4996a9bda784b0dfda588e24bba3fa9f8", "coins_repo_api_url": "https://api.github.com/repos/GLEECBTC/coins", "coins_repo_content_url": "https://raw.githubusercontent.com/GLEECBTC/coins", - "coins_repo_branch": "feat/add-tron-coins", + "coins_repo_branch": "master", "runtime_updates_enabled": true, "mapped_files": { "assets/config/coins_config.json": "utils/coins_config_unfiltered.json", diff --git a/packages/komodo_defi_local_auth/lib/src/auth/auth_service.dart b/packages/komodo_defi_local_auth/lib/src/auth/auth_service.dart index 07b64bb1..bef49991 100644 --- a/packages/komodo_defi_local_auth/lib/src/auth/auth_service.dart +++ b/packages/komodo_defi_local_auth/lib/src/auth/auth_service.dart @@ -94,6 +94,16 @@ abstract interface class IAuthService { /// is fully integrated with KW. This may be deprecated in the future. Future setActiveUserMetadata(JsonMap metadata); + /// Atomically reads the current value of [key] from the active user's + /// metadata, applies [transform] to it, and writes the result back. + /// + /// This is safe to call concurrently — a dedicated metadata mutex + /// serialises all read-modify-write cycles. + Future updateActiveUserMetadataKey( + String key, + dynamic Function(dynamic currentValue) transform, + ); + /// Attempts to restore a user session without requiring password authentication /// Only works if the KDF API is running and the wallet exists Future restoreSession(KdfUser user); @@ -123,6 +133,7 @@ class KdfAuthService implements IAuthService { StreamController.broadcast(); final SecureLocalStorage _secureStorage = SecureLocalStorage(); final ReadWriteMutex _authMutex = ReadWriteMutex(); + final Mutex _metadataMutex = Mutex(); final Logger _logger = Logger('KdfAuthService'); final String _sessionId; @@ -697,21 +708,45 @@ class KdfAuthService implements IAuthService { @override Future setActiveUserMetadata(Map metadata) async { - final activeUser = await _activeUserOrThrow(); - // TODO: Implement locks for this to avoid this method interfering with - // more sensitive operations. - final user = await _secureStorage.getUser(activeUser.walletId.name); - if (user == null) throw AuthException.notFound(); + await _metadataMutex.protect(() async { + final activeUser = await _activeUserOrThrow(); + final user = await _secureStorage.getUser(activeUser.walletId.name); + if (user == null) throw AuthException.notFound(); + + final updatedUser = user.copyWith(metadata: metadata); + await _secureStorage.saveUser(updatedUser); + + // Update cache silently without triggering auth state change. Updating + // the storage and cache at the same time emulates the same behaviour as + // before. Update user metadata for any subsequent access without emitting + // auth state changes, as the metadata field is currently used for events + // like coin activation, wallet type (derivation), and seed backup status + _lastEmittedUser = updatedUser; + }); + } - final updatedUser = user.copyWith(metadata: metadata); - await _secureStorage.saveUser(updatedUser); + @override + Future updateActiveUserMetadataKey( + String key, + dynamic Function(dynamic currentValue) transform, + ) async { + await _metadataMutex.protect(() async { + final activeUser = await _activeUserOrThrow(); + final user = await _secureStorage.getUser(activeUser.walletId.name); + if (user == null) throw AuthException.notFound(); + + final metadata = JsonMap.from(user.metadata); + final transformed = transform(metadata[key]); + if (transformed == null) { + metadata.remove(key); + } else { + metadata[key] = transformed; + } - // Update cache silently without triggering auth state change. Updating the - // storage and cache at the same time emulates the same behaviour as before. - // Update user metadata for any subsequent access without emitting auth - // state changes, as the metadata field is currently used for events like - // coin activation, wallet type (derivation), and seed backup status - _lastEmittedUser = updatedUser; + final updatedUser = user.copyWith(metadata: metadata); + await _secureStorage.saveUser(updatedUser); + _lastEmittedUser = updatedUser; + }); } @override diff --git a/packages/komodo_defi_local_auth/lib/src/komodo_defi_local_auth.dart b/packages/komodo_defi_local_auth/lib/src/komodo_defi_local_auth.dart index a637be0a..df9392c9 100644 --- a/packages/komodo_defi_local_auth/lib/src/komodo_defi_local_auth.dart +++ b/packages/komodo_defi_local_auth/lib/src/komodo_defi_local_auth.dart @@ -6,7 +6,6 @@ import 'package:komodo_defi_local_auth/src/auth/auth_state.dart'; import 'package:komodo_defi_local_auth/src/auth/storage/secure_storage.dart'; import 'package:komodo_defi_local_auth/src/trezor/_trezor_index.dart'; import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; -import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; /// The [KomodoDefiAuth] class provides a simplified local authentication @@ -193,6 +192,15 @@ abstract interface class KomodoDefiAuth { Future setOrRemoveActiveUserKeyValue(String key, dynamic value); + /// Atomically reads the current value of [key] from the active user's + /// metadata, applies [transform], and writes the result back. + /// + /// Safe to call concurrently — uses a dedicated metadata mutex internally. + Future updateActiveUserKeyValue( + String key, + dynamic Function(dynamic currentValue) transform, + ); + /// Provides PIN to a Trezor hardware device during authentication. /// /// The [taskId] should be obtained from the authentication state when the @@ -611,15 +619,15 @@ class KomodoDefiLocalAuth implements KomodoDefiAuth { @override Future setOrRemoveActiveUserKeyValue(String key, dynamic value) async { - final activeUser = await _authService.getActiveUser(); - - if (activeUser == null) throw AuthException.notFound(); - - final updatedMetadata = JsonMap.from(activeUser.metadata)..[key] = value; - - if (value == null) updatedMetadata.remove(key); + await _authService.updateActiveUserMetadataKey(key, (_) => value); + } - await _authService.setActiveUserMetadata(updatedMetadata); + @override + Future updateActiveUserKeyValue( + String key, + dynamic Function(dynamic currentValue) transform, + ) async { + await _authService.updateActiveUserMetadataKey(key, transform); } @override diff --git a/packages/komodo_defi_local_auth/lib/src/trezor/trezor_auth_service.dart b/packages/komodo_defi_local_auth/lib/src/trezor/trezor_auth_service.dart index 2fef1f74..eeca492e 100644 --- a/packages/komodo_defi_local_auth/lib/src/trezor/trezor_auth_service.dart +++ b/packages/komodo_defi_local_auth/lib/src/trezor/trezor_auth_service.dart @@ -104,6 +104,13 @@ class TrezorAuthService implements IAuthService { Future setActiveUserMetadata(JsonMap metadata) => _authService.setActiveUserMetadata(metadata); + @override + Future updateActiveUserMetadataKey( + String key, + dynamic Function(dynamic currentValue) transform, + ) => + _authService.updateActiveUserMetadataKey(key, transform); + @override Future restoreSession(KdfUser user) => _authService.restoreSession(user);