import 'dart:collection'; import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:result_monad/result_monad.dart'; import '../globals.dart'; import '../models/auth/basic_credentials.dart'; import '../models/auth/credentials_intf.dart'; import '../models/auth/oauth_credentials.dart'; import '../models/auth/profile.dart'; import '../models/exec_error.dart'; const _storageAccountName = 'social.myportal.relatica.secure_storage'; class SecretsService { static const _basicProfilesKey = 'basic_profiles'; static const _oauthProfilesKey = 'oauth_profiles'; final _cachedProfiles = {}; List get profiles => UnmodifiableListView(List.from(_cachedProfiles)); final _secureStorage = const FlutterSecureStorage( iOptions: IOSOptions( accountName: _storageAccountName, accessibility: KeychainAccessibility.first_unlock, ), mOptions: MacOsOptions( accountName: _storageAccountName, groupId: macOsGroupId, ), ); FutureResult, ExecError> initialize() async { return await loadProfiles(); } FutureResult, ExecError> clearCredentials() async { try { await _secureStorage.delete(key: _basicProfilesKey); await _secureStorage.delete(key: _oauthProfilesKey); return Result.ok(profiles); } catch (e) { return Result.error(ExecError( type: ErrorType.localError, message: e.toString(), )); } } FutureResult, ExecError> addOrUpdateProfile( Profile profile) async { try { _cachedProfiles.remove(profile); _cachedProfiles.add(profile); return await saveCredentials(); } catch (e) { return Result.error(ExecError( type: ErrorType.localError, message: e.toString(), )); } } FutureResult, ExecError> removeProfile(Profile profile) async { try { _cachedProfiles.remove(profile); return await saveCredentials(); } catch (e) { return Result.error(ExecError( type: ErrorType.localError, message: e.toString(), )); } } FutureResult, ExecError> loadProfiles() async { try { await _loadJson(_basicProfilesKey, BasicCredentials.fromJson); await _loadJson(_oauthProfilesKey, OAuthCredentials.fromJson); return Result.ok(profiles); } catch (e) { return Result.error(ExecError( type: ErrorType.localError, message: e.toString(), )); } } FutureResult, ExecError> saveCredentials() async { try { await _saveJson(_basicProfilesKey); await _saveJson(_oauthProfilesKey); return Result.ok(profiles); } catch (e) { return Result.error(ExecError( type: ErrorType.localError, message: e.toString(), )); } } Future _loadJson( String key, ICredentials Function(Map) fromJson, ) async { final jsonString = await _secureStorage.read(key: key); if (jsonString == null || jsonString.isEmpty) { return; } final profiles = (jsonDecode(jsonString) as List) .map((json) => Profile.fromJson(json, fromJson)) .toList(); _cachedProfiles.addAll(profiles); } FutureResult _saveJson( String key, ) async { final json = _cachedProfiles .where((p) => p.credentials is T) .map((p) => p.toJson()) .toList(); final jsonString = jsonEncode(json); await _secureStorage.write(key: key, value: jsonString); final pulledResult = await _secureStorage.read(key: key); if (pulledResult == jsonString) { return Result.ok(true); } return buildErrorResult( type: ErrorType.localError, message: 'For key $key value read from secure storage did not match value to secure storage', ); } }