Merge pull request 'refactor: Introduce a new storage for tokens' (#505) from inex/april-refactor into master

Reviewed-on: https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/pulls/505
This commit is contained in:
Inex Code 2024-07-09 20:06:18 +03:00
commit 59cf273fa9
44 changed files with 1400 additions and 483 deletions

4
.vscode/launch.json vendored
View file

@ -31,7 +31,7 @@
] ]
}, },
{ {
"name": "debug (production)", "name": "debug (production flavor)",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"args": [ "args": [
@ -41,7 +41,7 @@
}, },
{ {
"name": "debug (nightly)", "name": "debug (nightly flavor)",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"args": [ "args": [

View file

@ -46,12 +46,10 @@
}, },
"console_page": { "console_page": {
"title": "Console", "title": "Console",
"waiting": "Waiting for initialization…",
"copy": "Copy", "copy": "Copy",
"copy_raw": "Raw response", "copy_raw": "Raw response",
"history_empty": "No data yet", "history_empty": "No data yet",
"error": "Error", "error": "Error",
"log": "Log",
"rest_api_request": "Rest API Request", "rest_api_request": "Rest API Request",
"rest_api_response": "Rest API Response", "rest_api_response": "Rest API Response",
"graphql_request": "GraphQL Request", "graphql_request": "GraphQL Request",

18
lib/config/config.dart Normal file
View file

@ -0,0 +1,18 @@
import 'package:flutter/foundation.dart';
/// internal app configuration
const config = InternalConfig(
shouldDebugPrint: kDebugMode,
);
class InternalConfig {
const InternalConfig({
required this.shouldDebugPrint,
});
final bool shouldDebugPrint;
// example of other possible fields
// final String appVersion;
//
}

View file

@ -3,6 +3,7 @@ import 'package:selfprivacy/logic/get_it/api_config.dart';
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
import 'package:selfprivacy/logic/get_it/console_model.dart'; import 'package:selfprivacy/logic/get_it/console_model.dart';
import 'package:selfprivacy/logic/get_it/navigation.dart'; import 'package:selfprivacy/logic/get_it/navigation.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
export 'package:selfprivacy/logic/get_it/api_config.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart';
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
@ -14,9 +15,10 @@ final GetIt getIt = GetIt.instance;
Future<void> getItSetup() async { Future<void> getItSetup() async {
getIt.registerSingleton<NavigationService>(NavigationService()); getIt.registerSingleton<NavigationService>(NavigationService());
getIt.registerSingleton<ConsoleModel>(ConsoleModel()); getIt.registerSingleton<ConsoleModel>(ConsoleModel());
getIt.registerSingleton<ResourcesModel>(ResourcesModel()..init());
getIt.registerSingleton<WizardDataModel>(WizardDataModel()..init());
final apiConfigModel = ApiConfigModel(); final apiConfigModel = ApiConfigModel();
await apiConfigModel.init();
getIt.registerSingleton<ApiConfigModel>(apiConfigModel); getIt.registerSingleton<ApiConfigModel>(apiConfigModel);
getIt.registerSingleton<ApiConnectionRepository>( getIt.registerSingleton<ApiConnectionRepository>(

View file

@ -1,75 +1,227 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/dns_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
import 'package:selfprivacy/utils/app_logger.dart';
import 'package:selfprivacy/utils/platform_adapter.dart'; import 'package:selfprivacy/utils/platform_adapter.dart';
import 'package:selfprivacy/utils/secure_storage.dart';
class HiveConfig { class HiveConfig {
static final log = const AppLogger(name: 'hive_config').log;
/// bump on schema changes
static const version = 2;
static Future<void> init() async { static Future<void> init() async {
final String? storagePath = PlatformAdapter.storagePath; final String? storagePath = PlatformAdapter.storagePath;
print('HiveConfig: Custom storage path: $storagePath'); log('set custom storage path to: "$storagePath"');
await Hive.initFlutter(storagePath); await Hive.initFlutter(storagePath);
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ServerHostingDetailsAdapter());
Hive.registerAdapter(ServerDomainAdapter());
Hive.registerAdapter(BackupsCredentialAdapter());
Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerProviderVolumeAdapter());
Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
await Hive.openBox(BNames.appSettingsBox); registerAdapters();
await decryptBoxes();
await performMigrations();
}
static void registerAdapters() {
try { try {
final HiveAesCipher cipher = HiveAesCipher( Hive.registerAdapter(UserAdapter());
await getEncryptedKey(BNames.serverInstallationEncryptionKey), Hive.registerAdapter(ServerHostingDetailsAdapter());
Hive.registerAdapter(ServerDomainAdapter());
Hive.registerAdapter(BackupsCredentialAdapter());
Hive.registerAdapter(ServerProviderVolumeAdapter());
Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerProviderCredentialAdapter());
Hive.registerAdapter(DnsProviderCredentialAdapter());
Hive.registerAdapter(ServerAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
Hive.registerAdapter(ServerInstallationWizardDataAdapter());
log('successfully registered every adapter');
} catch (error, stackTrace) {
log(
'error registering adapters',
error: error,
stackTrace: stackTrace,
); );
rethrow;
}
}
await Hive.openBox<User>(BNames.usersDeprecated); static Future<HiveAesCipher> getCipher() async {
await Hive.openBox<User>(BNames.usersBox, encryptionCipher: cipher); List<int>? key = await SecureStorage.getKey();
if (key == null) {
key = Hive.generateSecureKey();
await SecureStorage.setKey(key);
}
return HiveAesCipher(key);
}
final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated); static Future<void> decryptBoxes() async {
if (deprecatedUsers.isNotEmpty) { try {
final Box<User> users = Hive.box<User>(BNames.usersBox); // load encrypted boxes into memory
await users.addAll(deprecatedUsers.values.toList()); final HiveAesCipher cipher = await getCipher();
await deprecatedUsers.clear();
}
await Hive.openBox( await Hive.openBox(
BNames.serverInstallationBox, BNames.serverInstallationBox,
encryptionCipher: cipher, encryptionCipher: cipher,
); );
} on PlatformException catch (e) { await Hive.openBox(
print('HiveConfig: Error while opening boxes: $e'); BNames.resourcesBox,
encryptionCipher: cipher,
);
await Hive.openBox(
BNames.wizardDataBox,
encryptionCipher: cipher,
);
log('successfully decrypted boxes');
} catch (error, stackTrace) {
log(
'error initializing encrypted boxes',
error: error,
stackTrace: stackTrace,
);
rethrow; rethrow;
} }
} }
static Future<Uint8List> getEncryptedKey(final String encKey) async { // migrations
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
static Future<void> performMigrations() async {
try { try {
final bool hasEncryptionKey = // perform migration check
await secureStorage.containsKey(key: encKey); final localSettingsBox = await Hive.openBox(BNames.appSettingsBox);
if (!hasEncryptionKey) {
final List<int> key = Hive.generateSecureKey(); // if it is an initial app launch, we do not need to perform any migrations
await secureStorage.write(key: encKey, value: base64UrlEncode(key)); final savedVersion = localSettingsBox.isEmpty
? version
// if box was initialized, but database version was not introduced in
// it yet, it means that we have initial value
: await localSettingsBox.get(BNames.databaseVersion, defaultValue: 1);
/// launch migrations based on version
if (savedVersion < version) {
if (savedVersion < 2) {
await migrateFrom1To2();
}
/// add new migrations here, like:
/// if (version < 3) {...}, etc.
} }
final String? string = await secureStorage.read(key: encKey); /// update saved version after successfull migrations
return base64Url.decode(string!); await localSettingsBox.put(BNames.databaseVersion, version);
} on PlatformException catch (e) { } catch (error, stackTrace) {
print('HiveConfig: Error while getting encryption key: $e'); log(
'error running db migrations',
error: error,
stackTrace: stackTrace,
);
rethrow; rethrow;
} }
} }
/// introduce and populate resourcesBox
static Future<void> migrateFrom1To2() async {
final Box resourcesBox = Hive.box(BNames.resourcesBox);
if (resourcesBox.isEmpty) {
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
final ServerHostingDetails? serverDetails =
serverInstallationBox.get(BNames.serverDetails);
// move server provider config
final ServerProviderType? serverProvider =
serverInstallationBox.get(BNames.serverProvider) ??
serverDetails?.provider;
final String? serverProviderKey =
serverInstallationBox.get(BNames.hetznerKey);
if (serverProviderKey != null && serverProvider.isSpecified) {
final ServerProviderCredential serverProviderCredential =
ServerProviderCredential(
tokenId: null,
token: serverProviderKey,
provider: serverProvider!,
associatedServerIds: [if (serverDetails != null) serverDetails.id],
);
await resourcesBox.put(
BNames.serverProviderTokens,
[serverProviderCredential],
);
}
final String? serverLocation =
serverInstallationBox.get(BNames.serverLocation);
final String? serverType =
serverInstallationBox.get(BNames.serverTypeIdentifier);
final ServerDomain? serverDomain =
serverInstallationBox.get(BNames.serverDomain);
if (serverDetails != null && serverDomain != null) {
await resourcesBox.put(
BNames.servers,
[
Server(
domain: serverDomain,
hostingDetails: serverDetails.copyWith(
serverLocation: serverLocation,
serverType: serverType,
),
),
],
);
}
// move dns config
final String? dnsProviderKey =
serverInstallationBox.get(BNames.cloudFlareKey);
final DnsProviderType? dnsProvider =
serverInstallationBox.get(BNames.dnsProvider) ??
serverDomain?.provider;
if (dnsProviderKey != null && dnsProvider.isSpecified) {
final DnsProviderCredential dnsProviderCredential =
DnsProviderCredential(
tokenId: null,
token: dnsProviderKey,
provider: dnsProvider!,
associatedDomainNames: [
if (serverDomain != null) serverDomain.domainName,
],
);
await resourcesBox
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
}
// move backblaze (backups) config
final BackupsCredential? backblazeCredential =
serverInstallationBox.get(BNames.backblazeCredential);
final BackblazeBucket? backblazeBucket =
serverInstallationBox.get(BNames.backblazeBucket);
if (backblazeCredential != null) {
await resourcesBox
.put(BNames.backupsProviderTokens, [backblazeCredential]);
}
if (backblazeBucket != null) {
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
}
}
log('successfully migrated db from 1 to 2 version');
}
} }
/// Mappings for the different boxes and their keys /// Mappings for the different boxes and their keys
@ -77,6 +229,9 @@ class BNames {
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding] /// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
static String appSettingsBox = 'appSettings'; static String appSettingsBox = 'appSettings';
/// An integer with last saved version of the database
static String databaseVersion = 'databaseVersion';
/// A boolean field of [appSettingsBox] box. /// A boolean field of [appSettingsBox] box.
static String darkThemeModeOn = 'isDarkModeOn'; static String darkThemeModeOn = 'isDarkModeOn';
@ -89,9 +244,6 @@ class BNames {
/// A string field /// A string field
static String appLocale = 'appLocale'; static String appLocale = 'appLocale';
/// Encryption key to decrypt [serverInstallationBox] and [usersBox] box.
static String serverInstallationEncryptionKey = 'key';
/// Server installation box. Contains server details and provider tokens. /// Server installation box. Contains server details and provider tokens.
static String serverInstallationBox = 'appConfig'; static String serverInstallationBox = 'appConfig';
@ -122,7 +274,7 @@ class BNames {
/// A String field of [serverInstallationBox] box. /// A String field of [serverInstallationBox] box.
static String cloudFlareKey = 'cloudFlareKey'; static String cloudFlareKey = 'cloudFlareKey';
/// A String field of [serverTypeIdentifier] box. /// A String field of [serverInstallationBox] box.
static String serverTypeIdentifier = 'serverTypeIdentifier'; static String serverTypeIdentifier = 'serverTypeIdentifier';
/// A [User] field of [serverInstallationBox] box. /// A [User] field of [serverInstallationBox] box.
@ -149,9 +301,24 @@ class BNames {
/// A boolean field of [serverInstallationBox] box. /// A boolean field of [serverInstallationBox] box.
static String isRecoveringServer = 'isRecoveringServer'; static String isRecoveringServer = 'isRecoveringServer';
/// Deprecated users box as it is unencrypted /// Resources and provider tokens box,
static String usersDeprecated = 'users'; static String resourcesBox = 'resourcesBox';
/// Box with users /// Server Provider Tokens of [resourcesBox] box.
static String usersBox = 'usersEncrypted'; static String serverProviderTokens = 'serverProviderTokens';
/// DNS Provider Tokens of [resourcesBox] box.
static String dnsProviderTokens = 'dnsProviderTokens';
/// Backups Provider Tokens of [resourcesBox] box.
static String backupsProviderTokens = 'backupsProviderTokens';
/// Servers of [resourcesBox] box.
static String servers = 'servers';
/// Wizard data box
static String wizardDataBox = 'wizardDataBox';
/// Server installation wizard data of [wizardDataBox] box.
static String serverInstallationWizardData = 'serverInstallationWizardData';
} }

View file

@ -5,6 +5,7 @@ import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:http/io_client.dart'; import 'package:http/io_client.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/console_log.dart'; import 'package:selfprivacy/logic/models/console_log.dart';
void _addConsoleLog(final ConsoleLog message) => void _addConsoleLog(final ConsoleLog message) =>
@ -120,7 +121,7 @@ abstract class GraphQLApiMap {
String get _token { String get _token {
String token = ''; String token = '';
final serverDetails = getIt<ApiConfigModel>().serverDetails; final serverDetails = getIt<ResourcesModel>().serverDetails;
if (serverDetails != null) { if (serverDetails != null) {
token = serverDetails.apiToken; token = serverDetails.apiToken;
} }

View file

@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphq
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -57,7 +58,7 @@ class ServerApi extends GraphQLApiMap
String customToken; String customToken;
@override @override
String? get rootAddress => String? get rootAddress =>
overrideDomain ?? getIt<ApiConfigModel>().serverDomain?.domainName; overrideDomain ?? getIt<ResourcesModel>().serverDomain?.domainName;
String? overrideDomain; String? overrideDomain;
Future<String?> getApiVersion() async { Future<String?> getApiVersion() async {

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
@ -39,7 +40,7 @@ class BackblazeApi extends RestApiMap {
); );
if (isWithToken) { if (isWithToken) {
final BackupsCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ResourcesModel>().backblazeCredential;
final String token = backblazeCredential!.applicationKey; final String token = backblazeCredential!.applicationKey;
options.headers = {'Authorization': 'Basic $token'}; options.headers = {'Authorization': 'Basic $token'};
} }
@ -59,7 +60,7 @@ class BackblazeApi extends RestApiMap {
Future<BackblazeApiAuth> getAuthorizationToken() async { Future<BackblazeApiAuth> getAuthorizationToken() async {
final Dio client = await getClient(); final Dio client = await getClient();
final BackupsCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ResourcesModel>().backblazeCredential;
if (backblazeCredential == null) { if (backblazeCredential == null) {
throw Exception('Backblaze credential is null'); throw Exception('Backblaze credential is null');
} }
@ -124,7 +125,7 @@ class BackblazeApi extends RestApiMap {
Future<String> createBucket(final String bucketName) async { Future<String> createBucket(final String bucketName) async {
final BackblazeApiAuth auth = await getAuthorizationToken(); final BackblazeApiAuth auth = await getAuthorizationToken();
final BackupsCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ResourcesModel>().backblazeCredential;
final Dio client = await getClient(); final Dio client = await getClient();
client.options.baseUrl = auth.apiUrl; client.options.baseUrl = auth.apiUrl;
final Response response = await client.post( final Response response = await client.post(
@ -161,7 +162,7 @@ class BackblazeApi extends RestApiMap {
final Response response = await client.post( final Response response = await client.post(
'$apiPrefix/b2_create_key', '$apiPrefix/b2_create_key',
data: { data: {
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId, 'accountId': getIt<ResourcesModel>().backblazeCredential!.keyId,
'bucketId': bucketId, 'bucketId': bucketId,
'capabilities': ['listBuckets', 'listFiles', 'readFiles', 'writeFiles'], 'capabilities': ['listBuckets', 'listFiles', 'readFiles', 'writeFiles'],
'keyName': 'selfprivacy-restricted-server-key', 'keyName': 'selfprivacy-restricted-server-key',
@ -192,7 +193,7 @@ class BackblazeApi extends RestApiMap {
final Response response = await client.get( final Response response = await client.get(
'$apiPrefix/b2_list_buckets', '$apiPrefix/b2_list_buckets',
queryParameters: { queryParameters: {
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId, 'accountId': getIt<ResourcesModel>().backblazeCredential!.keyId,
}, },
options: Options( options: Options(
headers: {'Authorization': auth.authorizationToken}, headers: {'Authorization': auth.authorizationToken},

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart';
class CloudflareApi extends RestApiMap { class CloudflareApi extends RestApiMap {
@ -27,7 +28,7 @@ class CloudflareApi extends RestApiMap {
responseType: ResponseType.json, responseType: ResponseType.json,
); );
if (isWithToken) { if (isWithToken) {
final String? token = getIt<ApiConfigModel>().dnsProviderKey; final String? token = getIt<ResourcesModel>().dnsProviderKey;
assert(token != null); assert(token != null);
options.headers = {'Authorization': 'Bearer $token'}; options.headers = {'Authorization': 'Bearer $token'};
} }

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/json/dns_providers/desec_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/desec_dns_info.dart';
class DesecApi extends RestApiMap { class DesecApi extends RestApiMap {
@ -27,7 +28,7 @@ class DesecApi extends RestApiMap {
responseType: ResponseType.json, responseType: ResponseType.json,
); );
if (isWithToken) { if (isWithToken) {
final String? token = getIt<ApiConfigModel>().dnsProviderKey; final String? token = getIt<ResourcesModel>().dnsProviderKey;
assert(token != null); assert(token != null);
options.headers = {'Authorization': 'Token $token'}; options.headers = {'Authorization': 'Token $token'};
} }

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart';
class DigitalOceanDnsApi extends RestApiMap { class DigitalOceanDnsApi extends RestApiMap {
@ -27,7 +28,7 @@ class DigitalOceanDnsApi extends RestApiMap {
responseType: ResponseType.json, responseType: ResponseType.json,
); );
if (isWithToken) { if (isWithToken) {
final String? token = getIt<ApiConfigModel>().dnsProviderKey; final String? token = getIt<ResourcesModel>().dnsProviderKey;
assert(token != null); assert(token != null);
options.headers = {'Authorization': 'Bearer $token'}; options.headers = {'Authorization': 'Bearer $token'};
} }

View file

@ -1,19 +1,25 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:selfprivacy/config/config.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/console_log.dart'; import 'package:selfprivacy/logic/models/console_log.dart';
import 'package:selfprivacy/utils/app_logger.dart';
abstract class RestApiMap { abstract class RestApiMap {
static final log = const AppLogger(name: 'rest_api_map').log;
Future<Dio> getClient({final BaseOptions? customOptions}) async { Future<Dio> getClient({final BaseOptions? customOptions}) async {
final Dio dio = Dio(customOptions ?? (await options)); final Dio dio = Dio(customOptions ?? (await options));
if (hasLogger) { if (hasLogger && config.shouldDebugPrint) {
dio.interceptors.add(PrettyDioLogger()); dio.interceptors.add(
PrettyDioLogger(logPrint: (final object) => debugPrint('$object')),
);
} }
dio.interceptors.add(ConsoleInterceptor()); dio.interceptors.add(ConsoleInterceptor());
dio.httpClientAdapter = IOHttpClientAdapter( dio.httpClientAdapter = IOHttpClientAdapter(
@ -28,14 +34,13 @@ abstract class RestApiMap {
dio.interceptors.add( dio.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(
onError: (final DioException e, final ErrorInterceptorHandler handler) { onError: (
print(e.requestOptions.path); final DioException exception,
print(e.requestOptions.data); final ErrorInterceptorHandler handler,
) {
log('got dio exception:', error: exception);
print(e.message); return handler.next(exception);
print(e.response);
return handler.next(e);
}, },
), ),
); );
@ -103,7 +108,6 @@ class ConsoleInterceptor extends InterceptorsWrapper {
final ErrorInterceptorHandler handler, final ErrorInterceptorHandler handler,
) async { ) async {
final Response? response = err.response; final Response? response = err.response;
log(err.toString());
addConsoleLog( addConsoleLog(
ManualConsoleLog.warning( ManualConsoleLog.warning(

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart'; import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart';
import 'package:selfprivacy/utils/password_generator.dart'; import 'package:selfprivacy/utils/password_generator.dart';
@ -30,7 +31,7 @@ class DigitalOceanApi extends RestApiMap {
responseType: ResponseType.json, responseType: ResponseType.json,
); );
if (isWithToken) { if (isWithToken) {
final String? token = getIt<ApiConfigModel>().serverProviderKey; final String? token = getIt<ResourcesModel>().serverProviderKey;
assert(token != null); assert(token != null);
options.headers = {'Authorization': 'Bearer $token'}; options.headers = {'Authorization': 'Bearer $token'};
} }

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
@ -31,7 +32,7 @@ class HetznerApi extends RestApiMap {
responseType: ResponseType.json, responseType: ResponseType.json,
); );
if (isWithToken) { if (isWithToken) {
final String? token = getIt<ApiConfigModel>().serverProviderKey; final String? token = getIt<ResourcesModel>().serverProviderKey;
assert(token != null); assert(token != null);
options.headers = {'Authorization': 'Bearer $token'}; options.headers = {'Authorization': 'Bearer $token'};
} }

View file

@ -6,6 +6,7 @@ import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
@ -108,7 +109,7 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
final BackupsServerLoaded event, final BackupsServerLoaded event,
final Emitter<BackupsState> emit, final Emitter<BackupsState> emit,
) async { ) async {
BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket; BackblazeBucket? bucket = getIt<ResourcesModel>().backblazeBucket;
final backups = getIt<ApiConnectionRepository>().apiData.backups; final backups = getIt<ApiConnectionRepository>().apiData.backups;
final backupConfig = getIt<ApiConnectionRepository>().apiData.backupConfig; final backupConfig = getIt<ApiConnectionRepository>().apiData.backupConfig;
if (backupConfig.data == null || backups.data == null) { if (backupConfig.data == null || backups.data == null) {
@ -227,7 +228,7 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
emit(BackupsUnititialized()); emit(BackupsUnititialized());
return; return;
} }
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket; final BackblazeBucket? bucket = getIt<ResourcesModel>().backblazeBucket;
emit( emit(
BackupsInitialized( BackupsInitialized(
backblazeBucket: bucket, backblazeBucket: bucket,

View file

@ -2,6 +2,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart'; import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart'; import 'package:selfprivacy/logic/providers/providers_controller.dart';
class MetricsLoadException implements Exception { class MetricsLoadException implements Exception {
@ -30,7 +31,7 @@ class MetricsRepository {
break; break;
} }
final serverId = getIt<ApiConfigModel>().serverDetails!.id; final serverId = getIt<ResourcesModel>().serverDetails!.id;
final result = await ProvidersController.currentServerProvider!.getMetrics( final result = await ProvidersController.currentServerProvider!.getMetrics(
serverId, serverId,
start, start,

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/logic/models/server_metadata.dart';
import 'package:selfprivacy/logic/models/ssh_settings.dart'; import 'package:selfprivacy/logic/models/ssh_settings.dart';
@ -43,7 +44,7 @@ class ServerDetailsCubit
final serverProviderApi = ProvidersController.currentServerProvider; final serverProviderApi = ProvidersController.currentServerProvider;
final dnsProviderApi = ProvidersController.currentDnsProvider; final dnsProviderApi = ProvidersController.currentDnsProvider;
if (serverProviderApi != null && dnsProviderApi != null) { if (serverProviderApi != null && dnsProviderApi != null) {
final serverId = getIt<ApiConfigModel>().serverDetails?.id ?? 0; final serverId = getIt<ResourcesModel>().serverDetails?.id ?? 0;
final metadataResult = await serverProviderApi.getMetadata(serverId); final metadataResult = await serverProviderApi.getMetadata(serverId);
metadataResult.data.add( metadataResult.data.add(
ServerMetadataEntity( ServerMetadataEntity(
@ -60,7 +61,7 @@ class ServerDetailsCubit
} }
void check() async { void check() async {
final bool isReadyToCheck = getIt<ApiConfigModel>().serverDetails != null; final bool isReadyToCheck = getIt<ResourcesModel>().serverDetails != null;
try { try {
if (isReadyToCheck) { if (isReadyToCheck) {
emit(const ServerDetailsLoading()); emit(const ServerDetailsLoading());

View file

@ -15,6 +15,7 @@ import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
import 'package:selfprivacy/logic/models/launch_installation_data.dart'; import 'package:selfprivacy/logic/models/launch_installation_data.dart';
import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/price.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart';
@ -222,12 +223,14 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: BackupsProviderType.backblaze, provider: BackupsProviderType.backblaze,
); );
final BackblazeBucket? bucket; final BackblazeBucket? bucket;
await repository.saveBackblazeKey(backblazeCredential); await repository.saveBackupsCredential(backblazeCredential);
if (state is ServerInstallationRecovery) { if (state is ServerInstallationRecovery) {
final configuration = await ServerApi( final configuration = await ServerApi(
customToken: customToken:
(state as ServerInstallationRecovery).serverDetails!.apiToken, (state as ServerInstallationRecovery).serverDetails!.apiToken,
isWithToken: true, isWithToken: true,
overrideDomain:
(state as ServerInstallationRecovery).serverDomain!.domainName,
).getBackupsConfiguration(); ).getBackupsConfiguration();
if (configuration != null) { if (configuration != null) {
try { try {
@ -401,7 +404,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
); );
timer = Timer(pauseDuration, () async { timer = Timer(pauseDuration, () async {
final ServerHostingDetails serverDetails = await repository.restart(); final ServerHostingDetails serverDetails = await repository.restart();
await repository.saveIsServerResetedFirstTime(true); await repository.saveIsServerRebootedFirstTime(true);
await repository.saveServerDetails(serverDetails); await repository.saveServerDetails(serverDetails);
final ServerInstallationNotFinished newState = dataState.copyWith( final ServerInstallationNotFinished newState = dataState.copyWith(
@ -442,7 +445,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
); );
timer = Timer(pauseDuration, () async { timer = Timer(pauseDuration, () async {
final ServerHostingDetails serverDetails = await repository.restart(); final ServerHostingDetails serverDetails = await repository.restart();
await repository.saveIsServerResetedSecondTime(true); await repository.saveIsServerRebootedSecondTime(true);
await repository.saveServerDetails(serverDetails); await repository.saveServerDetails(serverDetails);
final ServerInstallationNotFinished newState = dataState.copyWith( final ServerInstallationNotFinished newState = dataState.copyWith(
@ -577,10 +580,12 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final ServerProviderType serverProvider = await ServerApi( final ServerProviderType serverProvider = await ServerApi(
customToken: serverDetails.apiToken, customToken: serverDetails.apiToken,
isWithToken: true, isWithToken: true,
overrideDomain: serverDomain.domainName,
).getServerProviderType(); ).getServerProviderType();
final dnsProvider = await ServerApi( final dnsProvider = await ServerApi(
customToken: serverDetails.apiToken, customToken: serverDetails.apiToken,
isWithToken: true, isWithToken: true,
overrideDomain: serverDomain.domainName,
).getDnsProviderType(); ).getDnsProviderType();
if (serverProvider == ServerProviderType.unknown || if (serverProvider == ServerProviderType.unknown ||
dnsProvider == DnsProviderType.unknown) { dnsProvider == DnsProviderType.unknown) {
@ -762,6 +767,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final dnsProviderType = await ServerApi( final dnsProviderType = await ServerApi(
customToken: dataState.serverDetails!.apiToken, customToken: dataState.serverDetails!.apiToken,
isWithToken: true, isWithToken: true,
overrideDomain: serverDomain.domainName,
).getDnsProviderType(); ).getDnsProviderType();
await repository.saveDomain( await repository.saveDomain(
ServerDomain( ServerDomain(
@ -769,6 +775,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: dnsProviderType, provider: dnsProviderType,
), ),
); );
await repository.setDnsApiToken(token);
emit( emit(
dataState.copyWith( dataState.copyWith(
serverDomain: ServerDomain( serverDomain: ServerDomain(
@ -785,21 +792,18 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final BackupsCredential backblazeCredential, final BackupsCredential backblazeCredential,
) async { ) async {
await repository.saveIsServerStarted(true); await repository.saveIsServerStarted(true);
await repository.saveIsServerResetedFirstTime(true); await repository.saveIsServerRebootedFirstTime(true);
await repository.saveIsServerResetedSecondTime(true); await repository.saveIsServerRebootedSecondTime(true);
await repository.saveHasFinalChecked(true);
await repository.saveIsRecoveringServer(false); await repository.saveIsRecoveringServer(false);
final serverType = await ProvidersController.currentServerProvider! final serverType = await ProvidersController.currentServerProvider!
.getServerType(state.serverDetails!.id); .getServerType(state.serverDetails!.id);
await repository.saveServerType(serverType.data!); await repository.saveServerType(serverType.data!);
await ProvidersController.currentServerProvider! await ProvidersController.currentServerProvider!
.trySetServerLocation(serverType.data!.location.identifier); .trySetServerLocation(serverType.data!.location.identifier);
final User mainUser = await repository.getMainUser(); await repository.saveHasFinalChecked(true);
await repository.saveRootUser(mainUser);
final ServerInstallationRecovery updatedState = final ServerInstallationRecovery updatedState =
(state as ServerInstallationRecovery).copyWith( (state as ServerInstallationRecovery).copyWith(
backblazeCredential: backblazeCredential, backblazeCredential: backblazeCredential,
rootUser: mainUser,
serverTypeIdentificator: serverType.data!.identifier, serverTypeIdentificator: serverType.data!.identifier,
); );
emit(updatedState.finish()); emit(updatedState.finish());

View file

@ -9,10 +9,15 @@ import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/dns_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
import 'package:selfprivacy/logic/models/json/device_token.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart';
@ -34,22 +39,25 @@ class ServerAuthorizationException implements Exception {
class ServerInstallationRepository { class ServerInstallationRepository {
Box box = Hive.box(BNames.serverInstallationBox); Box box = Hive.box(BNames.serverInstallationBox);
Box<User> usersBox = Hive.box(BNames.usersBox);
Future<ServerInstallationState> load() async { Future<ServerInstallationState> load() async {
final String? providerApiToken = getIt<ApiConfigModel>().serverProviderKey; final ServerInstallationWizardData? wizardData =
final String? location = getIt<ApiConfigModel>().serverLocation; getIt<WizardDataModel>().serverInstallation;
final String? dnsApiToken = getIt<ApiConfigModel>().dnsProviderKey; final List<Server> servers = getIt<ResourcesModel>().servers;
final String? serverTypeIdentificator = getIt<ApiConfigModel>().serverType; final String? providerApiToken = getIt<ResourcesModel>().serverProviderKey;
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain; final String? location = getIt<ResourcesModel>().serverLocation;
final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().dnsProvider; final String? dnsApiToken = getIt<ResourcesModel>().dnsProviderKey;
final String? serverTypeIdentificator = getIt<ResourcesModel>().serverType;
final ServerDomain? serverDomain = getIt<ResourcesModel>().serverDomain;
final DnsProviderType? dnsProvider = getIt<ResourcesModel>().dnsProvider;
final ServerProviderType? serverProvider = final ServerProviderType? serverProvider =
getIt<ApiConfigModel>().serverProvider; getIt<ResourcesModel>().serverProvider;
final BackupsCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ResourcesModel>().backblazeCredential;
final ServerHostingDetails? serverDetails = final ServerHostingDetails? serverDetails =
getIt<ApiConfigModel>().serverDetails; getIt<ResourcesModel>().serverDetails;
// TODO: Init server providers in another place
if (serverProvider != null || if (serverProvider != null ||
(serverDetails != null && (serverDetails != null &&
serverDetails.provider != ServerProviderType.unknown)) { serverDetails.provider != ServerProviderType.unknown)) {
@ -73,85 +81,48 @@ class ServerInstallationRepository {
); );
} }
if (box.get(BNames.hasFinalChecked, defaultValue: false)) { // If we don't have any wizard data, we either have a server set up, or we are starting from scratch
TlsOptions.verifyCertificate = true; // This behaviour shall change when we introduce multitenancy
if (serverTypeIdentificator == null && serverDetails != null) { if (wizardData == null) {
final finalServerType = await ProvidersController.currentServerProvider! if (servers.isEmpty) {
.getServerType(serverDetails.id); // We don't have anything set up, so we start from scratch
await saveServerType(finalServerType.data!); return ServerInstallationNotFinished.fromWizardData(
await ProvidersController.currentServerProvider! ServerInstallationWizardData.empty(),
.trySetServerLocation(finalServerType.data!.location.identifier);
return ServerInstallationFinished(
installationDialoguePopUp: null,
providerApiToken: providerApiToken!,
serverTypeIdentificator: finalServerType.data!.identifier,
dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!,
serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerResetedFirstTime:
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
isServerResetedSecondTime:
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
); );
} else { } else {
// We have a server set up, so we load it
TlsOptions.verifyCertificate = true;
return ServerInstallationFinished( return ServerInstallationFinished(
installationDialoguePopUp: null,
providerApiToken: providerApiToken!, providerApiToken: providerApiToken!,
serverTypeIdentificator: serverTypeIdentificator!, serverTypeIdentificator: serverTypeIdentificator!,
dnsApiToken: dnsApiToken!, dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDetails: serverDetails!, serverDetails: serverDetails!,
rootUser: box.get(BNames.rootUser),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerResetedFirstTime:
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
isServerResetedSecondTime:
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
); );
} }
} }
if (box.get(BNames.isRecoveringServer, defaultValue: false) && if (wizardData.isRecoveringServer && wizardData.serverDomain != null) {
serverDomain != null) {
return ServerInstallationRecovery( return ServerInstallationRecovery(
providerApiToken: providerApiToken, providerApiToken: wizardData.serverProviderKey,
dnsApiToken: dnsApiToken, dnsApiToken: wizardData.dnsProviderKey,
serverDomain: serverDomain, serverDomain: wizardData.serverDomain,
serverTypeIdentificator: serverTypeIdentificator, serverTypeIdentificator: wizardData.serverTypeIdentifier,
backblazeCredential: backblazeCredential, backblazeCredential: wizardData.backupsCredential,
serverDetails: serverDetails, serverDetails: wizardData.serverDetails,
rootUser: box.get(BNames.rootUser),
currentStep: _getCurrentRecoveryStep( currentStep: _getCurrentRecoveryStep(
providerApiToken, wizardData.serverProviderKey,
dnsApiToken, wizardData.dnsProviderKey,
serverDomain, wizardData.serverDomain!,
serverDetails, wizardData.serverDetails,
), ),
recoveryCapabilities: await getRecoveryCapabilities(serverDomain), recoveryCapabilities:
await getRecoveryCapabilities(wizardData.serverDomain!),
); );
} }
return ServerInstallationNotFinished( return ServerInstallationNotFinished.fromWizardData(wizardData);
providerApiToken: providerApiToken,
dnsApiToken: dnsApiToken,
serverDomain: serverDomain,
serverTypeIdentificator: serverTypeIdentificator,
backblazeCredential: backblazeCredential,
serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerResetedFirstTime:
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
isServerResetedSecondTime:
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
isLoading: box.get(BNames.isLoading, defaultValue: false),
dnsMatches: null,
customSshKey: null,
);
} }
RecoveryStep _getCurrentRecoveryStep( RecoveryStep _getCurrentRecoveryStep(
@ -177,7 +148,9 @@ class ServerInstallationRepository {
void clearAppConfig() { void clearAppConfig() {
box.clear(); box.clear();
usersBox.clear(); getIt<ResourcesModel>().clear();
getIt<WizardDataModel>().clear();
getIt<ApiConnectionRepository>().clear();
} }
Future<ServerHostingDetails> startServer( Future<ServerHostingDetails> startServer(
@ -209,6 +182,9 @@ class ServerInstallationRepository {
if (!domainResult.success || domainResult.data.isEmpty) { if (!domainResult.success || domainResult.data.isEmpty) {
return false; return false;
} }
await getIt<ResourcesModel>().removeDnsProviderToken(
getIt<ResourcesModel>().dnsProviderCredentials.first,
);
return domainResult.data.any( return domainResult.data.any(
(final serverDomain) => serverDomain.domainName == domain, (final serverDomain) => serverDomain.domainName == domain,
@ -216,7 +192,12 @@ class ServerInstallationRepository {
} }
Future<void> createDkimRecord(final ServerDomain domain) async { Future<void> createDkimRecord(final ServerDomain domain) async {
final ServerApi api = ServerApi(); final ServerApi api = ServerApi(
overrideDomain: domain.domainName,
customToken:
getIt<WizardDataModel>().serverInstallation!.serverDetails!.apiToken,
isWithToken: true,
);
late DnsRecord record; late DnsRecord record;
try { try {
@ -233,14 +214,26 @@ class ServerInstallationRepository {
} }
Future<bool> isHttpServerWorking() async { Future<bool> isHttpServerWorking() async {
final ServerApi api = ServerApi(); final ServerApi api = ServerApi(
overrideDomain:
getIt<WizardDataModel>().serverInstallation!.serverDomain!.domainName,
customToken:
getIt<WizardDataModel>().serverInstallation!.serverDetails!.apiToken,
isWithToken: true,
);
return api.isHttpServerWorking(); return api.isHttpServerWorking();
} }
Future<ServerHostingDetails> restart() async { Future<ServerHostingDetails> restart() async {
final server = getIt<ApiConfigModel>().serverDetails!; final server = getIt<WizardDataModel>().serverInstallation!.serverDetails!;
final result = await ServerApi().reboot(); final result = await ServerApi(
overrideDomain:
getIt<WizardDataModel>().serverInstallation!.serverDomain!.domainName,
customToken:
getIt<WizardDataModel>().serverInstallation!.serverDetails!.apiToken,
isWithToken: true,
).reboot();
if (result.success && result.data != null) { if (result.success && result.data != null) {
server.copyWith(startTime: result.data); server.copyWith(startTime: result.data);
@ -252,7 +245,7 @@ class ServerInstallationRepository {
} }
Future<ServerHostingDetails> powerOn() async { Future<ServerHostingDetails> powerOn() async {
final server = getIt<ApiConfigModel>().serverDetails!; final server = getIt<ResourcesModel>().serverDetails!;
return startServer(server); return startServer(server);
} }
@ -436,174 +429,119 @@ class ServerInstallationRepository {
); );
} }
Future<User> getMainUser() async {
final ServerApi serverApi = ServerApi();
const User fallbackUser = User(
isFoundOnServer: false,
type: UserType.primary,
note: "Couldn't find main user on server, API is outdated",
login: 'UNKNOWN',
sshKeys: [],
);
final String? serverApiVersion = await serverApi.getApiVersion();
final users = await serverApi.getAllUsers();
if (serverApiVersion == null || users.isEmpty) {
return fallbackUser;
}
try {
final Version parsedVersion = Version.parse(serverApiVersion);
if (!VersionConstraint.parse('>=1.2.5').allows(parsedVersion)) {
return fallbackUser;
}
return users.firstWhere(
(final User user) => user.type == UserType.primary,
);
} on FormatException {
return fallbackUser;
}
}
Future<List<ServerBasicInfo>> getServersOnProviderAccount() async => Future<List<ServerBasicInfo>> getServersOnProviderAccount() async =>
(await ProvidersController.currentServerProvider!.getServers()).data; (await ProvidersController.currentServerProvider!.getServers()).data;
Future<void> saveServerDetails( Future<void> saveServerDetails(
final ServerHostingDetails serverDetails, final ServerHostingDetails serverDetails,
) async { ) async {
await getIt<ApiConfigModel>().setServerDetails(serverDetails); await getIt<WizardDataModel>().setServerDetails(serverDetails);
} }
Future<void> deleteServerDetails() async { Future<void> deleteServerDetails() async {
await box.delete(BNames.serverDetails); await getIt<WizardDataModel>().deleteServerDetails();
await getIt<ApiConfigModel>().init();
} }
Future<void> saveServerProviderType(final ServerProviderType type) async { Future<void> saveServerProviderType(final ServerProviderType type) async {
await getIt<ApiConfigModel>().storeServerProviderType(type); await getIt<WizardDataModel>().setServerProviderType(type);
} }
Future<void> saveDnsProviderType(final DnsProviderType type) async { Future<void> saveDnsProviderType(final DnsProviderType type) async {
await getIt<ApiConfigModel>().setDnsProviderType(type); await getIt<WizardDataModel>().setDnsProviderType(type);
} }
Future<void> saveServerProviderKey(final String key) async { Future<void> saveServerProviderKey(final String key) async {
await getIt<ApiConfigModel>().setServerProviderKey(key); await getIt<WizardDataModel>().setServerProviderKey(key);
await getIt<ResourcesModel>().addServerProviderToken(
ServerProviderCredential(
tokenId: null,
token: key,
provider:
getIt<WizardDataModel>().serverInstallation!.serverProviderType!,
associatedServerIds: [],
),
);
} }
Future<void> saveServerType(final ServerType serverType) async { Future<void> saveServerType(final ServerType serverType) async {
await getIt<ApiConfigModel>().setServerTypeIdentifier( await getIt<WizardDataModel>().setServerTypeIdentifier(
serverType.identifier, serverType.identifier,
); );
await getIt<ApiConfigModel>().setServerLocation( await getIt<WizardDataModel>().setServerLocation(
serverType.location.identifier, serverType.location.identifier,
); );
} }
Future<void> deleteServerProviderKey() async { Future<void> saveBackupsCredential(
await box.delete(BNames.hetznerKey); final BackupsCredential backupsCredential,
await getIt<ApiConfigModel>().init();
}
Future<void> saveBackblazeKey(
final BackupsCredential backblazeCredential,
) async { ) async {
await getIt<ApiConfigModel>().setBackblazeCredential(backblazeCredential); await getIt<WizardDataModel>().setBackupsCredential(backupsCredential);
}
Future<void> deleteBackblazeKey() async {
await box.delete(BNames.backblazeCredential);
await getIt<ApiConfigModel>().init();
} }
Future<void> setDnsApiToken(final String key) async { Future<void> setDnsApiToken(final String key) async {
await getIt<ApiConfigModel>().setDnsProviderKey(key); await getIt<WizardDataModel>().setDnsProviderKey(key);
} await getIt<ResourcesModel>().addDnsProviderToken(
DnsProviderCredential(
Future<void> deleteDnsProviderKey() async { tokenId: null,
await box.delete(BNames.cloudFlareKey); token: key,
await getIt<ApiConfigModel>().init(); provider: getIt<WizardDataModel>().serverInstallation!.dnsProviderType!,
associatedDomainNames: [],
),
);
} }
Future<void> saveDomain(final ServerDomain serverDomain) async { Future<void> saveDomain(final ServerDomain serverDomain) async {
await getIt<ApiConfigModel>().setServerDomain(serverDomain); await getIt<WizardDataModel>().setServerDomain(serverDomain);
} }
Future<void> deleteDomain() async { Future<void> deleteDomain() async {
await box.delete(BNames.serverDomain); await getIt<WizardDataModel>().deleteServerDomain();
await getIt<ApiConfigModel>().init();
} }
Future<void> saveIsServerStarted(final bool value) async { Future<void> saveIsServerStarted(final bool value) async {
await box.put(BNames.isServerStarted, value); await getIt<WizardDataModel>().setIsServerStarted(value);
} }
Future<void> saveIsServerResetedFirstTime(final bool value) async { Future<void> saveIsServerRebootedFirstTime(final bool value) async {
await box.put(BNames.isServerResetedFirstTime, value); await getIt<WizardDataModel>().setIsServerRebootedFirstTime(value);
} }
Future<void> saveIsServerResetedSecondTime(final bool value) async { Future<void> saveIsServerRebootedSecondTime(final bool value) async {
await box.put(BNames.isServerResetedSecondTime, value); await getIt<WizardDataModel>().setIsServerRebootedSecondTime(value);
} }
Future<void> saveRootUser(final User rootUser) async { Future<void> saveRootUser(final User rootUser) async {
await box.put(BNames.rootUser, rootUser); await getIt<WizardDataModel>().setRootUser(rootUser);
} }
Future<void> saveIsRecoveringServer(final bool value) async { Future<void> saveIsRecoveringServer(final bool value) async {
await box.put(BNames.isRecoveringServer, value); await getIt<WizardDataModel>().setIsRecoveringServer(value);
} }
Future<void> saveHasFinalChecked(final bool value) async { Future<void> saveHasFinalChecked(final bool value) async {
await box.put(BNames.hasFinalChecked, value); // We are finished here. Time to save the state and finish the wizard
} // TODO: A lot of null checks are skipped here. Implication that every value exists might become false in the future.
// TODO: We would actually want to handle token creation elsewhere.
Future<bool> deleteServer(final ServerDomain serverDomain) async { await getIt<WizardDataModel>().moveServerTypeToServerDetails();
final ServerApi api = ServerApi(); final ServerInstallationWizardData wizardData =
final dnsRecords = await api.getDnsRecords(); getIt<WizardDataModel>().serverInstallation!;
final GenericResult<void> removalResult = await getIt<ResourcesModel>().addServer(
await ProvidersController.currentDnsProvider!.removeDomainRecords( Server(
domain: serverDomain, hostingDetails: wizardData.serverDetails!,
records: dnsRecords, domain: wizardData.serverDomain!,
),
); );
await getIt<ResourcesModel>().associateServerWithToken(
if (!removalResult.success) { wizardData.serverDetails!.id,
getIt<NavigationService>().showSnackBar( wizardData.serverProviderKey!,
'modals.dns_removal_error'.tr(),
);
return false;
}
final deletionResult =
await ProvidersController.currentServerProvider!.deleteServer(
serverDomain.domainName,
); );
await getIt<ResourcesModel>().associateDomainWithToken(
if (!deletionResult.success) { wizardData.serverDomain!.domainName,
getIt<NavigationService>().showSnackBar( wizardData.dnsProviderKey!,
'modals.server_validators_error'.tr(), );
); await getIt<ResourcesModel>().addBackupsCredential(
return false; wizardData.backupsCredential!,
} );
await getIt<WizardDataModel>().clearServerInstallation();
await box.put(BNames.hasFinalChecked, false);
await box.put(BNames.isServerStarted, false);
await box.put(BNames.isServerResetedFirstTime, false);
await box.put(BNames.isServerResetedSecondTime, false);
await box.put(BNames.isLoading, false);
await box.put(BNames.serverDetails, null);
return true;
}
Future<void> deleteServerRelatedRecords() async {
await box.deleteAll([
BNames.serverDetails,
BNames.isServerStarted,
BNames.isServerResetedFirstTime,
BNames.isServerResetedSecondTime,
BNames.hasFinalChecked,
BNames.isLoading,
]);
await getIt<ApiConfigModel>().init();
} }
} }

View file

@ -54,7 +54,7 @@ abstract class ServerInstallationState extends Equatable {
ServerSetupProgress get progress => ServerSetupProgress ServerSetupProgress get progress => ServerSetupProgress
.values[_fulfillmentList.where((final el) => el!).length]; .values[_fulfillmentList.where((final el) => el!).length];
int get porgressBar { int get progressBar {
if (progress.index < 6) { if (progress.index < 6) {
return progress.index; return progress.index;
} else if (progress.index < 10) { } else if (progress.index < 10) {
@ -119,7 +119,7 @@ class TimerState extends ServerInstallationNotFinished {
enum ServerSetupProgress { enum ServerSetupProgress {
nothingYet, nothingYet,
serverProviderFilled, serverProviderFilled,
servertTypeFilled, serverTypeFilled,
dnsProviderFilled, dnsProviderFilled,
backblazeFilled, backblazeFilled,
domainFilled, domainFilled,
@ -147,6 +147,26 @@ class ServerInstallationNotFinished extends ServerInstallationState {
super.serverDetails, super.serverDetails,
super.installationDialoguePopUp, super.installationDialoguePopUp,
}); });
ServerInstallationNotFinished.fromWizardData(
final ServerInstallationWizardData data,
) : this(
providerApiToken: data.serverProviderKey,
dnsApiToken: data.dnsProviderKey,
serverDomain: data.serverDomain,
serverTypeIdentificator: data.serverTypeIdentifier,
backblazeCredential: data.backupsCredential,
serverDetails: data.serverDetails,
rootUser: data.rootUser,
isServerStarted: data.isServerStarted,
isServerResetedFirstTime: data.isServerResetedFirstTime,
isServerResetedSecondTime: data.isServerResetedSecondTime,
isLoading: data.isLoading,
dnsMatches: null,
customSshKey: null,
installationDialoguePopUp: null,
);
final bool isLoading; final bool isLoading;
final Map<String, DnsRecordStatus>? dnsMatches; final Map<String, DnsRecordStatus>? dnsMatches;
final String? customSshKey; final String? customSshKey;
@ -211,12 +231,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
dnsApiToken: dnsApiToken!, dnsApiToken: dnsApiToken!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
rootUser: rootUser!,
serverDetails: serverDetails!, serverDetails: serverDetails!,
isServerStarted: isServerStarted,
isServerResetedFirstTime: isServerResetedFirstTime,
isServerResetedSecondTime: isServerResetedSecondTime,
installationDialoguePopUp: installationDialoguePopUp,
); );
} }
@ -247,13 +262,14 @@ class ServerInstallationFinished extends ServerInstallationState {
required String super.dnsApiToken, required String super.dnsApiToken,
required BackupsCredential super.backblazeCredential, required BackupsCredential super.backblazeCredential,
required ServerDomain super.serverDomain, required ServerDomain super.serverDomain,
required User super.rootUser,
required ServerHostingDetails super.serverDetails, required ServerHostingDetails super.serverDetails,
required super.isServerStarted, }) : super(
required super.isServerResetedFirstTime, rootUser: null,
required super.isServerResetedSecondTime, isServerStarted: true,
required super.installationDialoguePopUp, isServerResetedFirstTime: true,
}); isServerResetedSecondTime: true,
installationDialoguePopUp: null,
);
@override @override
List<Object?> get props => [ List<Object?> get props => [
@ -302,9 +318,9 @@ class ServerInstallationRecovery extends ServerInstallationState {
super.dnsApiToken, super.dnsApiToken,
super.backblazeCredential, super.backblazeCredential,
super.serverDomain, super.serverDomain,
super.rootUser,
super.serverDetails, super.serverDetails,
}) : super( }) : super(
rootUser: null,
isServerStarted: true, isServerStarted: true,
isServerResetedFirstTime: true, isServerResetedFirstTime: true,
isServerResetedSecondTime: true, isServerResetedSecondTime: true,
@ -334,7 +350,6 @@ class ServerInstallationRecovery extends ServerInstallationState {
final String? dnsApiToken, final String? dnsApiToken,
final BackupsCredential? backblazeCredential, final BackupsCredential? backblazeCredential,
final ServerDomain? serverDomain, final ServerDomain? serverDomain,
final User? rootUser,
final ServerHostingDetails? serverDetails, final ServerHostingDetails? serverDetails,
final RecoveryStep? currentStep, final RecoveryStep? currentStep,
final ServerRecoveryCapabilities? recoveryCapabilities, final ServerRecoveryCapabilities? recoveryCapabilities,
@ -346,7 +361,6 @@ class ServerInstallationRecovery extends ServerInstallationState {
dnsApiToken: dnsApiToken ?? this.dnsApiToken, dnsApiToken: dnsApiToken ?? this.dnsApiToken,
backblazeCredential: backblazeCredential ?? this.backblazeCredential, backblazeCredential: backblazeCredential ?? this.backblazeCredential,
serverDomain: serverDomain ?? this.serverDomain, serverDomain: serverDomain ?? this.serverDomain,
rootUser: rootUser ?? this.rootUser,
serverDetails: serverDetails ?? this.serverDetails, serverDetails: serverDetails ?? this.serverDetails,
currentStep: currentStep ?? this.currentStep, currentStep: currentStep ?? this.currentStep,
recoveryCapabilities: recoveryCapabilities ?? this.recoveryCapabilities, recoveryCapabilities: recoveryCapabilities ?? this.recoveryCapabilities,
@ -358,11 +372,6 @@ class ServerInstallationRecovery extends ServerInstallationState {
dnsApiToken: dnsApiToken!, dnsApiToken: dnsApiToken!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
rootUser: rootUser!,
serverDetails: serverDetails!, serverDetails: serverDetails!,
isServerStarted: true,
isServerResetedFirstTime: true,
isServerResetedSecondTime: true,
installationDialoguePopUp: null,
); );
} }

View file

@ -1,115 +1,18 @@
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
class ApiConfigModel { class ApiConfigModel {
final Box _box = Hive.box(BNames.serverInstallationBox); final Box _box = Hive.box(BNames.serverInstallationBox);
ServerHostingDetails? get serverDetails => _serverDetails;
String? get serverProviderKey => _serverProviderKey;
String? get serverLocation => _serverLocation;
String? get serverType => _serverType;
String? get dnsProviderKey => _dnsProviderKey;
ServerProviderType? get serverProvider => _serverProvider;
DnsProviderType? get dnsProvider => _dnsProvider;
BackupsCredential? get backblazeCredential => _backblazeCredential;
ServerDomain? get serverDomain => _serverDomain;
BackblazeBucket? get backblazeBucket => _backblazeBucket;
static const localeCodeFallback = 'en'; static const localeCodeFallback = 'en';
String? _localeCode; String? _localeCode;
String get localeCode => _localeCode ?? localeCodeFallback; String get localeCode => _localeCode ?? localeCodeFallback;
Future<void> setLocaleCode(final String value) async => _localeCode = value; Future<void> setLocaleCode(final String value) async => _localeCode = value;
Future<void> resetLocaleCode() async => _localeCode = null; Future<void> resetLocaleCode() async => _localeCode = null;
String? _serverProviderKey;
String? _serverLocation;
String? _dnsProviderKey;
String? _serverType;
ServerProviderType? _serverProvider;
DnsProviderType? _dnsProvider;
ServerHostingDetails? _serverDetails;
BackupsCredential? _backblazeCredential;
ServerDomain? _serverDomain;
BackblazeBucket? _backblazeBucket;
Future<void> storeServerProviderType(final ServerProviderType value) async {
await _box.put(BNames.serverProvider, value);
_serverProvider = value;
}
Future<void> setDnsProviderType(final DnsProviderType value) async {
await _box.put(BNames.dnsProvider, value);
_dnsProvider = value;
}
Future<void> setServerProviderKey(final String value) async {
await _box.put(BNames.hetznerKey, value);
_serverProviderKey = value;
}
Future<void> setDnsProviderKey(final String value) async {
await _box.put(BNames.cloudFlareKey, value);
_dnsProviderKey = value;
}
Future<void> setServerTypeIdentifier(final String typeIdentifier) async {
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
_serverType = typeIdentifier;
}
Future<void> setServerLocation(final String serverLocation) async {
await _box.put(BNames.serverLocation, serverLocation);
_serverLocation = serverLocation;
}
Future<void> setBackblazeCredential(final BackupsCredential value) async {
await _box.put(BNames.backblazeCredential, value);
_backblazeCredential = value;
}
Future<void> setServerDomain(final ServerDomain value) async {
await _box.put(BNames.serverDomain, value);
_serverDomain = value;
}
Future<void> setServerDetails(final ServerHostingDetails value) async {
await _box.put(BNames.serverDetails, value);
_serverDetails = value;
}
Future<void> setBackblazeBucket(final BackblazeBucket value) async { Future<void> setBackblazeBucket(final BackblazeBucket value) async {
await _box.put(BNames.backblazeBucket, value); await _box.put(BNames.backblazeBucket, value);
_backblazeBucket = value;
}
void clear() {
_serverProviderKey = null;
_dnsProvider = null;
_serverLocation = null;
_dnsProviderKey = null;
_backblazeCredential = null;
_serverDomain = null;
_serverDetails = null;
_backblazeBucket = null;
_serverType = null;
_serverProvider = null;
}
Future<void> init() async {
_serverProviderKey = _box.get(BNames.hetznerKey);
_serverLocation = _box.get(BNames.serverLocation);
_dnsProviderKey = _box.get(BNames.cloudFlareKey);
_backblazeCredential = _box.get(BNames.backblazeCredential);
_serverDomain = _box.get(BNames.serverDomain);
_serverDetails = _box.get(BNames.serverDetails);
_backblazeBucket = _box.get(BNames.backblazeBucket);
_serverType = _box.get(BNames.serverTypeIdentifier);
_serverProvider = _box.get(BNames.serverProvider);
_dnsProvider = _box.get(BNames.dnsProvider);
} }
} }

View file

@ -6,6 +6,7 @@ import 'package:pub_semver/pub_semver.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -246,14 +247,12 @@ class ApiConnectionRepository {
} }
ServerHostingDetails? get serverDetails => ServerHostingDetails? get serverDetails =>
getIt<ApiConfigModel>().serverDetails; getIt<ResourcesModel>().serverDetails;
ServerDomain? get serverDomain => getIt<ApiConfigModel>().serverDomain; ServerDomain? get serverDomain => getIt<ResourcesModel>().serverDomain;
void init() async { void init() async {
final serverDetails = getIt<ApiConfigModel>().serverDetails; final serverDetails = getIt<ResourcesModel>().serverDetails;
final hasFinalChecked = if (serverDetails == null) {
box.get(BNames.hasFinalChecked, defaultValue: false);
if (serverDetails == null || !hasFinalChecked) {
return; return;
} }
connectionStatus = ConnectionStatus.reconnecting; connectionStatus = ConnectionStatus.reconnecting;
@ -281,6 +280,12 @@ class ApiConnectionRepository {
); );
} }
void clear() async {
connectionStatus = ConnectionStatus.nonexistent;
_connectionStatusStream.add(connectionStatus);
_timer?.cancel();
}
Future<void> _refetchEverything(final Version version) async { Future<void> _refetchEverything(final Version version) async {
await _apiData.serverJobs await _apiData.serverJobs
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
@ -302,7 +307,7 @@ class ApiConnectionRepository {
} }
Future<void> reload(final Timer? timer) async { Future<void> reload(final Timer? timer) async {
final serverDetails = getIt<ApiConfigModel>().serverDetails; final serverDetails = getIt<ResourcesModel>().serverDetails;
if (serverDetails == null) { if (serverDetails == null) {
return; return;
} }

View file

@ -0,0 +1,342 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/dns_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
class ResourcesModel {
final Box _box = Hive.box(BNames.resourcesBox);
List<ServerProviderCredential> get serverProviderCredentials =>
_serverProviderTokens;
List<DnsProviderCredential> get dnsProviderCredentials => _dnsProviderTokens;
List<BackupsCredential> get backupsCredentials => _backupsCredentials;
List<Server> get servers => _servers;
BackblazeBucket? get backblazeBucket => _backblazeBucket;
List<ServerProviderCredential> _serverProviderTokens = [];
List<DnsProviderCredential> _dnsProviderTokens = [];
List<BackupsCredential> _backupsCredentials = [];
List<Server> _servers = [];
// TODO: As we will add support for other backup storages, we should
// refactor this.
BackblazeBucket? _backblazeBucket;
@Deprecated('Compatibility getter')
ServerHostingDetails? get serverDetails =>
_servers.firstOrNull?.hostingDetails;
@Deprecated('Compatibility getter')
String? get serverProviderKey => _serverProviderTokens.firstOrNull?.token;
@Deprecated('Compatibility getter')
String? get serverLocation =>
_servers.firstOrNull?.hostingDetails.serverLocation;
@Deprecated('Compatibility getter')
String? get serverType => _servers.firstOrNull?.hostingDetails.serverType;
@Deprecated('Compatibility getter')
String? get dnsProviderKey => _dnsProviderTokens.firstOrNull?.token;
@Deprecated('Compatibility getter')
ServerProviderType? get serverProvider =>
_serverProviderTokens.firstOrNull?.provider;
@Deprecated('Compatibility getter')
DnsProviderType? get dnsProvider => _dnsProviderTokens.firstOrNull?.provider;
@Deprecated('Compatibility getter')
BackupsCredential? get backblazeCredential => _backupsCredentials.firstOrNull;
@Deprecated('Compatibility getter')
ServerDomain? get serverDomain => _servers.firstOrNull?.domain;
Future<void> addServerProviderToken(
final ServerProviderCredential token,
) async {
_serverProviderTokens.add(token);
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
}
Future<void> associateServerWithToken(
final int serverId,
final String token,
) async {
_serverProviderTokens
.firstWhere(
(final credential) => credential.token == token,
)
.associatedServerIds
.add(serverId);
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
}
Future<void> removeServerProviderToken(
final ServerProviderCredential token,
) async {
_serverProviderTokens.remove(token);
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
}
Future<void> addDnsProviderToken(final DnsProviderCredential token) async {
// Check if this token already exists
if (_dnsProviderTokens
.any((final credential) => credential.token == token.token)) {
throw Exception('Token already exists');
}
_dnsProviderTokens.add(token);
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
}
Future<void> associateDomainWithToken(
final String domain,
final String token,
) async {
_dnsProviderTokens
.firstWhere(
(final credential) => credential.token == token,
)
.associatedDomainNames
.add(domain);
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
}
Future<void> removeDnsProviderToken(final DnsProviderCredential token) async {
_dnsProviderTokens.remove(token);
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
}
Future<void> addBackupsCredential(final BackupsCredential credential) async {
_backupsCredentials.add(credential);
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
}
Future<void> removeBackupsCredential(
final BackupsCredential credential,
) async {
_backupsCredentials.remove(credential);
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
}
Future<void> addServer(final Server server) async {
_servers.add(server);
await _box.put(BNames.servers, _servers);
}
Future<void> removeServer(final Server server) async {
_servers.remove(server);
await _box.put(BNames.servers, _servers);
}
Future<void> setBackblazeBucket(final BackblazeBucket bucket) async {
_backblazeBucket = bucket;
await _box.put(BNames.backblazeBucket, _backblazeBucket);
}
Future<void> removeBackblazeBucket() async {
_backblazeBucket = null;
await _box.delete(BNames.backblazeBucket);
}
void clear() {
_servers.clear();
_serverProviderTokens.clear();
_dnsProviderTokens.clear();
_backupsCredentials.clear();
_backblazeBucket = null;
_box.clear();
_box.compact();
}
void init() {
_serverProviderTokens = _box
.get(
BNames.serverProviderTokens,
defaultValue: <ServerProviderCredential>[],
)
.map<ServerProviderCredential>(
(final e) => e as ServerProviderCredential,
)
.toList();
_dnsProviderTokens = _box
.get(
BNames.dnsProviderTokens,
defaultValue: <DnsProviderCredential>[],
)
.map<DnsProviderCredential>((final e) => e as DnsProviderCredential)
.toList();
_backupsCredentials = _box
.get(
BNames.backupsProviderTokens,
defaultValue: <BackupsCredential>[],
)
.map<BackupsCredential>((final e) => e as BackupsCredential)
.toList();
_servers = _box
.get(
BNames.servers,
defaultValue: <Server>[],
)
.map<Server>((final e) => e as Server)
.toList();
_backblazeBucket = _box.get(BNames.backblazeBucket);
}
}
class WizardDataModel {
final Box _box = Hive.box(BNames.wizardDataBox);
ServerInstallationWizardData? get serverInstallation => _serverInstallation;
ServerInstallationWizardData? _serverInstallation;
Future<void> setServerProviderType(final ServerProviderType provider) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverProviderType: provider);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerProviderKey(final String key) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverProviderKey: key);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setDnsProviderType(final DnsProviderType provider) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(dnsProviderType: provider);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setDnsProviderKey(final String key) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(dnsProviderKey: key);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerTypeIdentifier(final String identifier) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverTypeIdentifier: identifier);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerLocation(final String location) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverLocation: location);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> moveServerTypeToServerDetails() async {
final details = _serverInstallation?.serverDetails;
if (details != null) {
if (_serverInstallation?.serverTypeIdentifier != null &&
_serverInstallation?.serverLocation != null) {
_serverInstallation = _serverInstallation?.copyWith(
serverDetails: () => details.copyWith(
serverType: _serverInstallation?.serverTypeIdentifier,
serverLocation: _serverInstallation?.serverLocation,
),
);
await _box.put(
BNames.serverInstallationWizardData,
_serverInstallation,
);
}
}
}
Future<void> setServerDetails(final ServerHostingDetails details) async {
final detailsWithServerType = details.copyWith(
serverLocation: _serverInstallation?.serverLocation,
serverType: _serverInstallation?.serverTypeIdentifier,
);
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDetails: () => detailsWithServerType);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> deleteServerDetails() async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDetails: () => null);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setBackupsCredential(final BackupsCredential credential) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(backupsCredential: credential);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerDomain(final ServerDomain domain) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDomain: () => domain);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> deleteServerDomain() async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDomain: () => null);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsServerStarted(final bool isStarted) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isServerStarted: isStarted);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsServerRebootedFirstTime(final bool isRebooted) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isServerResetedFirstTime: isRebooted);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsServerRebootedSecondTime(final bool isRebooted) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isServerResetedSecondTime: isRebooted);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setRootUser(final User user) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(rootUser: user);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsRecoveringServer(final bool isRecovering) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isRecoveringServer: isRecovering);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> clearServerInstallation() async {
_serverInstallation = null;
await _box.delete(BNames.serverInstallationWizardData);
}
Future<void> clear() async {
await _box.clear();
await _box.compact();
}
void init() {
_serverInstallation =
_box.get(BNames.serverInstallationWizardData, defaultValue: null);
}
}

View file

@ -3,13 +3,19 @@
1. User 1. User
2. ServerHostingDetails 2. ServerHostingDetails
3. ServerDomain 3. ServerDomain
4. BackblazeCredential 4. BackupsCredential
5. ServerVolume 5. ServerProviderVolume
6. BackblazeBucket 6. BackblazeBucket
7. ServerProviderCredential
8. DnsProviderCredential
9. Server
## Wizards store
60. ServerInstallationWizardData
## Enums ## Enums
100. DnsProvider 100. DnsProvider
101. ServerProvider 101. ServerProvider
102. UserType 102. UserType
103. BackupsProvider 103. BackupsProviderType

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.da
part 'backups_credential.g.dart'; part 'backups_credential.g.dart';
// TODO: Make a constant type.
@HiveType(typeId: 4) @HiveType(typeId: 4)
class BackupsCredential { class BackupsCredential {
BackupsCredential({ BackupsCredential({

View file

@ -0,0 +1,27 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
part 'dns_provider_credential.g.dart';
// TODO: Make a constant type.
@HiveType(typeId: 8)
class DnsProviderCredential {
DnsProviderCredential({
required this.tokenId,
required this.token,
required this.provider,
required this.associatedDomainNames,
});
@HiveField(0)
final String? tokenId;
@HiveField(1)
final String token;
@HiveField(2)
final DnsProviderType provider;
@HiveField(3)
final List<String> associatedDomainNames;
}

View file

@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'dns_provider_credential.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class DnsProviderCredentialAdapter extends TypeAdapter<DnsProviderCredential> {
@override
final int typeId = 8;
@override
DnsProviderCredential read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return DnsProviderCredential(
tokenId: fields[0] as String?,
token: fields[1] as String,
provider: fields[2] as DnsProviderType,
associatedDomainNames: (fields[3] as List).cast<String>(),
);
}
@override
void write(BinaryWriter writer, DnsProviderCredential obj) {
writer
..writeByte(4)
..writeByte(0)
..write(obj.tokenId)
..writeByte(1)
..write(obj.token)
..writeByte(2)
..write(obj.provider)
..writeByte(3)
..write(obj.associatedDomainNames);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is DnsProviderCredentialAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -0,0 +1,20 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
part 'server.g.dart';
// TODO: Make a constant type.
@HiveType(typeId: 9)
class Server {
Server({
required this.hostingDetails,
required this.domain,
});
@HiveField(0)
final ServerHostingDetails hostingDetails;
@HiveField(1)
final ServerDomain domain;
}

View file

@ -0,0 +1,44 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ServerAdapter extends TypeAdapter<Server> {
@override
final int typeId = 9;
@override
Server read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Server(
hostingDetails: fields[0] as ServerHostingDetails,
domain: fields[1] as ServerDomain,
);
}
@override
void write(BinaryWriter writer, Server obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.hostingDetails)
..writeByte(1)
..write(obj.domain);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServerAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -12,6 +12,8 @@ class ServerHostingDetails {
required this.volume, required this.volume,
required this.apiToken, required this.apiToken,
required this.provider, required this.provider,
this.serverLocation,
this.serverType,
this.startTime, this.startTime,
}); });
@ -21,12 +23,13 @@ class ServerHostingDetails {
@HiveField(1) @HiveField(1)
final int id; final int id;
@HiveField(3) // TODO: Check if it is still needed
final DateTime? createTime;
@HiveField(2) @HiveField(2)
final DateTime? startTime; final DateTime? startTime;
@HiveField(3)
final DateTime? createTime;
// TODO: Check if it is still needed // TODO: Check if it is still needed
@HiveField(4) @HiveField(4)
final ServerProviderVolume volume; final ServerProviderVolume volume;
@ -37,9 +40,21 @@ class ServerHostingDetails {
@HiveField(6, defaultValue: ServerProviderType.hetzner) @HiveField(6, defaultValue: ServerProviderType.hetzner)
final ServerProviderType provider; final ServerProviderType provider;
ServerHostingDetails copyWith({final DateTime? startTime}) => @HiveField(7)
final String? serverLocation;
@HiveField(8)
final String? serverType;
ServerHostingDetails copyWith({
final DateTime? startTime,
final String? serverLocation,
final String? serverType,
}) =>
ServerHostingDetails( ServerHostingDetails(
startTime: startTime ?? this.startTime, startTime: startTime ?? this.startTime,
serverLocation: serverLocation ?? this.serverLocation,
serverType: serverType ?? this.serverType,
createTime: createTime, createTime: createTime,
id: id, id: id,
ip4: ip4, ip4: ip4,
@ -103,3 +118,7 @@ enum ServerProviderType {
unknown => 'Unknown', unknown => 'Unknown',
}; };
} }
extension ServerProviderTypeIsSpecified on ServerProviderType? {
bool get isSpecified => this != null && this != ServerProviderType.unknown;
}

View file

@ -25,6 +25,8 @@ class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
provider: fields[6] == null provider: fields[6] == null
? ServerProviderType.hetzner ? ServerProviderType.hetzner
: fields[6] as ServerProviderType, : fields[6] as ServerProviderType,
serverLocation: fields[7] as String?,
serverType: fields[8] as String?,
startTime: fields[2] as DateTime?, startTime: fields[2] as DateTime?,
); );
} }
@ -32,21 +34,25 @@ class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
@override @override
void write(BinaryWriter writer, ServerHostingDetails obj) { void write(BinaryWriter writer, ServerHostingDetails obj) {
writer writer
..writeByte(7) ..writeByte(9)
..writeByte(0) ..writeByte(0)
..write(obj.ip4) ..write(obj.ip4)
..writeByte(1) ..writeByte(1)
..write(obj.id) ..write(obj.id)
..writeByte(3)
..write(obj.createTime)
..writeByte(2) ..writeByte(2)
..write(obj.startTime) ..write(obj.startTime)
..writeByte(3)
..write(obj.createTime)
..writeByte(4) ..writeByte(4)
..write(obj.volume) ..write(obj.volume)
..writeByte(5) ..writeByte(5)
..write(obj.apiToken) ..write(obj.apiToken)
..writeByte(6) ..writeByte(6)
..write(obj.provider); ..write(obj.provider)
..writeByte(7)
..write(obj.serverLocation)
..writeByte(8)
..write(obj.serverType);
} }
@override @override

View file

@ -57,3 +57,7 @@ enum DnsProviderType {
unknown => 'Unknown', unknown => 'Unknown',
}; };
} }
extension DnsProviderTypeIsSpecified on DnsProviderType? {
bool get isSpecified => this != null && this != DnsProviderType.unknown;
}

View file

@ -0,0 +1,27 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
part 'server_provider_credential.g.dart';
// TODO: Make a constant type.
@HiveType(typeId: 7)
class ServerProviderCredential {
ServerProviderCredential({
required this.tokenId,
required this.token,
required this.provider,
required this.associatedServerIds,
});
@HiveField(0)
final String? tokenId;
@HiveField(1)
final String token;
@HiveField(2)
final ServerProviderType provider;
@HiveField(3)
final List<int> associatedServerIds;
}

View file

@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_provider_credential.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ServerProviderCredentialAdapter
extends TypeAdapter<ServerProviderCredential> {
@override
final int typeId = 7;
@override
ServerProviderCredential read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ServerProviderCredential(
tokenId: fields[0] as String?,
token: fields[1] as String,
provider: fields[2] as ServerProviderType,
associatedServerIds: (fields[3] as List).cast<int>(),
);
}
@override
void write(BinaryWriter writer, ServerProviderCredential obj) {
writer
..writeByte(4)
..writeByte(0)
..write(obj.tokenId)
..writeByte(1)
..write(obj.token)
..writeByte(2)
..write(obj.provider)
..writeByte(3)
..write(obj.associatedServerIds);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServerProviderCredentialAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -0,0 +1,130 @@
import 'package:flutter/cupertino.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
part 'server_installation_wizard_data.g.dart';
@HiveType(typeId: 60)
class ServerInstallationWizardData {
const ServerInstallationWizardData({
required this.hasFinalChecked,
required this.isServerStarted,
required this.isServerResetedFirstTime,
required this.isServerResetedSecondTime,
required this.isLoading,
required this.isRecoveringServer,
required this.rootUser,
required this.serverProviderType,
required this.serverProviderKey,
required this.dnsProviderType,
required this.dnsProviderKey,
required this.backupsCredential,
required this.serverTypeIdentifier,
required this.serverLocation,
required this.serverDetails,
required this.serverDomain,
});
ServerInstallationWizardData.empty()
: this(
hasFinalChecked: false,
isServerStarted: false,
isServerResetedFirstTime: false,
isServerResetedSecondTime: false,
isLoading: false,
isRecoveringServer: false,
rootUser: null,
serverProviderType: null,
serverProviderKey: null,
dnsProviderType: null,
dnsProviderKey: null,
backupsCredential: null,
serverTypeIdentifier: null,
serverLocation: null,
serverDetails: null,
serverDomain: null,
);
// Bool flags used by installer
@HiveField(0)
final bool hasFinalChecked;
@HiveField(1)
final bool isServerStarted;
@HiveField(2)
final bool isServerResetedFirstTime;
@HiveField(3)
final bool isServerResetedSecondTime;
@HiveField(4)
final bool isLoading;
@HiveField(5)
final bool isRecoveringServer;
@HiveField(6)
final User? rootUser;
@HiveField(7)
final ServerProviderType? serverProviderType;
@HiveField(8)
final String? serverProviderKey;
@HiveField(9)
final DnsProviderType? dnsProviderType;
@HiveField(10)
final String? dnsProviderKey;
@HiveField(11)
final BackupsCredential? backupsCredential;
@HiveField(12)
final String? serverTypeIdentifier;
@HiveField(13)
final String? serverLocation;
@HiveField(14)
final ServerHostingDetails? serverDetails;
@HiveField(15)
final ServerDomain? serverDomain;
ServerInstallationWizardData copyWith({
final bool? hasFinalChecked,
final bool? isServerStarted,
final bool? isServerResetedFirstTime,
final bool? isServerResetedSecondTime,
final bool? isLoading,
final bool? isRecoveringServer,
final User? rootUser,
final ServerProviderType? serverProviderType,
final String? serverProviderKey,
final DnsProviderType? dnsProviderType,
final String? dnsProviderKey,
final BackupsCredential? backupsCredential,
final String? serverTypeIdentifier,
final String? serverLocation,
final ValueGetter<ServerHostingDetails?>? serverDetails,
final ValueGetter<ServerDomain?>? serverDomain,
}) =>
ServerInstallationWizardData(
hasFinalChecked: hasFinalChecked ?? this.hasFinalChecked,
isServerStarted: isServerStarted ?? this.isServerStarted,
isServerResetedFirstTime:
isServerResetedFirstTime ?? this.isServerResetedFirstTime,
isServerResetedSecondTime:
isServerResetedSecondTime ?? this.isServerResetedSecondTime,
isLoading: isLoading ?? this.isLoading,
isRecoveringServer: isRecoveringServer ?? this.isRecoveringServer,
rootUser: rootUser ?? this.rootUser,
serverProviderType: serverProviderType ?? this.serverProviderType,
serverProviderKey: serverProviderKey ?? this.serverProviderKey,
dnsProviderType: dnsProviderType ?? this.dnsProviderType,
dnsProviderKey: dnsProviderKey ?? this.dnsProviderKey,
backupsCredential: backupsCredential ?? this.backupsCredential,
serverTypeIdentifier: serverTypeIdentifier ?? this.serverTypeIdentifier,
serverLocation: serverLocation ?? this.serverLocation,
serverDetails:
serverDetails != null ? serverDetails() : this.serverDetails,
serverDomain: serverDomain != null ? serverDomain() : this.serverDomain,
);
}

View file

@ -0,0 +1,87 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_installation_wizard_data.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ServerInstallationWizardDataAdapter
extends TypeAdapter<ServerInstallationWizardData> {
@override
final int typeId = 60;
@override
ServerInstallationWizardData read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ServerInstallationWizardData(
hasFinalChecked: fields[0] as bool,
isServerStarted: fields[1] as bool,
isServerResetedFirstTime: fields[2] as bool,
isServerResetedSecondTime: fields[3] as bool,
isLoading: fields[4] as bool,
isRecoveringServer: fields[5] as bool,
rootUser: fields[6] as User?,
serverProviderType: fields[7] as ServerProviderType?,
serverProviderKey: fields[8] as String?,
dnsProviderType: fields[9] as DnsProviderType?,
dnsProviderKey: fields[10] as String?,
backupsCredential: fields[11] as BackupsCredential?,
serverTypeIdentifier: fields[12] as String?,
serverLocation: fields[13] as String?,
serverDetails: fields[14] as ServerHostingDetails?,
serverDomain: fields[15] as ServerDomain?,
);
}
@override
void write(BinaryWriter writer, ServerInstallationWizardData obj) {
writer
..writeByte(16)
..writeByte(0)
..write(obj.hasFinalChecked)
..writeByte(1)
..write(obj.isServerStarted)
..writeByte(2)
..write(obj.isServerResetedFirstTime)
..writeByte(3)
..write(obj.isServerResetedSecondTime)
..writeByte(4)
..write(obj.isLoading)
..writeByte(5)
..write(obj.isRecoveringServer)
..writeByte(6)
..write(obj.rootUser)
..writeByte(7)
..write(obj.serverProviderType)
..writeByte(8)
..write(obj.serverProviderKey)
..writeByte(9)
..write(obj.dnsProviderType)
..writeByte(10)
..write(obj.dnsProviderKey)
..writeByte(11)
..write(obj.backupsCredential)
..writeByte(12)
..write(obj.serverTypeIdentifier)
..writeByte(13)
..write(obj.serverLocation)
..writeByte(14)
..write(obj.serverDetails)
..writeByte(15)
..write(obj.serverDomain);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServerInstallationWizardDataAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -11,7 +11,7 @@ class Price {
enum CurrencyType { enum CurrencyType {
eur, eur,
usd, usd,
unkown, unknown,
} }
class Currency { class Currency {

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/cards/filled_card.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
@ -86,7 +87,7 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
final bool isReady = context.watch<ServerInstallationCubit>().state final bool isReady = context.watch<ServerInstallationCubit>().state
is ServerInstallationFinished; is ServerInstallationFinished;
final String domain = final String domain =
getIt<ApiConfigModel>().serverDomain?.domainName ?? ''; getIt<ResourcesModel>().serverDomain?.domainName ?? '';
final DnsRecordsState dnsCubit = context.watch<DnsRecordsCubit>().state; final DnsRecordsState dnsCubit = context.watch<DnsRecordsCubit>().state;
print(dnsCubit.dnsState); print(dnsCubit.dnsState);

View file

@ -1,7 +1,6 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/console_log.dart'; import 'package:selfprivacy/logic/models/console_log.dart';
import 'package:selfprivacy/ui/pages/more/console/console_log_item_widget.dart'; import 'package:selfprivacy/ui/pages/more/console/console_log_item_widget.dart';
@ -18,21 +17,15 @@ class ConsolePage extends StatefulWidget {
class _ConsolePageState extends State<ConsolePage> { class _ConsolePageState extends State<ConsolePage> {
ConsoleModel get console => getIt<ConsoleModel>(); ConsoleModel get console => getIt<ConsoleModel>();
/// should freeze logs state to properly read logs
late final Future<void> future;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
future = getIt.allReady();
console.addListener(update); console.addListener(update);
} }
@override @override
void dispose() { void dispose() {
console.removeListener(update); console.removeListener(update);
super.dispose(); super.dispose();
} }
@ -48,66 +41,36 @@ class _ConsolePageState extends State<ConsolePage> {
} }
@override @override
Widget build(final BuildContext context) => SafeArea( Widget build(final BuildContext context) {
child: Scaffold( final List<ConsoleLog> logs = console.logs;
appBar: AppBar(
title: Text('console_page.title'.tr()), return SafeArea(
leading: IconButton( child: Scaffold(
icon: const Icon(Icons.arrow_back), appBar: AppBar(
onPressed: () => Navigator.of(context).maybePop(), title: Text('console_page.title'.tr()),
), leading: IconButton(
actions: [ icon: const Icon(Icons.arrow_back),
IconButton( onPressed: () => Navigator.of(context).maybePop(),
icon: Icon( ),
console.paused actions: [
? Icons.play_arrow_outlined IconButton(
: Icons.pause_outlined, icon: Icon(
), console.paused
onPressed: console.paused ? console.play : console.pause, ? Icons.play_arrow_outlined
: Icons.pause_outlined,
), ),
], onPressed: console.paused ? console.play : console.pause,
),
body: Scrollbar(
child: FutureBuilder(
future: future,
builder: (
final BuildContext context,
final AsyncSnapshot<void> snapshot,
) {
if (snapshot.hasData) {
final List<ConsoleLog> logs = console.logs;
return logs.isEmpty
? const _ConsoleViewEmpty()
: _ConsoleViewLoaded(logs: logs);
}
return const _ConsoleViewLoading();
},
), ),
), ],
), ),
); body: Scrollbar(
} child: logs.isEmpty
? const _ConsoleViewEmpty()
class _ConsoleViewLoading extends StatelessWidget { : _ConsoleViewLoaded(logs: logs),
const _ConsoleViewLoading(); ),
),
@override );
Widget build(final BuildContext context) => Column( }
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('console_page.waiting'.tr()),
const Gap(16),
const Expanded(
child: Center(
child: CircularProgressIndicator.adaptive(),
),
),
],
);
} }
class _ConsoleViewEmpty extends StatelessWidget { class _ConsoleViewEmpty extends StatelessWidget {

View file

@ -112,7 +112,7 @@ class InitializingPage extends StatelessWidget {
'Server', 'Server',
'Installation', 'Installation',
], ],
activeIndex: cubit.state.porgressBar, activeIndex: cubit.state.progressBar,
), ),
), ),
), ),

28
lib/utils/app_logger.dart Normal file
View file

@ -0,0 +1,28 @@
import 'dart:developer' as developer;
import 'package:selfprivacy/config/config.dart';
class AppLogger {
const AppLogger({required this.name});
final String name;
// TODO: research other possible options, which could support both
// throttling and output formatting
void log(
final String message, {
final Object? error,
final StackTrace? stackTrace,
}) {
if (config.shouldDebugPrint) {
// TODO: could probably add UI logging for console_page
developer.log(
message,
error: error,
stackTrace: stackTrace,
time: DateTime.now(),
name: name,
);
}
}
}

View file

@ -0,0 +1,48 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:selfprivacy/utils/app_logger.dart';
class SecureStorage {
static final log = const AppLogger(name: 'secure_storage').log;
static const FlutterSecureStorage secureStorage = FlutterSecureStorage();
static String keyName = 'key';
static Future<Uint8List?> getKey() async {
try {
final bool hasEncryptionKey =
await secureStorage.containsKey(key: keyName);
if (!hasEncryptionKey) {
return null;
}
final String? string = await secureStorage.read(key: keyName);
log('successfully got encryption key: $string');
return base64Url.decode(string!);
} catch (error, stackTrace) {
log(
'error reading encryption key',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
static Future<void> setKey(final List<int> key) async {
try {
final value = base64UrlEncode(key);
await secureStorage.write(key: keyName, value: value);
log('successfully saved encryption key: $value');
} catch (error, stackTrace) {
log(
'error saving new encryption key',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
}

View file

@ -9,6 +9,7 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h> #include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_auth_windows/local_auth_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View file

@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus connectivity_plus
dynamic_color dynamic_color
flutter_secure_storage_windows flutter_secure_storage_windows
local_auth_windows
url_launcher_windows url_launcher_windows
) )