diff --git a/assets/translations/en.json b/assets/translations/en.json index 18e70c5b..de30d6f0 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -273,6 +273,7 @@ "place_where_data": "A place where your data and SelfPrivacy services will reside:", "how": "How to obtain API token", "provider_bad_key_error": "Provider API key is invalid", + "could_not_connect": "Counldn't connect to the provider, please check your connection.", "choose_location_type": "Choose your server location and type:", "back_to_locations": "Go back to available locations!", "no_locations_found": "No available locations found. Make sure your account is accessible.", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 3dd4e590..b1228fa1 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -272,6 +272,7 @@ "place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", "how": "Как получить API Token", "provider_bad_key_error": "API ключ провайдера неверен", + "could_not_connect": "Не удалось соединиться с провайдером. Пожалуйста, проверьте подключение.", "choose_location_type": "Выберите локацию и тип вашего сервера:", "back_to_locations": "Назад к доступным локациям!", "no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.", diff --git a/lib/logic/api_maps/api_generic_result.dart b/lib/logic/api_maps/api_generic_result.dart new file mode 100644 index 00000000..b5bacfb6 --- /dev/null +++ b/lib/logic/api_maps/api_generic_result.dart @@ -0,0 +1,13 @@ +class APIGenericResult { + APIGenericResult({ + required this.success, + required this.data, + this.message, + }); + + /// Whether was a response successfully received, + /// doesn't represent success of the request if `data` is `bool` + final bool success; + final String? message; + final T data; +} diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index 4370131f..5216ffa8 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -1,5 +1,6 @@ import 'package:graphql/client.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/api_generic_result.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/api_map.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; @@ -22,27 +23,15 @@ import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/ssh_settings.dart'; import 'package:selfprivacy/logic/models/system_settings.dart'; +export 'package:selfprivacy/logic/api_maps/api_generic_result.dart'; + part 'jobs_api.dart'; part 'server_actions_api.dart'; part 'services_api.dart'; part 'users_api.dart'; part 'volume_api.dart'; -class GenericResult { - GenericResult({ - required this.success, - required this.data, - this.message, - }); - - /// Whether was a response successfully received, - /// doesn't represent success of the request if `data` is `bool` - final bool success; - final String? message; - final T data; -} - -class GenericMutationResult extends GenericResult { +class GenericMutationResult extends APIGenericResult { GenericMutationResult({ required super.success, required this.code, @@ -206,7 +195,7 @@ class ServerApi extends ApiMap return settings; } - Future> getRecoveryTokenStatus() async { + Future> getRecoveryTokenStatus() async { RecoveryKeyStatus? key; QueryResult response; String? error; @@ -223,18 +212,18 @@ class ServerApi extends ApiMap print(e); } - return GenericResult( + return APIGenericResult( success: error == null, data: key, message: error, ); } - Future> generateRecoveryToken( + Future> generateRecoveryToken( final DateTime? expirationDate, final int? numberOfUses, ) async { - GenericResult key; + APIGenericResult key; QueryResult response; try { @@ -255,19 +244,19 @@ class ServerApi extends ApiMap ); if (response.hasException) { print(response.exception.toString()); - key = GenericResult( + key = APIGenericResult( success: false, data: '', message: response.exception.toString(), ); } - key = GenericResult( + key = APIGenericResult( success: true, data: response.parsedData!.getNewRecoveryApiKey.key!, ); } catch (e) { print(e); - key = GenericResult( + key = APIGenericResult( success: false, data: '', message: e.toString(), @@ -300,8 +289,8 @@ class ServerApi extends ApiMap return records; } - Future>> getApiTokens() async { - GenericResult> tokens; + Future>> getApiTokens() async { + APIGenericResult> tokens; QueryResult response; try { @@ -310,7 +299,7 @@ class ServerApi extends ApiMap if (response.hasException) { final message = response.exception.toString(); print(message); - tokens = GenericResult>( + tokens = APIGenericResult>( success: false, data: [], message: message, @@ -324,13 +313,13 @@ class ServerApi extends ApiMap ApiToken.fromGraphQL(device), ) .toList(); - tokens = GenericResult>( + tokens = APIGenericResult>( success: true, data: parsed, ); } catch (e) { print(e); - tokens = GenericResult>( + tokens = APIGenericResult>( success: false, data: [], message: e.toString(), @@ -340,8 +329,8 @@ class ServerApi extends ApiMap return tokens; } - Future> deleteApiToken(final String name) async { - GenericResult returnable; + Future> deleteApiToken(final String name) async { + APIGenericResult returnable; QueryResult response; try { @@ -358,19 +347,19 @@ class ServerApi extends ApiMap ); if (response.hasException) { print(response.exception.toString()); - returnable = GenericResult( + returnable = APIGenericResult( success: false, data: null, message: response.exception.toString(), ); } - returnable = GenericResult( + returnable = APIGenericResult( success: true, data: null, ); } catch (e) { print(e); - returnable = GenericResult( + returnable = APIGenericResult( success: false, data: null, message: e.toString(), @@ -380,8 +369,8 @@ class ServerApi extends ApiMap return returnable; } - Future> createDeviceToken() async { - GenericResult token; + Future> createDeviceToken() async { + APIGenericResult token; QueryResult response; try { @@ -393,19 +382,19 @@ class ServerApi extends ApiMap ); if (response.hasException) { print(response.exception.toString()); - token = GenericResult( + token = APIGenericResult( success: false, data: '', message: response.exception.toString(), ); } - token = GenericResult( + token = APIGenericResult( success: true, data: response.parsedData!.getNewDeviceApiKey.key!, ); } catch (e) { print(e); - token = GenericResult( + token = APIGenericResult( success: false, data: '', message: e.toString(), @@ -417,10 +406,10 @@ class ServerApi extends ApiMap Future isHttpServerWorking() async => (await getApiVersion()) != null; - Future> authorizeDevice( + Future> authorizeDevice( final DeviceToken deviceToken, ) async { - GenericResult token; + APIGenericResult token; QueryResult response; try { @@ -442,19 +431,19 @@ class ServerApi extends ApiMap ); if (response.hasException) { print(response.exception.toString()); - token = GenericResult( + token = APIGenericResult( success: false, data: '', message: response.exception.toString(), ); } - token = GenericResult( + token = APIGenericResult( success: true, data: response.parsedData!.authorizeWithNewDeviceApiKey.token!, ); } catch (e) { print(e); - token = GenericResult( + token = APIGenericResult( success: false, data: '', message: e.toString(), @@ -464,10 +453,10 @@ class ServerApi extends ApiMap return token; } - Future> useRecoveryToken( + Future> useRecoveryToken( final DeviceToken deviceToken, ) async { - GenericResult token; + APIGenericResult token; QueryResult response; try { @@ -489,19 +478,19 @@ class ServerApi extends ApiMap ); if (response.hasException) { print(response.exception.toString()); - token = GenericResult( + token = APIGenericResult( success: false, data: '', message: response.exception.toString(), ); } - token = GenericResult( + token = APIGenericResult( success: true, data: response.parsedData!.useRecoveryApiKey.token!, ); } catch (e) { print(e); - token = GenericResult( + token = APIGenericResult( success: false, data: '', message: e.toString(), diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index ba3f0d63..b97018e2 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -59,35 +59,50 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { String get displayProviderName => 'Digital Ocean'; @override - Future isApiTokenValid(final String token) async { + Future> isApiTokenValid(final String token) async { bool isValid = false; Response? response; + String message = ''; final Dio client = await getClient(); try { response = await client.get( '/account', options: Options( + followRedirects: false, + validateStatus: (final status) => + status != null && (status >= 200 || status == 401), headers: {'Authorization': 'Bearer $token'}, ), ); } catch (e) { print(e); isValid = false; + message = e.toString(); } finally { close(client); } - if (response != null) { - if (response.statusCode == HttpStatus.ok) { - isValid = true; - } else if (response.statusCode == HttpStatus.unauthorized) { - isValid = false; - } else { - throw Exception('code: ${response.statusCode}'); - } + if (response == null) { + return APIGenericResult( + data: isValid, + success: false, + message: message, + ); } - return isValid; + if (response.statusCode == HttpStatus.ok) { + isValid = true; + } else if (response.statusCode == HttpStatus.unauthorized) { + isValid = false; + } else { + throw Exception('code: ${response.statusCode}'); + } + + return APIGenericResult( + data: isValid, + success: true, + message: response.statusMessage, + ); } /// Hardcoded on their documentation and there is no pricing API at all diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 57df7837..87331d85 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -60,35 +60,50 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { String get displayProviderName => 'Hetzner'; @override - Future isApiTokenValid(final String token) async { + Future> isApiTokenValid(final String token) async { bool isValid = false; Response? response; + String message = ''; final Dio client = await getClient(); try { response = await client.get( '/servers', options: Options( + followRedirects: false, + validateStatus: (final status) => + status != null && (status >= 200 || status == 401), headers: {'Authorization': 'Bearer $token'}, ), ); } catch (e) { print(e); isValid = false; + message = e.toString(); } finally { close(client); } - if (response != null) { - if (response.statusCode == HttpStatus.ok) { - isValid = true; - } else if (response.statusCode == HttpStatus.unauthorized) { - isValid = false; - } else { - throw Exception('code: ${response.statusCode}'); - } + if (response == null) { + return APIGenericResult( + data: isValid, + success: false, + message: message, + ); } - return isValid; + if (response.statusCode == HttpStatus.ok) { + isValid = true; + } else if (response.statusCode == HttpStatus.unauthorized) { + isValid = false; + } else { + throw Exception('code: ${response.statusCode}'); + } + + return APIGenericResult( + data: isValid, + success: true, + message: response.statusMessage, + ); } @override diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index a2eb71f3..45e2bd2e 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -1,3 +1,4 @@ +import 'package:selfprivacy/logic/api_maps/api_generic_result.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; @@ -8,6 +9,8 @@ import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/logic/models/server_type.dart'; +export 'package:selfprivacy/logic/api_maps/api_generic_result.dart'; + class ProviderApiTokenValidation { ProviderApiTokenValidation({ required this.length, @@ -39,7 +42,7 @@ abstract class ServerProviderApi extends ApiMap { required final ServerDomain domain, }); - Future isApiTokenValid(final String token); + Future> isApiTokenValid(final String token); ProviderApiTokenValidation getApiTokenValidation(); Future> getMetadata(final int serverId); Future getMetrics( diff --git a/lib/logic/cubit/devices/devices_cubit.dart b/lib/logic/cubit/devices/devices_cubit.dart index 10ad943d..5e5c145c 100644 --- a/lib/logic/cubit/devices/devices_cubit.dart +++ b/lib/logic/cubit/devices/devices_cubit.dart @@ -35,7 +35,7 @@ class ApiDevicesCubit } Future?> _getApiTokens() async { - final GenericResult> response = await api.getApiTokens(); + final APIGenericResult> response = await api.getApiTokens(); if (response.success) { return response.data; } else { @@ -44,7 +44,8 @@ class ApiDevicesCubit } Future deleteDevice(final ApiToken device) async { - final GenericResult response = await api.deleteApiToken(device.name); + final APIGenericResult response = + await api.deleteApiToken(device.name); if (response.success) { emit( ApiDevicesState( @@ -59,7 +60,7 @@ class ApiDevicesCubit } Future getNewDeviceKey() async { - final GenericResult response = await api.createDeviceToken(); + final APIGenericResult response = await api.createDeviceToken(); if (response.success) { return response.data; } else { diff --git a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart index d3307762..ebabb5e7 100644 --- a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart @@ -29,21 +29,24 @@ class ProviderFormCubit extends FormCubit { @override FutureOr asyncValidation() async { - late bool isKeyValid; + bool? isKeyValid; try { isKeyValid = await serverInstallationCubit .isServerProviderApiTokenValid(apiKey.state.value); } catch (e) { addError(e); - isKeyValid = false; + } + + if (isKeyValid == null) { + apiKey.setError(''); + return false; } if (!isKeyValid) { apiKey.setError('initializing.provider_bad_key_error'.tr()); - return false; } - return true; + return isKeyValid; } } diff --git a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart index 76a572d1..56800be3 100644 --- a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart +++ b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart @@ -32,7 +32,7 @@ class RecoveryKeyCubit } Future _getRecoveryKeyStatus() async { - final GenericResult response = + final APIGenericResult response = await api.getRecoveryTokenStatus(); if (response.success) { return response.data; @@ -57,7 +57,7 @@ class RecoveryKeyCubit final DateTime? expirationDate, final int? numberOfUses, }) async { - final GenericResult response = + final APIGenericResult response = await api.generateRecoveryToken(expirationDate, numberOfUses); if (response.success) { refresh(); diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 08852825..dd9dadbd 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -76,16 +76,27 @@ class ServerInstallationCubit extends Cubit { .getDnsProvider() .getApiTokenValidation(); - Future isServerProviderApiTokenValid( + Future isServerProviderApiTokenValid( final String providerToken, - ) async => - ApiController.currentServerProviderApiFactory! - .getServerProvider( - settings: const ServerProviderApiSettings( - isWithToken: false, - ), - ) - .isApiTokenValid(providerToken); + ) async { + final APIGenericResult apiResponse = + await ApiController.currentServerProviderApiFactory! + .getServerProvider( + settings: const ServerProviderApiSettings( + isWithToken: false, + ), + ) + .isApiTokenValid(providerToken); + + if (!apiResponse.success) { + getIt().showSnackBar( + 'initializing.could_not_connect'.tr(), + ); + return null; + } + + return apiResponse.data; + } Future isDnsProviderApiTokenValid( final String providerToken, diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index cc3860e3..8365529a 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -479,7 +479,7 @@ class ServerInstallationRepository { overrideDomain: serverDomain.domainName, ); final String serverIp = await getServerIpFromDomain(serverDomain); - final GenericResult result = await serverApi.authorizeDevice( + final APIGenericResult result = await serverApi.authorizeDevice( DeviceToken(device: await getDeviceName(), token: newDeviceKey), ); @@ -516,7 +516,7 @@ class ServerInstallationRepository { overrideDomain: serverDomain.domainName, ); final String serverIp = await getServerIpFromDomain(serverDomain); - final GenericResult result = await serverApi.useRecoveryToken( + final APIGenericResult result = await serverApi.useRecoveryToken( DeviceToken(device: await getDeviceName(), token: recoveryKey), ); @@ -577,9 +577,9 @@ class ServerInstallationRepository { ); } } - final GenericResult deviceAuthKey = + final APIGenericResult deviceAuthKey = await serverApi.createDeviceToken(); - final GenericResult result = await serverApi.authorizeDevice( + final APIGenericResult result = await serverApi.authorizeDevice( DeviceToken(device: await getDeviceName(), token: deviceAuthKey.data), );