feat: Implement distinction for connection errors on initialing page

Now it's 'false' when api token is invalid and null response if couldn't connect at all, to show different kinds of errors to the user
pull/149/head
NaiJi ✨ 2022-11-28 22:51:37 +04:00
parent 58ce0f0f8b
commit bd33b8d679
12 changed files with 142 additions and 90 deletions

View File

@ -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.",

View File

@ -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": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.",

View File

@ -0,0 +1,13 @@
class APIGenericResult<T> {
APIGenericResult({
required this.success,
required this.data,
this.message,
});
/// Whether was a response successfully received,
/// doesn't represent success of the request if `data<T>` is `bool`
final bool success;
final String? message;
final T data;
}

View File

@ -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<T> {
GenericResult({
required this.success,
required this.data,
this.message,
});
/// Whether was a response successfully received,
/// doesn't represent success of the request if `data<T>` is `bool`
final bool success;
final String? message;
final T data;
}
class GenericMutationResult<T> extends GenericResult<T> {
class GenericMutationResult<T> extends APIGenericResult<T> {
GenericMutationResult({
required super.success,
required this.code,
@ -206,7 +195,7 @@ class ServerApi extends ApiMap
return settings;
}
Future<GenericResult<RecoveryKeyStatus?>> getRecoveryTokenStatus() async {
Future<APIGenericResult<RecoveryKeyStatus?>> getRecoveryTokenStatus() async {
RecoveryKeyStatus? key;
QueryResult<Query$RecoveryKey> response;
String? error;
@ -223,18 +212,18 @@ class ServerApi extends ApiMap
print(e);
}
return GenericResult<RecoveryKeyStatus?>(
return APIGenericResult<RecoveryKeyStatus?>(
success: error == null,
data: key,
message: error,
);
}
Future<GenericResult<String>> generateRecoveryToken(
Future<APIGenericResult<String>> generateRecoveryToken(
final DateTime? expirationDate,
final int? numberOfUses,
) async {
GenericResult<String> key;
APIGenericResult<String> key;
QueryResult<Mutation$GetNewRecoveryApiKey> response;
try {
@ -255,19 +244,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
key = GenericResult<String>(
key = APIGenericResult<String>(
success: false,
data: '',
message: response.exception.toString(),
);
}
key = GenericResult<String>(
key = APIGenericResult<String>(
success: true,
data: response.parsedData!.getNewRecoveryApiKey.key!,
);
} catch (e) {
print(e);
key = GenericResult<String>(
key = APIGenericResult<String>(
success: false,
data: '',
message: e.toString(),
@ -300,8 +289,8 @@ class ServerApi extends ApiMap
return records;
}
Future<GenericResult<List<ApiToken>>> getApiTokens() async {
GenericResult<List<ApiToken>> tokens;
Future<APIGenericResult<List<ApiToken>>> getApiTokens() async {
APIGenericResult<List<ApiToken>> tokens;
QueryResult<Query$GetApiTokens> response;
try {
@ -310,7 +299,7 @@ class ServerApi extends ApiMap
if (response.hasException) {
final message = response.exception.toString();
print(message);
tokens = GenericResult<List<ApiToken>>(
tokens = APIGenericResult<List<ApiToken>>(
success: false,
data: [],
message: message,
@ -324,13 +313,13 @@ class ServerApi extends ApiMap
ApiToken.fromGraphQL(device),
)
.toList();
tokens = GenericResult<List<ApiToken>>(
tokens = APIGenericResult<List<ApiToken>>(
success: true,
data: parsed,
);
} catch (e) {
print(e);
tokens = GenericResult<List<ApiToken>>(
tokens = APIGenericResult<List<ApiToken>>(
success: false,
data: [],
message: e.toString(),
@ -340,8 +329,8 @@ class ServerApi extends ApiMap
return tokens;
}
Future<GenericResult<void>> deleteApiToken(final String name) async {
GenericResult<void> returnable;
Future<APIGenericResult<void>> deleteApiToken(final String name) async {
APIGenericResult<void> returnable;
QueryResult<Mutation$DeleteDeviceApiToken> response;
try {
@ -358,19 +347,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
returnable = GenericResult<void>(
returnable = APIGenericResult<void>(
success: false,
data: null,
message: response.exception.toString(),
);
}
returnable = GenericResult<void>(
returnable = APIGenericResult<void>(
success: true,
data: null,
);
} catch (e) {
print(e);
returnable = GenericResult<void>(
returnable = APIGenericResult<void>(
success: false,
data: null,
message: e.toString(),
@ -380,8 +369,8 @@ class ServerApi extends ApiMap
return returnable;
}
Future<GenericResult<String>> createDeviceToken() async {
GenericResult<String> token;
Future<APIGenericResult<String>> createDeviceToken() async {
APIGenericResult<String> token;
QueryResult<Mutation$GetNewDeviceApiKey> response;
try {
@ -393,19 +382,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
token = GenericResult<String>(
token = APIGenericResult<String>(
success: false,
data: '',
message: response.exception.toString(),
);
}
token = GenericResult<String>(
token = APIGenericResult<String>(
success: true,
data: response.parsedData!.getNewDeviceApiKey.key!,
);
} catch (e) {
print(e);
token = GenericResult<String>(
token = APIGenericResult<String>(
success: false,
data: '',
message: e.toString(),
@ -417,10 +406,10 @@ class ServerApi extends ApiMap
Future<bool> isHttpServerWorking() async => (await getApiVersion()) != null;
Future<GenericResult<String>> authorizeDevice(
Future<APIGenericResult<String>> authorizeDevice(
final DeviceToken deviceToken,
) async {
GenericResult<String> token;
APIGenericResult<String> token;
QueryResult<Mutation$AuthorizeWithNewDeviceApiKey> response;
try {
@ -442,19 +431,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
token = GenericResult<String>(
token = APIGenericResult<String>(
success: false,
data: '',
message: response.exception.toString(),
);
}
token = GenericResult<String>(
token = APIGenericResult<String>(
success: true,
data: response.parsedData!.authorizeWithNewDeviceApiKey.token!,
);
} catch (e) {
print(e);
token = GenericResult<String>(
token = APIGenericResult<String>(
success: false,
data: '',
message: e.toString(),
@ -464,10 +453,10 @@ class ServerApi extends ApiMap
return token;
}
Future<GenericResult<String>> useRecoveryToken(
Future<APIGenericResult<String>> useRecoveryToken(
final DeviceToken deviceToken,
) async {
GenericResult<String> token;
APIGenericResult<String> token;
QueryResult<Mutation$UseRecoveryApiKey> response;
try {
@ -489,19 +478,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
token = GenericResult<String>(
token = APIGenericResult<String>(
success: false,
data: '',
message: response.exception.toString(),
);
}
token = GenericResult<String>(
token = APIGenericResult<String>(
success: true,
data: response.parsedData!.useRecoveryApiKey.token!,
);
} catch (e) {
print(e);
token = GenericResult<String>(
token = APIGenericResult<String>(
success: false,
data: '',
message: e.toString(),

View File

@ -59,35 +59,50 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
String get displayProviderName => 'Digital Ocean';
@override
Future<bool> isApiTokenValid(final String token) async {
Future<APIGenericResult<bool>> 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

View File

@ -60,35 +60,50 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
String get displayProviderName => 'Hetzner';
@override
Future<bool> isApiTokenValid(final String token) async {
Future<APIGenericResult<bool>> 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

View File

@ -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<bool> isApiTokenValid(final String token);
Future<APIGenericResult<bool>> isApiTokenValid(final String token);
ProviderApiTokenValidation getApiTokenValidation();
Future<List<ServerMetadataEntity>> getMetadata(final int serverId);
Future<ServerMetrics?> getMetrics(

View File

@ -35,7 +35,7 @@ class ApiDevicesCubit
}
Future<List<ApiToken>?> _getApiTokens() async {
final GenericResult<List<ApiToken>> response = await api.getApiTokens();
final APIGenericResult<List<ApiToken>> response = await api.getApiTokens();
if (response.success) {
return response.data;
} else {
@ -44,7 +44,8 @@ class ApiDevicesCubit
}
Future<void> deleteDevice(final ApiToken device) async {
final GenericResult<void> response = await api.deleteApiToken(device.name);
final APIGenericResult<void> response =
await api.deleteApiToken(device.name);
if (response.success) {
emit(
ApiDevicesState(
@ -59,7 +60,7 @@ class ApiDevicesCubit
}
Future<String?> getNewDeviceKey() async {
final GenericResult<String> response = await api.createDeviceToken();
final APIGenericResult<String> response = await api.createDeviceToken();
if (response.success) {
return response.data;
} else {

View File

@ -29,21 +29,24 @@ class ProviderFormCubit extends FormCubit {
@override
FutureOr<bool> 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;
}
}

View File

@ -32,7 +32,7 @@ class RecoveryKeyCubit
}
Future<RecoveryKeyStatus?> _getRecoveryKeyStatus() async {
final GenericResult<RecoveryKeyStatus?> response =
final APIGenericResult<RecoveryKeyStatus?> 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<String> response =
final APIGenericResult<String> response =
await api.generateRecoveryToken(expirationDate, numberOfUses);
if (response.success) {
refresh();

View File

@ -76,16 +76,27 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
.getDnsProvider()
.getApiTokenValidation();
Future<bool> isServerProviderApiTokenValid(
Future<bool?> isServerProviderApiTokenValid(
final String providerToken,
) async =>
ApiController.currentServerProviderApiFactory!
.getServerProvider(
settings: const ServerProviderApiSettings(
isWithToken: false,
),
)
.isApiTokenValid(providerToken);
) async {
final APIGenericResult<bool> apiResponse =
await ApiController.currentServerProviderApiFactory!
.getServerProvider(
settings: const ServerProviderApiSettings(
isWithToken: false,
),
)
.isApiTokenValid(providerToken);
if (!apiResponse.success) {
getIt<NavigationService>().showSnackBar(
'initializing.could_not_connect'.tr(),
);
return null;
}
return apiResponse.data;
}
Future<bool> isDnsProviderApiTokenValid(
final String providerToken,

View File

@ -479,7 +479,7 @@ class ServerInstallationRepository {
overrideDomain: serverDomain.domainName,
);
final String serverIp = await getServerIpFromDomain(serverDomain);
final GenericResult<String> result = await serverApi.authorizeDevice(
final APIGenericResult<String> 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<String> result = await serverApi.useRecoveryToken(
final APIGenericResult<String> result = await serverApi.useRecoveryToken(
DeviceToken(device: await getDeviceName(), token: recoveryKey),
);
@ -577,9 +577,9 @@ class ServerInstallationRepository {
);
}
}
final GenericResult<String> deviceAuthKey =
final APIGenericResult<String> deviceAuthKey =
await serverApi.createDeviceToken();
final GenericResult<String> result = await serverApi.authorizeDevice(
final APIGenericResult<String> result = await serverApi.authorizeDevice(
DeviceToken(device: await getDeviceName(), token: deviceAuthKey.data),
);