relatica/lib/services/secrets_service.dart

140 wiersze
3.9 KiB
Dart

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 = <Profile>{};
List<Profile> 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<List<Profile>, ExecError> initialize() async {
return await loadProfiles();
}
FutureResult<List<Profile>, 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<List<Profile>, 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<List<Profile>, 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<List<Profile>, 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<List<Profile>, ExecError> saveCredentials() async {
try {
await _saveJson<BasicCredentials>(_basicProfilesKey);
await _saveJson<OAuthCredentials>(_oauthProfilesKey);
return Result.ok(profiles);
} catch (e) {
return Result.error(ExecError(
type: ErrorType.localError,
message: e.toString(),
));
}
}
Future<void> _loadJson(
String key,
ICredentials Function(Map<String, dynamic>) fromJson,
) async {
final jsonString = await _secureStorage.read(key: key);
if (jsonString == null || jsonString.isEmpty) {
return;
}
final profiles = (jsonDecode(jsonString) as List<dynamic>)
.map((json) => Profile.fromJson(json, fromJson))
.toList();
_cachedProfiles.addAll(profiles);
}
FutureResult<bool, ExecError> _saveJson<T>(
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',
);
}
}