mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-03-11 09:14:10 +00:00
feat: BackupsProvider and TokensBloc
This commit is contained in:
parent
1c7724347f
commit
74eb1135df
36 changed files with 1058 additions and 96 deletions
assets/translations
lib
config
logic
api_maps/rest_maps
bloc
cubit/server_installation
get_it
providers
ui
pages
backups
more
setup/initializing
router
|
@ -668,5 +668,19 @@
|
||||||
"australia": "Australia",
|
"australia": "Australia",
|
||||||
"united_states": "United States",
|
"united_states": "United States",
|
||||||
"finland": "Finland"
|
"finland": "Finland"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"title": "Provider tokens",
|
||||||
|
"description": "These tokens are stored on this device and are used to connect SelfPrivacy to your server provider, DNS provider, and backup storage provider.",
|
||||||
|
"server_provider_tokens": "Server provider tokens",
|
||||||
|
"dns_provider_tokens": "DNS provider tokens",
|
||||||
|
"backup_provider_tokens": "Backup storage provider tokens",
|
||||||
|
"valid": "Valid",
|
||||||
|
"invalid": "Invalid",
|
||||||
|
"no_access": "Has no access to associated resources",
|
||||||
|
"loading": "Loading token status",
|
||||||
|
"used_by": "Used for {servers}.",
|
||||||
|
"check_again": "Check again",
|
||||||
|
"no_tokens": "No tokens"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_logs/server_logs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_logs/server_logs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/tokens/tokens_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
|
@ -40,6 +41,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
late final VolumesBloc volumesBloc;
|
late final VolumesBloc volumesBloc;
|
||||||
late final ServerLogsBloc serverLogsBloc;
|
late final ServerLogsBloc serverLogsBloc;
|
||||||
late final OutdatedServerCheckerBloc outdatedServerCheckerBloc;
|
late final OutdatedServerCheckerBloc outdatedServerCheckerBloc;
|
||||||
|
late final TokensBloc tokensBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -58,6 +60,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
volumesBloc = VolumesBloc();
|
volumesBloc = VolumesBloc();
|
||||||
serverLogsBloc = ServerLogsBloc();
|
serverLogsBloc = ServerLogsBloc();
|
||||||
outdatedServerCheckerBloc = OutdatedServerCheckerBloc();
|
outdatedServerCheckerBloc = OutdatedServerCheckerBloc();
|
||||||
|
tokensBloc = TokensBloc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -106,6 +109,9 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => outdatedServerCheckerBloc,
|
create: (final _) => outdatedServerCheckerBloc,
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (final _) => tokensBloc,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,20 @@ class BackblazeApplicationKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackblazeApi extends RestApiMap {
|
class BackblazeApi extends RestApiMap {
|
||||||
BackblazeApi({this.hasLogger = false, this.isWithToken = true});
|
BackblazeApi({
|
||||||
|
this.token = '',
|
||||||
|
this.tokenId = '',
|
||||||
|
this.hasLogger = false,
|
||||||
|
this.isWithToken = true,
|
||||||
|
}) : assert(isWithToken ? token.isNotEmpty && tokenId.isNotEmpty : true);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hasLogger;
|
||||||
|
@override
|
||||||
|
bool isWithToken;
|
||||||
|
|
||||||
|
final String token;
|
||||||
|
final String tokenId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
|
@ -39,10 +52,8 @@ class BackblazeApi extends RestApiMap {
|
||||||
responseType: ResponseType.json,
|
responseType: ResponseType.json,
|
||||||
);
|
);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final BackupsCredential? backblazeCredential =
|
final encodedApiKey = encodedBackblazeKey(tokenId, token);
|
||||||
getIt<ResourcesModel>().backblazeCredential;
|
options.headers = {'Authorization': 'Basic $encodedApiKey'};
|
||||||
final String token = backblazeCredential!.applicationKey;
|
|
||||||
options.headers = {'Authorization': 'Basic $token'};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validateStatus != null) {
|
if (validateStatus != null) {
|
||||||
|
@ -59,14 +70,12 @@ 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 =
|
if (token.isEmpty || tokenId.isEmpty) {
|
||||||
getIt<ResourcesModel>().backblazeCredential;
|
|
||||||
if (backblazeCredential == null) {
|
|
||||||
throw Exception('Backblaze credential is null');
|
throw Exception('Backblaze credential is null');
|
||||||
}
|
}
|
||||||
final String encodedApiKey = encodedBackblazeKey(
|
final String encodedApiKey = encodedBackblazeKey(
|
||||||
backblazeCredential.keyId,
|
tokenId,
|
||||||
backblazeCredential.applicationKey,
|
token,
|
||||||
);
|
);
|
||||||
final Response response = await client.get(
|
final Response response = await client.get(
|
||||||
'b2_authorize_account',
|
'b2_authorize_account',
|
||||||
|
@ -122,16 +131,14 @@ class BackblazeApi extends RestApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create bucket
|
// Create bucket
|
||||||
Future<String> createBucket(final String bucketName) async {
|
Future<GenericResult<String>> createBucket(final String bucketName) async {
|
||||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||||
final BackupsCredential? 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(
|
||||||
'$apiPrefix/b2_create_bucket',
|
'$apiPrefix/b2_create_bucket',
|
||||||
data: {
|
data: {
|
||||||
'accountId': backblazeCredential!.keyId,
|
'accountId': tokenId,
|
||||||
'bucketName': bucketName,
|
'bucketName': bucketName,
|
||||||
'bucketType': 'allPrivate',
|
'bucketType': 'allPrivate',
|
||||||
'lifecycleRules': [
|
'lifecycleRules': [
|
||||||
|
@ -148,14 +155,23 @@ class BackblazeApi extends RestApiMap {
|
||||||
);
|
);
|
||||||
close(client);
|
close(client);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return response.data['bucketId'];
|
return GenericResult(
|
||||||
|
data: response.data['bucketId'],
|
||||||
|
success: true,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('code: ${response.statusCode}');
|
return GenericResult(
|
||||||
|
data: '',
|
||||||
|
success: false,
|
||||||
|
message: 'code: ${response.statusCode}, ${response.data}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a limited capability key with access to the given bucket
|
// Create a limited capability key with access to the given bucket
|
||||||
Future<BackblazeApplicationKey> createKey(final String bucketId) async {
|
Future<GenericResult<BackblazeApplicationKey>> createKey(
|
||||||
|
final String bucketId,
|
||||||
|
) async {
|
||||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
client.options.baseUrl = auth.apiUrl;
|
client.options.baseUrl = auth.apiUrl;
|
||||||
|
@ -173,16 +189,26 @@ class BackblazeApi extends RestApiMap {
|
||||||
);
|
);
|
||||||
close(client);
|
close(client);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return BackblazeApplicationKey(
|
return GenericResult(
|
||||||
applicationKeyId: response.data['applicationKeyId'],
|
success: true,
|
||||||
applicationKey: response.data['applicationKey'],
|
data: BackblazeApplicationKey(
|
||||||
|
applicationKeyId: response.data['applicationKeyId'],
|
||||||
|
applicationKey: response.data['applicationKey'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('code: ${response.statusCode}');
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
data: BackblazeApplicationKey(
|
||||||
|
applicationKeyId: '',
|
||||||
|
applicationKey: '',
|
||||||
|
),
|
||||||
|
message: 'code: ${response.statusCode}, ${response.data}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<BackblazeBucket?> fetchBucket(
|
Future<GenericResult<BackblazeBucket?>> fetchBucket(
|
||||||
final BackupsCredential credentials,
|
final BackupsCredential credentials,
|
||||||
final BackupConfiguration configuration,
|
final BackupConfiguration configuration,
|
||||||
) async {
|
) async {
|
||||||
|
@ -212,15 +238,16 @@ class BackblazeApi extends RestApiMap {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bucket;
|
return GenericResult(
|
||||||
|
success: bucket != null,
|
||||||
|
data: bucket,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('code: ${response.statusCode}');
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
message: 'code: ${response.statusCode}, ${response.data}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
bool hasLogger;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool isWithToken;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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 {
|
||||||
CloudflareApi({
|
CloudflareApi({
|
||||||
|
this.token = '',
|
||||||
this.hasLogger = false,
|
this.hasLogger = false,
|
||||||
this.isWithToken = true,
|
this.isWithToken = true,
|
||||||
this.customToken,
|
this.customToken,
|
||||||
});
|
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool hasLogger;
|
final bool hasLogger;
|
||||||
@override
|
@override
|
||||||
final bool isWithToken;
|
final bool isWithToken;
|
||||||
|
|
||||||
|
final String token;
|
||||||
final String? customToken;
|
final String? customToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,8 +29,7 @@ class CloudflareApi extends RestApiMap {
|
||||||
responseType: ResponseType.json,
|
responseType: ResponseType.json,
|
||||||
);
|
);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final String? token = getIt<ResourcesModel>().dnsProviderKey;
|
assert(token.isNotEmpty);
|
||||||
assert(token != null);
|
|
||||||
options.headers = {'Authorization': 'Bearer $token'};
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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 {
|
||||||
DesecApi({
|
DesecApi({
|
||||||
|
this.token = '',
|
||||||
this.hasLogger = false,
|
this.hasLogger = false,
|
||||||
this.isWithToken = true,
|
this.isWithToken = true,
|
||||||
this.customToken,
|
this.customToken,
|
||||||
});
|
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool hasLogger;
|
final bool hasLogger;
|
||||||
@override
|
@override
|
||||||
final bool isWithToken;
|
final bool isWithToken;
|
||||||
|
|
||||||
|
final String token;
|
||||||
final String? customToken;
|
final String? customToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,8 +29,7 @@ class DesecApi extends RestApiMap {
|
||||||
responseType: ResponseType.json,
|
responseType: ResponseType.json,
|
||||||
);
|
);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final String? token = getIt<ResourcesModel>().dnsProviderKey;
|
assert(token.isNotEmpty);
|
||||||
assert(token != null);
|
|
||||||
options.headers = {'Authorization': 'Token $token'};
|
options.headers = {'Authorization': 'Token $token'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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 {
|
||||||
DigitalOceanDnsApi({
|
DigitalOceanDnsApi({
|
||||||
|
this.token = '',
|
||||||
this.hasLogger = false,
|
this.hasLogger = false,
|
||||||
this.isWithToken = true,
|
this.isWithToken = true,
|
||||||
this.customToken,
|
this.customToken,
|
||||||
});
|
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool hasLogger;
|
final bool hasLogger;
|
||||||
@override
|
@override
|
||||||
final bool isWithToken;
|
final bool isWithToken;
|
||||||
|
|
||||||
|
final String token;
|
||||||
final String? customToken;
|
final String? customToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,8 +29,7 @@ class DigitalOceanDnsApi extends RestApiMap {
|
||||||
responseType: ResponseType.json,
|
responseType: ResponseType.json,
|
||||||
);
|
);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final String? token = getIt<ResourcesModel>().dnsProviderKey;
|
assert(token.isNotEmpty);
|
||||||
assert(token != null);
|
|
||||||
options.headers = {'Authorization': 'Bearer $token'};
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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/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';
|
||||||
|
@ -13,15 +11,18 @@ import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
class DigitalOceanApi extends RestApiMap {
|
class DigitalOceanApi extends RestApiMap {
|
||||||
DigitalOceanApi({
|
DigitalOceanApi({
|
||||||
required this.region,
|
required this.region,
|
||||||
|
this.token = '',
|
||||||
this.hasLogger = true,
|
this.hasLogger = true,
|
||||||
this.isWithToken = true,
|
this.isWithToken = true,
|
||||||
});
|
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool hasLogger;
|
bool hasLogger;
|
||||||
@override
|
@override
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
|
||||||
final String? region;
|
final String? region;
|
||||||
|
final String token;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
|
@ -31,8 +32,7 @@ class DigitalOceanApi extends RestApiMap {
|
||||||
responseType: ResponseType.json,
|
responseType: ResponseType.json,
|
||||||
);
|
);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final String? token = getIt<ResourcesModel>().serverProviderKey;
|
assert(token.isNotEmpty);
|
||||||
assert(token != null);
|
|
||||||
options.headers = {'Authorization': 'Bearer $token'};
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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/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';
|
||||||
|
@ -14,15 +12,18 @@ import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
class HetznerApi extends RestApiMap {
|
class HetznerApi extends RestApiMap {
|
||||||
HetznerApi({
|
HetznerApi({
|
||||||
this.region,
|
this.region,
|
||||||
|
this.token = '',
|
||||||
this.hasLogger = true,
|
this.hasLogger = true,
|
||||||
this.isWithToken = true,
|
this.isWithToken = true,
|
||||||
});
|
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool hasLogger;
|
bool hasLogger;
|
||||||
@override
|
@override
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
|
||||||
final String? region;
|
final String? region;
|
||||||
|
final String token;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
|
@ -32,8 +33,7 @@ class HetznerApi extends RestApiMap {
|
||||||
responseType: ResponseType.json,
|
responseType: ResponseType.json,
|
||||||
);
|
);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final String? token = getIt<ResourcesModel>().serverProviderKey;
|
assert(token.isNotEmpty);
|
||||||
assert(token != null);
|
|
||||||
options.headers = {'Authorization': 'Bearer $token'};
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
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/get_it/resources_model.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';
|
||||||
|
@ -13,6 +12,9 @@ import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||||
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
||||||
|
|
||||||
part 'backups_event.dart';
|
part 'backups_event.dart';
|
||||||
part 'backups_state.dart';
|
part 'backups_state.dart';
|
||||||
|
@ -103,8 +105,6 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final BackblazeApi backblaze = BackblazeApi();
|
|
||||||
|
|
||||||
Future<void> _loadState(
|
Future<void> _loadState(
|
||||||
final BackupsServerLoaded event,
|
final BackupsServerLoaded event,
|
||||||
final Emitter<BackupsState> emit,
|
final Emitter<BackupsState> emit,
|
||||||
|
@ -166,6 +166,14 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
||||||
final BackblazeBucket bucket;
|
final BackblazeBucket bucket;
|
||||||
|
|
||||||
if (state.backblazeBucket == null) {
|
if (state.backblazeBucket == null) {
|
||||||
|
final settings = BackupsProviderSettings(
|
||||||
|
provider: BackupsProviderType.backblaze,
|
||||||
|
tokenId: event.credential.keyId,
|
||||||
|
token: event.credential.applicationKey,
|
||||||
|
isAuthorized: true,
|
||||||
|
);
|
||||||
|
final provider =
|
||||||
|
BackupsProviderFactory.createBackupsProviderInterface(settings);
|
||||||
final String domain = getIt<ApiConnectionRepository>()
|
final String domain = getIt<ApiConnectionRepository>()
|
||||||
.serverDomain!
|
.serverDomain!
|
||||||
.domainName
|
.domainName
|
||||||
|
@ -176,9 +184,30 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
||||||
if (bucketName.length > 49) {
|
if (bucketName.length > 49) {
|
||||||
bucketName = bucketName.substring(0, 49);
|
bucketName = bucketName.substring(0, 49);
|
||||||
}
|
}
|
||||||
final String bucketId = await backblaze.createBucket(bucketName);
|
|
||||||
|
|
||||||
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
final createStorageResult = await provider.createStorage(bucketName);
|
||||||
|
if (createStorageResult.success == false ||
|
||||||
|
createStorageResult.data.isEmpty) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
createStorageResult.message ??
|
||||||
|
"Couldn't create storage on your server.",
|
||||||
|
);
|
||||||
|
emit(BackupsUnititialized());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String bucketId = createStorageResult.data;
|
||||||
|
|
||||||
|
final BackupsApplicationKey? key =
|
||||||
|
(await provider.createApplicationKey(bucketId)).data;
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
"Couldn't create application key on your server.",
|
||||||
|
);
|
||||||
|
emit(BackupsUnititialized());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bucket = BackblazeBucket(
|
bucket = BackblazeBucket(
|
||||||
bucketId: bucketId,
|
bucketId: bucketId,
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
|
|
|
@ -19,7 +19,11 @@ class BackupsServerReset extends BackupsEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class InitializeBackupsRepository extends BackupsEvent {
|
class InitializeBackupsRepository extends BackupsEvent {
|
||||||
const InitializeBackupsRepository();
|
const InitializeBackupsRepository(
|
||||||
|
this.credential,
|
||||||
|
);
|
||||||
|
|
||||||
|
final BackupsCredential credential;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => [];
|
||||||
|
|
164
lib/logic/bloc/tokens/tokens_bloc.dart
Normal file
164
lib/logic/bloc/tokens/tokens_bloc.dart
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.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/dns_provider_credential.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/server_providers/server_provider_factory.dart';
|
||||||
|
|
||||||
|
part 'tokens_event.dart';
|
||||||
|
part 'tokens_state.dart';
|
||||||
|
|
||||||
|
class TokensBloc extends Bloc<TokensEvent, TokensState> {
|
||||||
|
TokensBloc() : super(const TokensInitial()) {
|
||||||
|
on<RevalidateTokens>(
|
||||||
|
validateTokens,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
|
||||||
|
add(const RevalidateTokens());
|
||||||
|
|
||||||
|
_resourcesModelSubscription =
|
||||||
|
getIt<ResourcesModel>().statusStream.listen((final _) {
|
||||||
|
add(const RevalidateTokens());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> validateTokens(
|
||||||
|
final RevalidateTokens event,
|
||||||
|
final Emitter<TokensState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const TokensInitial());
|
||||||
|
final List<ServerProviderCredential> serverProviderCredentials =
|
||||||
|
getIt<ResourcesModel>().serverProviderCredentials;
|
||||||
|
final List<DnsProviderCredential> dnsProviderCredentials =
|
||||||
|
getIt<ResourcesModel>().dnsProviderCredentials;
|
||||||
|
final List<BackupsCredential> backupsCredentials =
|
||||||
|
getIt<ResourcesModel>().backupsCredentials;
|
||||||
|
|
||||||
|
final List<TokenStatusWrapper<ServerProviderCredential>>
|
||||||
|
validatedServerProviderCredentials = [];
|
||||||
|
final List<TokenStatusWrapper<DnsProviderCredential>>
|
||||||
|
validatedDnsProviderCredentials = [];
|
||||||
|
final List<TokenStatusWrapper<BackupsCredential>>
|
||||||
|
validatedBackupsCredentials = [];
|
||||||
|
|
||||||
|
for (final credential in serverProviderCredentials) {
|
||||||
|
final TokenStatus status = await _validateServerProviderToken(credential);
|
||||||
|
validatedServerProviderCredentials
|
||||||
|
.add(TokenStatusWrapper(data: credential, status: status));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final credential in dnsProviderCredentials) {
|
||||||
|
final TokenStatus status = await _validateDnsProviderToken(credential);
|
||||||
|
validatedDnsProviderCredentials
|
||||||
|
.add(TokenStatusWrapper(data: credential, status: status));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final credential in backupsCredentials) {
|
||||||
|
final TokenStatus status = await _validateBackupsToken(credential);
|
||||||
|
validatedBackupsCredentials
|
||||||
|
.add(TokenStatusWrapper(data: credential, status: status));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(
|
||||||
|
TokensChecked(
|
||||||
|
serverProviderCredentials: validatedServerProviderCredentials,
|
||||||
|
dnsProviderCredentials: validatedDnsProviderCredentials,
|
||||||
|
backupsCredentials: validatedBackupsCredentials,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TokenStatus> _validateServerProviderToken(
|
||||||
|
final ServerProviderCredential credential,
|
||||||
|
) async {
|
||||||
|
final ServerProviderSettings settings = ServerProviderSettings(
|
||||||
|
provider: credential.provider,
|
||||||
|
token: credential.token,
|
||||||
|
isAuthorized: true,
|
||||||
|
);
|
||||||
|
final serverProvider =
|
||||||
|
ServerProviderFactory.createServerProviderInterface(settings);
|
||||||
|
// First, we check if the token works at all
|
||||||
|
final basicInitCheckResult =
|
||||||
|
await serverProvider.tryInitApiByToken(credential.token);
|
||||||
|
if (!basicInitCheckResult.data) {
|
||||||
|
return TokenStatus.invalid;
|
||||||
|
}
|
||||||
|
// Now, if there are associated servers, check if we have access to them
|
||||||
|
if (credential.associatedServerIds.isNotEmpty) {
|
||||||
|
final servers = (await serverProvider.getServers()).data;
|
||||||
|
for (final serverId in credential.associatedServerIds) {
|
||||||
|
if (!servers.any((final server) => server.id == serverId)) {
|
||||||
|
return TokenStatus.noAccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TokenStatus.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TokenStatus> _validateDnsProviderToken(
|
||||||
|
final DnsProviderCredential credential,
|
||||||
|
) async {
|
||||||
|
final DnsProviderSettings settings = DnsProviderSettings(
|
||||||
|
provider: credential.provider,
|
||||||
|
token: credential.token,
|
||||||
|
isAuthorized: true,
|
||||||
|
);
|
||||||
|
final dnsProvider = DnsProviderFactory.createDnsProviderInterface(settings);
|
||||||
|
final basicInitCheckResult =
|
||||||
|
await dnsProvider.tryInitApiByToken(credential.token);
|
||||||
|
if (!basicInitCheckResult.data) {
|
||||||
|
return TokenStatus.invalid;
|
||||||
|
}
|
||||||
|
if (credential.associatedDomainNames.isNotEmpty) {
|
||||||
|
final domains = (await dnsProvider.domainList()).data;
|
||||||
|
for (final domainName in credential.associatedDomainNames) {
|
||||||
|
if (!domains.any((final domain) => domain.domainName == domainName)) {
|
||||||
|
return TokenStatus.noAccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TokenStatus.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TokenStatus> _validateBackupsToken(
|
||||||
|
final BackupsCredential credential,
|
||||||
|
) async {
|
||||||
|
final BackupsProviderSettings settings = BackupsProviderSettings(
|
||||||
|
provider: credential.provider,
|
||||||
|
token: credential.applicationKey,
|
||||||
|
tokenId: credential.keyId,
|
||||||
|
isAuthorized: true,
|
||||||
|
);
|
||||||
|
final backupsProvider =
|
||||||
|
BackupsProviderFactory.createBackupsProviderInterface(settings);
|
||||||
|
final basicInitCheckResult = await backupsProvider.tryInitApiByToken(
|
||||||
|
encodedBackblazeKey(
|
||||||
|
credential.keyId,
|
||||||
|
credential.applicationKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!basicInitCheckResult.data) {
|
||||||
|
return TokenStatus.invalid;
|
||||||
|
}
|
||||||
|
return TokenStatus.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_resourcesModelSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription _resourcesModelSubscription;
|
||||||
|
}
|
12
lib/logic/bloc/tokens/tokens_event.dart
Normal file
12
lib/logic/bloc/tokens/tokens_event.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
part of 'tokens_bloc.dart';
|
||||||
|
|
||||||
|
sealed class TokensEvent extends Equatable {
|
||||||
|
const TokensEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RevalidateTokens extends TokensEvent {
|
||||||
|
const RevalidateTokens();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
110
lib/logic/bloc/tokens/tokens_state.dart
Normal file
110
lib/logic/bloc/tokens/tokens_state.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
part of 'tokens_bloc.dart';
|
||||||
|
|
||||||
|
enum TokenStatus {
|
||||||
|
loading,
|
||||||
|
valid,
|
||||||
|
invalid,
|
||||||
|
noAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenStatusWrapper<T> {
|
||||||
|
TokenStatusWrapper({
|
||||||
|
required this.data,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
final T data;
|
||||||
|
final TokenStatus status;
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TokensState extends Equatable {
|
||||||
|
const TokensState();
|
||||||
|
|
||||||
|
List<TokenStatusWrapper<ServerProviderCredential>>
|
||||||
|
get serverProviderCredentials;
|
||||||
|
List<TokenStatusWrapper<DnsProviderCredential>> get dnsProviderCredentials;
|
||||||
|
List<TokenStatusWrapper<BackupsCredential>> get backupsCredentials;
|
||||||
|
List<Server> get servers => _servers;
|
||||||
|
|
||||||
|
Server getServerById(final int serverId) => servers.firstWhere(
|
||||||
|
(final Server server) => server.hostingDetails.id == serverId,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ServerProviderCredential> get _serverProviderCredentials =>
|
||||||
|
getIt<ResourcesModel>().serverProviderCredentials;
|
||||||
|
List<DnsProviderCredential> get _dnsProviderCredentials =>
|
||||||
|
getIt<ResourcesModel>().dnsProviderCredentials;
|
||||||
|
List<BackupsCredential> get _backupsCredentials =>
|
||||||
|
getIt<ResourcesModel>().backupsCredentials;
|
||||||
|
List<Server> get _servers => getIt<ResourcesModel>().servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TokensInitial extends TokensState {
|
||||||
|
const TokensInitial();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TokenStatusWrapper<ServerProviderCredential>>
|
||||||
|
get serverProviderCredentials => _serverProviderCredentials
|
||||||
|
.map(
|
||||||
|
(final ServerProviderCredential serverProviderCredential) =>
|
||||||
|
TokenStatusWrapper<ServerProviderCredential>(
|
||||||
|
data: serverProviderCredential,
|
||||||
|
status: TokenStatus.loading,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TokenStatusWrapper<DnsProviderCredential>> get dnsProviderCredentials =>
|
||||||
|
_dnsProviderCredentials
|
||||||
|
.map(
|
||||||
|
(final DnsProviderCredential dnsProviderCredential) =>
|
||||||
|
TokenStatusWrapper<DnsProviderCredential>(
|
||||||
|
data: dnsProviderCredential,
|
||||||
|
status: TokenStatus.loading,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TokenStatusWrapper<BackupsCredential>> get backupsCredentials =>
|
||||||
|
_backupsCredentials
|
||||||
|
.map(
|
||||||
|
(final BackupsCredential backupsCredential) =>
|
||||||
|
TokenStatusWrapper<BackupsCredential>(
|
||||||
|
data: backupsCredential,
|
||||||
|
status: TokenStatus.loading,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Server> get servers => _servers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TokensChecked extends TokensState {
|
||||||
|
const TokensChecked({
|
||||||
|
required this.serverProviderCredentials,
|
||||||
|
required this.dnsProviderCredentials,
|
||||||
|
required this.backupsCredentials,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final List<TokenStatusWrapper<ServerProviderCredential>>
|
||||||
|
serverProviderCredentials;
|
||||||
|
@override
|
||||||
|
final List<TokenStatusWrapper<DnsProviderCredential>> dnsProviderCredentials;
|
||||||
|
@override
|
||||||
|
final List<TokenStatusWrapper<BackupsCredential>> backupsCredentials;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [
|
||||||
|
serverProviderCredentials,
|
||||||
|
dnsProviderCredentials,
|
||||||
|
backupsCredentials,
|
||||||
|
servers,
|
||||||
|
];
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ 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/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/rest_maps/backblaze.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_repository.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart';
|
||||||
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
|
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
|
||||||
|
@ -21,6 +20,7 @@ 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';
|
||||||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_type.dart';
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider_factory.dart';
|
||||||
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
||||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
@ -234,8 +234,17 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
).getBackupsConfiguration();
|
).getBackupsConfiguration();
|
||||||
if (configuration != null) {
|
if (configuration != null) {
|
||||||
try {
|
try {
|
||||||
bucket = await BackblazeApi()
|
final settings = BackupsProviderSettings(
|
||||||
.fetchBucket(backblazeCredential, configuration);
|
provider: BackupsProviderType.backblaze,
|
||||||
|
tokenId: keyId,
|
||||||
|
token: applicationKey,
|
||||||
|
isAuthorized: true,
|
||||||
|
);
|
||||||
|
final provider =
|
||||||
|
BackupsProviderFactory.createBackupsProviderInterface(settings);
|
||||||
|
final result =
|
||||||
|
await provider.getStorage(backblazeCredential, configuration);
|
||||||
|
bucket = result.data;
|
||||||
await getIt<ApiConfigModel>().setBackblazeBucket(bucket!);
|
await getIt<ApiConfigModel>().setBackblazeBucket(bucket!);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
|
|
@ -66,6 +66,7 @@ class ServerInstallationRepository {
|
||||||
provider: serverProvider ?? serverDetails!.provider,
|
provider: serverProvider ?? serverDetails!.provider,
|
||||||
isAuthorized: providerApiToken != null,
|
isAuthorized: providerApiToken != null,
|
||||||
location: location,
|
location: location,
|
||||||
|
token: providerApiToken,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -77,6 +78,7 @@ class ServerInstallationRepository {
|
||||||
DnsProviderSettings(
|
DnsProviderSettings(
|
||||||
isAuthorized: dnsApiToken != null,
|
isAuthorized: dnsApiToken != null,
|
||||||
provider: dnsProvider ?? serverDomain!.provider,
|
provider: dnsProvider ?? serverDomain!.provider,
|
||||||
|
token: dnsApiToken,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -461,6 +463,14 @@ class ServerInstallationRepository {
|
||||||
associatedServerIds: [],
|
associatedServerIds: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
ProvidersController.initServerProvider(
|
||||||
|
ServerProviderSettings(
|
||||||
|
provider:
|
||||||
|
getIt<WizardDataModel>().serverInstallation!.serverProviderType!,
|
||||||
|
token: key,
|
||||||
|
isAuthorized: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveServerType(final ServerType serverType) async {
|
Future<void> saveServerType(final ServerType serverType) async {
|
||||||
|
@ -488,6 +498,13 @@ class ServerInstallationRepository {
|
||||||
associatedDomainNames: [],
|
associatedDomainNames: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
ProvidersController.initDnsProvider(
|
||||||
|
DnsProviderSettings(
|
||||||
|
provider: getIt<WizardDataModel>().serverInstallation!.dnsProviderType!,
|
||||||
|
token: key,
|
||||||
|
isAuthorized: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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';
|
||||||
|
@ -10,9 +12,43 @@ 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/hive/wizards_data/server_installation_wizard_data.dart';
|
||||||
|
|
||||||
|
sealed class ResourcesModelEvent {
|
||||||
|
const ResourcesModelEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourcesModelLoaded extends ResourcesModelEvent {
|
||||||
|
const ResourcesModelLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangedServerProviderCredentials extends ResourcesModelEvent {
|
||||||
|
const ChangedServerProviderCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangedDnsProviderCredentials extends ResourcesModelEvent {
|
||||||
|
const ChangedDnsProviderCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangedBackupsCredentials extends ResourcesModelEvent {
|
||||||
|
const ChangedBackupsCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangedServers extends ResourcesModelEvent {
|
||||||
|
const ChangedServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClearedModel extends ResourcesModelEvent {
|
||||||
|
const ClearedModel();
|
||||||
|
}
|
||||||
|
|
||||||
class ResourcesModel {
|
class ResourcesModel {
|
||||||
final Box _box = Hive.box(BNames.resourcesBox);
|
final Box _box = Hive.box(BNames.resourcesBox);
|
||||||
|
|
||||||
|
final _statusStreamController =
|
||||||
|
StreamController<ResourcesModelEvent>.broadcast();
|
||||||
|
|
||||||
|
Stream<ResourcesModelEvent> get statusStream =>
|
||||||
|
_statusStreamController.stream;
|
||||||
|
|
||||||
List<ServerProviderCredential> get serverProviderCredentials =>
|
List<ServerProviderCredential> get serverProviderCredentials =>
|
||||||
_serverProviderTokens;
|
_serverProviderTokens;
|
||||||
List<DnsProviderCredential> get dnsProviderCredentials => _dnsProviderTokens;
|
List<DnsProviderCredential> get dnsProviderCredentials => _dnsProviderTokens;
|
||||||
|
@ -55,6 +91,7 @@ class ResourcesModel {
|
||||||
) async {
|
) async {
|
||||||
_serverProviderTokens.add(token);
|
_serverProviderTokens.add(token);
|
||||||
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
||||||
|
_statusStreamController.add(const ChangedServerProviderCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> associateServerWithToken(
|
Future<void> associateServerWithToken(
|
||||||
|
@ -68,6 +105,7 @@ class ResourcesModel {
|
||||||
.associatedServerIds
|
.associatedServerIds
|
||||||
.add(serverId);
|
.add(serverId);
|
||||||
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
||||||
|
_statusStreamController.add(const ChangedServerProviderCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeServerProviderToken(
|
Future<void> removeServerProviderToken(
|
||||||
|
@ -75,6 +113,7 @@ class ResourcesModel {
|
||||||
) async {
|
) async {
|
||||||
_serverProviderTokens.remove(token);
|
_serverProviderTokens.remove(token);
|
||||||
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
||||||
|
_statusStreamController.add(const ChangedServerProviderCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addDnsProviderToken(final DnsProviderCredential token) async {
|
Future<void> addDnsProviderToken(final DnsProviderCredential token) async {
|
||||||
|
@ -85,6 +124,7 @@ class ResourcesModel {
|
||||||
}
|
}
|
||||||
_dnsProviderTokens.add(token);
|
_dnsProviderTokens.add(token);
|
||||||
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
||||||
|
_statusStreamController.add(const ChangedDnsProviderCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> associateDomainWithToken(
|
Future<void> associateDomainWithToken(
|
||||||
|
@ -98,16 +138,19 @@ class ResourcesModel {
|
||||||
.associatedDomainNames
|
.associatedDomainNames
|
||||||
.add(domain);
|
.add(domain);
|
||||||
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
||||||
|
_statusStreamController.add(const ChangedDnsProviderCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeDnsProviderToken(final DnsProviderCredential token) async {
|
Future<void> removeDnsProviderToken(final DnsProviderCredential token) async {
|
||||||
_dnsProviderTokens.remove(token);
|
_dnsProviderTokens.remove(token);
|
||||||
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
||||||
|
_statusStreamController.add(const ChangedDnsProviderCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addBackupsCredential(final BackupsCredential credential) async {
|
Future<void> addBackupsCredential(final BackupsCredential credential) async {
|
||||||
_backupsCredentials.add(credential);
|
_backupsCredentials.add(credential);
|
||||||
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
|
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
|
||||||
|
_statusStreamController.add(const ChangedBackupsCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeBackupsCredential(
|
Future<void> removeBackupsCredential(
|
||||||
|
@ -115,16 +158,19 @@ class ResourcesModel {
|
||||||
) async {
|
) async {
|
||||||
_backupsCredentials.remove(credential);
|
_backupsCredentials.remove(credential);
|
||||||
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
|
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
|
||||||
|
_statusStreamController.add(const ChangedBackupsCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addServer(final Server server) async {
|
Future<void> addServer(final Server server) async {
|
||||||
_servers.add(server);
|
_servers.add(server);
|
||||||
await _box.put(BNames.servers, _servers);
|
await _box.put(BNames.servers, _servers);
|
||||||
|
_statusStreamController.add(const ChangedServers());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeServer(final Server server) async {
|
Future<void> removeServer(final Server server) async {
|
||||||
_servers.remove(server);
|
_servers.remove(server);
|
||||||
await _box.put(BNames.servers, _servers);
|
await _box.put(BNames.servers, _servers);
|
||||||
|
_statusStreamController.add(const ChangedServers());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setBackblazeBucket(final BackblazeBucket bucket) async {
|
Future<void> setBackblazeBucket(final BackblazeBucket bucket) async {
|
||||||
|
@ -146,6 +192,12 @@ class ResourcesModel {
|
||||||
|
|
||||||
_box.clear();
|
_box.clear();
|
||||||
_box.compact();
|
_box.compact();
|
||||||
|
|
||||||
|
_statusStreamController.add(const ClearedModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_statusStreamController.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
|
@ -180,6 +232,8 @@ class ResourcesModel {
|
||||||
.map<Server>((final e) => e as Server)
|
.map<Server>((final e) => e as Server)
|
||||||
.toList();
|
.toList();
|
||||||
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
||||||
|
|
||||||
|
_statusStreamController.add(const ResourcesModelLoaded());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
93
lib/logic/providers/backups_providers/backblaze.dart
Normal file
93
lib/logic/providers/backups_providers/backblaze.dart
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider.dart';
|
||||||
|
|
||||||
|
class ApiAdapter {
|
||||||
|
ApiAdapter({
|
||||||
|
final String? token,
|
||||||
|
final String? tokenId,
|
||||||
|
}) : _api = BackblazeApi(
|
||||||
|
isWithToken: true,
|
||||||
|
token: token ?? '',
|
||||||
|
tokenId: tokenId ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
BackblazeApi api({final bool getInitialized = true}) => getInitialized
|
||||||
|
? _api
|
||||||
|
: BackblazeApi(
|
||||||
|
isWithToken: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final BackblazeApi _api;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackblazeBackupsProvider extends BackupsProvider {
|
||||||
|
BackblazeBackupsProvider() : _adapter = ApiAdapter();
|
||||||
|
BackblazeBackupsProvider.load(
|
||||||
|
final bool isAuthorized,
|
||||||
|
final String? token,
|
||||||
|
final String? tokenId,
|
||||||
|
) : _adapter = ApiAdapter(
|
||||||
|
token: token,
|
||||||
|
tokenId: tokenId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final ApiAdapter _adapter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BackupsProviderType get type => BackupsProviderType.backblaze;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get howToRegister => 'how_backblaze';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||||
|
final api = _adapter.api(getInitialized: false);
|
||||||
|
final result = await api.isApiTokenValid(token);
|
||||||
|
if (!result.data || !result.success) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<GenericResult<String>> createStorage(final String desiredName) async {
|
||||||
|
final api = _adapter.api();
|
||||||
|
final result = await api.createBucket(desiredName);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<GenericResult<BackupsApplicationKey?>> createApplicationKey(
|
||||||
|
final String storageId,
|
||||||
|
) async {
|
||||||
|
final api = _adapter.api();
|
||||||
|
final result = await api.createKey(storageId);
|
||||||
|
if (!result.success) {
|
||||||
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
message: result.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GenericResult(
|
||||||
|
success: result.success,
|
||||||
|
data: BackupsApplicationKey(
|
||||||
|
applicationKeyId: result.data.applicationKeyId,
|
||||||
|
applicationKey: result.data.applicationKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<GenericResult<BackblazeBucket?>> getStorage(
|
||||||
|
final BackupsCredential credentials,
|
||||||
|
final BackupConfiguration configuration,
|
||||||
|
) async {
|
||||||
|
final api = _adapter.api();
|
||||||
|
final result = await api.fetchBucket(credentials, configuration);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
47
lib/logic/providers/backups_providers/backups_provider.dart
Normal file
47
lib/logic/providers/backups_providers/backups_provider.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||||
|
export 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
|
||||||
|
class BackupsApplicationKey {
|
||||||
|
BackupsApplicationKey({
|
||||||
|
required this.applicationKeyId,
|
||||||
|
required this.applicationKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String applicationKeyId;
|
||||||
|
final String applicationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BackupsProvider {
|
||||||
|
/// Returns an assigned enum value, respectively to which
|
||||||
|
/// provider implements [BackupsProvider] interface.
|
||||||
|
BackupsProviderType get type;
|
||||||
|
|
||||||
|
/// Returns a full url to a guide on how to setup
|
||||||
|
/// backups provider
|
||||||
|
String get howToRegister;
|
||||||
|
|
||||||
|
/// Tries to access an account linked to the provided token.
|
||||||
|
///
|
||||||
|
/// To generate a token for your account follow instructions of your
|
||||||
|
/// backups provider respectfully.
|
||||||
|
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||||
|
|
||||||
|
/// Creates a storage for backups (for example, a bucket)
|
||||||
|
/// and returns a storage ID to access it.
|
||||||
|
Future<GenericResult<String>> createStorage(final String desiredName);
|
||||||
|
|
||||||
|
/// Creates the credentials to access the backups storage
|
||||||
|
/// from the server
|
||||||
|
Future<GenericResult<BackupsApplicationKey?>> createApplicationKey(
|
||||||
|
final String storageId,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Get the backups storage
|
||||||
|
Future<GenericResult<BackblazeBucket?>> getStorage(
|
||||||
|
final BackupsCredential credentials,
|
||||||
|
final BackupConfiguration configuration,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backblaze.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
||||||
|
|
||||||
|
class UnknownProviderException implements Exception {
|
||||||
|
UnknownProviderException(this.message);
|
||||||
|
final String message;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsProviderFactory {
|
||||||
|
static BackupsProvider createBackupsProviderInterface(
|
||||||
|
final BackupsProviderSettings settings,
|
||||||
|
) {
|
||||||
|
switch (settings.provider) {
|
||||||
|
case BackupsProviderType.backblaze:
|
||||||
|
return settings.isAuthorized
|
||||||
|
? BackblazeBackupsProvider.load(
|
||||||
|
settings.isAuthorized,
|
||||||
|
settings.token,
|
||||||
|
settings.tokenId,
|
||||||
|
)
|
||||||
|
: BackblazeBackupsProvider();
|
||||||
|
case BackupsProviderType.none:
|
||||||
|
case BackupsProviderType.file:
|
||||||
|
case BackupsProviderType.memory:
|
||||||
|
throw UnknownProviderException('Unknown server provider');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,12 @@ import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({
|
ApiAdapter({
|
||||||
final bool isWithToken = true,
|
final bool isWithToken = true,
|
||||||
|
final String? token,
|
||||||
this.cachedDomain = '',
|
this.cachedDomain = '',
|
||||||
this.cachedZoneId = '',
|
this.cachedZoneId = '',
|
||||||
}) : _api = CloudflareApi(
|
}) : _api = CloudflareApi(
|
||||||
isWithToken: isWithToken,
|
isWithToken: isWithToken,
|
||||||
|
token: token ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
CloudflareApi api({final bool getInitialized = true}) => getInitialized
|
CloudflareApi api({final bool getInitialized = true}) => getInitialized
|
||||||
|
@ -28,8 +30,10 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
||||||
CloudflareDnsProvider.load(
|
CloudflareDnsProvider.load(
|
||||||
final bool isAuthorized,
|
final bool isAuthorized,
|
||||||
|
final String? token,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthorized,
|
isWithToken: isAuthorized,
|
||||||
|
token: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
@ -38,7 +42,7 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
DnsProviderType get type => DnsProviderType.cloudflare;
|
DnsProviderType get type => DnsProviderType.cloudflare;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get howToRegistar => 'how_fix_domain_cloudflare';
|
String get howToRegister => 'how_fix_domain_cloudflare';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||||
|
@ -47,8 +51,6 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
if (!result.data || !result.success) {
|
if (!result.data || !result.success) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_adapter = ApiAdapter(isWithToken: true);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +308,7 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
_adapter = ApiAdapter(
|
_adapter = ApiAdapter(
|
||||||
isWithToken: true,
|
isWithToken: true,
|
||||||
|
token: _adapter.api().token,
|
||||||
cachedDomain: domain,
|
cachedDomain: domain,
|
||||||
cachedZoneId: getZoneIdResult.data!,
|
cachedZoneId: getZoneIdResult.data!,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,9 +5,10 @@ import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final bool isWithToken = true})
|
ApiAdapter({final bool isWithToken = true, final String? token})
|
||||||
: _api = DesecApi(
|
: _api = DesecApi(
|
||||||
isWithToken: isWithToken,
|
isWithToken: isWithToken,
|
||||||
|
token: token ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
DesecApi api({final bool getInitialized = true}) => getInitialized
|
DesecApi api({final bool getInitialized = true}) => getInitialized
|
||||||
|
@ -23,17 +24,19 @@ class DesecDnsProvider extends DnsProvider {
|
||||||
DesecDnsProvider() : _adapter = ApiAdapter();
|
DesecDnsProvider() : _adapter = ApiAdapter();
|
||||||
DesecDnsProvider.load(
|
DesecDnsProvider.load(
|
||||||
final bool isAuthorized,
|
final bool isAuthorized,
|
||||||
|
final String? token,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthorized,
|
isWithToken: isAuthorized,
|
||||||
|
token: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
final ApiAdapter _adapter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DnsProviderType get type => DnsProviderType.desec;
|
DnsProviderType get type => DnsProviderType.desec;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get howToRegistar => 'how_fix_domain_desec';
|
String get howToRegister => 'how_fix_domain_desec';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||||
|
@ -42,8 +45,6 @@ class DesecDnsProvider extends DnsProvider {
|
||||||
if (!result.data || !result.success) {
|
if (!result.data || !result.success) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_adapter = ApiAdapter(isWithToken: true);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final bool isWithToken = true})
|
ApiAdapter({final bool isWithToken = true, final String? token})
|
||||||
: _api = DigitalOceanDnsApi(
|
: _api = DigitalOceanDnsApi(
|
||||||
isWithToken: isWithToken,
|
isWithToken: isWithToken,
|
||||||
|
token: token ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
DigitalOceanDnsApi api({final bool getInitialized = true}) => getInitialized
|
DigitalOceanDnsApi api({final bool getInitialized = true}) => getInitialized
|
||||||
|
@ -23,17 +24,19 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
||||||
DigitalOceanDnsProvider.load(
|
DigitalOceanDnsProvider.load(
|
||||||
final bool isAuthorized,
|
final bool isAuthorized,
|
||||||
|
final String? token,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthorized,
|
isWithToken: isAuthorized,
|
||||||
|
token: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
final ApiAdapter _adapter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DnsProviderType get type => DnsProviderType.digitalOcean;
|
DnsProviderType get type => DnsProviderType.digitalOcean;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get howToRegistar => 'how_fix_domain_digital_ocean';
|
String get howToRegister => 'how_fix_domain_digital_ocean';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||||
|
@ -42,8 +45,6 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
if (!result.data || !result.success) {
|
if (!result.data || !result.success) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_adapter = ApiAdapter(isWithToken: true);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,12 @@ abstract class DnsProvider {
|
||||||
|
|
||||||
/// Returns a full url to a guide on how to setup
|
/// Returns a full url to a guide on how to setup
|
||||||
/// DNS provider nameservers
|
/// DNS provider nameservers
|
||||||
String get howToRegistar;
|
String get howToRegister;
|
||||||
|
|
||||||
/// Tries to access an account linked to the provided token.
|
/// Tries to access an account linked to the provided token.
|
||||||
///
|
///
|
||||||
/// To generate a token for your account follow instructions of your
|
/// To generate a token for your account follow instructions of your
|
||||||
/// DNS provider respectfully.
|
/// DNS provider respectfully.
|
||||||
///
|
|
||||||
/// If success, saves it for future usage.
|
|
||||||
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||||
|
|
||||||
/// Returns list of all available domain entries assigned to the account.
|
/// Returns list of all available domain entries assigned to the account.
|
||||||
|
|
|
@ -19,18 +19,21 @@ class DnsProviderFactory {
|
||||||
return settings.isAuthorized
|
return settings.isAuthorized
|
||||||
? CloudflareDnsProvider.load(
|
? CloudflareDnsProvider.load(
|
||||||
settings.isAuthorized,
|
settings.isAuthorized,
|
||||||
|
settings.token,
|
||||||
)
|
)
|
||||||
: CloudflareDnsProvider();
|
: CloudflareDnsProvider();
|
||||||
case DnsProviderType.digitalOcean:
|
case DnsProviderType.digitalOcean:
|
||||||
return settings.isAuthorized
|
return settings.isAuthorized
|
||||||
? DigitalOceanDnsProvider.load(
|
? DigitalOceanDnsProvider.load(
|
||||||
settings.isAuthorized,
|
settings.isAuthorized,
|
||||||
|
settings.token,
|
||||||
)
|
)
|
||||||
: DigitalOceanDnsProvider();
|
: DigitalOceanDnsProvider();
|
||||||
case DnsProviderType.desec:
|
case DnsProviderType.desec:
|
||||||
return settings.isAuthorized
|
return settings.isAuthorized
|
||||||
? DesecDnsProvider.load(
|
? DesecDnsProvider.load(
|
||||||
settings.isAuthorized,
|
settings.isAuthorized,
|
||||||
|
settings.token,
|
||||||
)
|
)
|
||||||
: DesecDnsProvider();
|
: DesecDnsProvider();
|
||||||
case DnsProviderType.unknown:
|
case DnsProviderType.unknown:
|
||||||
|
|
|
@ -1,24 +1,43 @@
|
||||||
|
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';
|
||||||
|
|
||||||
class ServerProviderSettings {
|
class ServerProviderSettings {
|
||||||
ServerProviderSettings({
|
ServerProviderSettings({
|
||||||
required this.provider,
|
required this.provider,
|
||||||
|
this.token,
|
||||||
this.isAuthorized = false,
|
this.isAuthorized = false,
|
||||||
this.location,
|
this.location,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isAuthorized;
|
final bool isAuthorized;
|
||||||
final ServerProviderType provider;
|
final ServerProviderType provider;
|
||||||
|
final String? token;
|
||||||
final String? location;
|
final String? location;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DnsProviderSettings {
|
class DnsProviderSettings {
|
||||||
DnsProviderSettings({
|
DnsProviderSettings({
|
||||||
required this.provider,
|
required this.provider,
|
||||||
|
this.token,
|
||||||
this.isAuthorized = false,
|
this.isAuthorized = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isAuthorized;
|
final bool isAuthorized;
|
||||||
final DnsProviderType provider;
|
final DnsProviderType provider;
|
||||||
|
final String? token;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsProviderSettings {
|
||||||
|
BackupsProviderSettings({
|
||||||
|
required this.provider,
|
||||||
|
this.tokenId,
|
||||||
|
this.token,
|
||||||
|
this.isAuthorized = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isAuthorized;
|
||||||
|
final BackupsProviderType provider;
|
||||||
|
final String? tokenId;
|
||||||
|
final String? token;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/backups_providers/backups_provider_factory.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider_factory.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider_factory.dart';
|
||||||
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
||||||
|
@ -7,6 +9,7 @@ import 'package:selfprivacy/logic/providers/server_providers/server_provider_fac
|
||||||
class ProvidersController {
|
class ProvidersController {
|
||||||
static ServerProvider? get currentServerProvider => _serverProvider;
|
static ServerProvider? get currentServerProvider => _serverProvider;
|
||||||
static DnsProvider? get currentDnsProvider => _dnsProvider;
|
static DnsProvider? get currentDnsProvider => _dnsProvider;
|
||||||
|
static BackupsProvider? get currentBackupsProvider => _backupsProvider;
|
||||||
|
|
||||||
static void initServerProvider(
|
static void initServerProvider(
|
||||||
final ServerProviderSettings settings,
|
final ServerProviderSettings settings,
|
||||||
|
@ -24,11 +27,21 @@ class ProvidersController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void initBackupsProvider(
|
||||||
|
final BackupsProviderSettings settings,
|
||||||
|
) {
|
||||||
|
_backupsProvider = BackupsProviderFactory.createBackupsProviderInterface(
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static void clearProviders() {
|
static void clearProviders() {
|
||||||
_serverProvider = null;
|
_serverProvider = null;
|
||||||
_dnsProvider = null;
|
_dnsProvider = null;
|
||||||
|
_backupsProvider = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ServerProvider? _serverProvider;
|
static ServerProvider? _serverProvider;
|
||||||
static DnsProvider? _dnsProvider;
|
static DnsProvider? _dnsProvider;
|
||||||
|
static BackupsProvider? _backupsProvider;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,14 @@ import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final String? region, final bool isWithToken = true})
|
ApiAdapter({
|
||||||
: _api = DigitalOceanApi(
|
final String? region,
|
||||||
|
final bool isWithToken = true,
|
||||||
|
final String? token,
|
||||||
|
}) : _api = DigitalOceanApi(
|
||||||
region: region,
|
region: region,
|
||||||
isWithToken: isWithToken,
|
isWithToken: isWithToken,
|
||||||
|
token: token ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
DigitalOceanApi api({final bool getInitialized = true}) => getInitialized
|
DigitalOceanApi api({final bool getInitialized = true}) => getInitialized
|
||||||
|
@ -39,9 +43,11 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
DigitalOceanServerProvider.load(
|
DigitalOceanServerProvider.load(
|
||||||
final String? location,
|
final String? location,
|
||||||
final bool isAuthorized,
|
final bool isAuthorized,
|
||||||
|
final String? token,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthorized,
|
isWithToken: isAuthorized,
|
||||||
region: location,
|
region: location,
|
||||||
|
token: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
@ -408,10 +414,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_adapter = ApiAdapter(
|
// _adapter = ApiAdapter(
|
||||||
isWithToken: true,
|
// isWithToken: true,
|
||||||
region: location,
|
// region: location,
|
||||||
);
|
// );
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,14 @@ import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final String? region, final bool isWithToken = true})
|
ApiAdapter({
|
||||||
: _api = HetznerApi(
|
final String? region,
|
||||||
|
final bool isWithToken = true,
|
||||||
|
final String? token,
|
||||||
|
}) : _api = HetznerApi(
|
||||||
region: region,
|
region: region,
|
||||||
isWithToken: isWithToken,
|
isWithToken: isWithToken,
|
||||||
|
token: token ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
HetznerApi api({final bool getInitialized = true}) => getInitialized
|
HetznerApi api({final bool getInitialized = true}) => getInitialized
|
||||||
|
@ -39,9 +43,11 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
HetznerServerProvider.load(
|
HetznerServerProvider.load(
|
||||||
final String? location,
|
final String? location,
|
||||||
final bool isAuthorized,
|
final bool isAuthorized,
|
||||||
|
final String? token,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthorized,
|
isWithToken: isAuthorized,
|
||||||
region: location,
|
region: location,
|
||||||
|
token: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
@ -410,7 +416,7 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_adapter = ApiAdapter(region: api.region, isWithToken: true);
|
// _adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,6 @@ abstract class ServerProvider {
|
||||||
///
|
///
|
||||||
/// To generate a token for your account follow instructions of your
|
/// To generate a token for your account follow instructions of your
|
||||||
/// server provider respectfully.
|
/// server provider respectfully.
|
||||||
///
|
|
||||||
/// If success, saves it for future usage.
|
|
||||||
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||||
|
|
||||||
/// Tries to assign the location shortcode for future usage.
|
/// Tries to assign the location shortcode for future usage.
|
||||||
|
|
|
@ -19,6 +19,7 @@ class ServerProviderFactory {
|
||||||
? HetznerServerProvider.load(
|
? HetznerServerProvider.load(
|
||||||
settings.location,
|
settings.location,
|
||||||
settings.isAuthorized,
|
settings.isAuthorized,
|
||||||
|
settings.token,
|
||||||
)
|
)
|
||||||
: HetznerServerProvider();
|
: HetznerServerProvider();
|
||||||
case ServerProviderType.digitalOcean:
|
case ServerProviderType.digitalOcean:
|
||||||
|
@ -26,6 +27,7 @@ class ServerProviderFactory {
|
||||||
? DigitalOceanServerProvider.load(
|
? DigitalOceanServerProvider.load(
|
||||||
settings.location,
|
settings.location,
|
||||||
settings.isAuthorized,
|
settings.isAuthorized,
|
||||||
|
settings.token,
|
||||||
)
|
)
|
||||||
: DigitalOceanServerProvider();
|
: DigitalOceanServerProvider();
|
||||||
case ServerProviderType.unknown:
|
case ServerProviderType.unknown:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/tokens/tokens_bloc.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/models/backup.dart';
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
@ -54,6 +55,8 @@ class BackupDetailsPage extends StatelessWidget {
|
||||||
.where((final job) => job.status != JobStatusEnum.finished)
|
.where((final job) => job.status != JobStatusEnum.finished)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
final TokensState tokensState = context.watch<TokensBloc>().state;
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
return BrandHeroScreen(
|
return BrandHeroScreen(
|
||||||
heroIcon: BrandIcons.save,
|
heroIcon: BrandIcons.save,
|
||||||
|
@ -69,7 +72,7 @@ class BackupDetailsPage extends StatelessWidget {
|
||||||
heroTitle: 'backup.card_title'.tr(),
|
heroTitle: 'backup.card_title'.tr(),
|
||||||
heroSubtitle: 'backup.description'.tr(),
|
heroSubtitle: 'backup.description'.tr(),
|
||||||
children: [
|
children: [
|
||||||
if (preventActions)
|
if (preventActions || tokensState.backupsCredentials.isEmpty)
|
||||||
const Center(
|
const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
|
@ -81,9 +84,11 @@ class BackupDetailsPage extends StatelessWidget {
|
||||||
onPressed: preventActions
|
onPressed: preventActions
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
context
|
context.read<BackupsBloc>().add(
|
||||||
.read<BackupsBloc>()
|
InitializeBackupsRepository(
|
||||||
.add(const InitializeBackupsRepository());
|
tokensState.backupsCredentials.first.data,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
text: 'backup.initialize'.tr(),
|
text: 'backup.initialize'.tr(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -59,6 +59,11 @@ class MorePage extends StatelessWidget {
|
||||||
goTo: () => const DevicesRoute(),
|
goTo: () => const DevicesRoute(),
|
||||||
title: 'devices.main_screen.header'.tr(),
|
title: 'devices.main_screen.header'.tr(),
|
||||||
),
|
),
|
||||||
|
_MoreMenuItem(
|
||||||
|
iconData: Icons.token_outlined,
|
||||||
|
title: 'tokens.title'.tr(),
|
||||||
|
goTo: () => const TokensRoute(),
|
||||||
|
),
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'application_settings.title'.tr(),
|
title: 'application_settings.title'.tr(),
|
||||||
iconData: Icons.settings_outlined,
|
iconData: Icons.settings_outlined,
|
||||||
|
|
262
lib/ui/pages/more/tokens/tokens_page.dart
Normal file
262
lib/ui/pages/more/tokens/tokens_page.dart
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/tokens/tokens_bloc.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_provider_credential.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class TokensPage extends StatelessWidget {
|
||||||
|
const TokensPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final TokensState state = context.watch<TokensBloc>().state;
|
||||||
|
return BrandHeroScreen(
|
||||||
|
heroTitle: 'tokens.title'.tr(),
|
||||||
|
heroSubtitle: 'tokens.description'.tr(),
|
||||||
|
heroIcon: Icons.token_outlined,
|
||||||
|
children: [
|
||||||
|
FilledCard(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title: 'tokens.server_provider_tokens'.tr(),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 0,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
if (state.serverProviderCredentials.isEmpty)
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title: 'tokens.no_tokens'.tr(),
|
||||||
|
leadingIcon: Icons.warning_amber_outlined,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: state.serverProviderCredentials
|
||||||
|
.map(
|
||||||
|
(final serverProviderCredential) =>
|
||||||
|
_ServerProviderListItem(
|
||||||
|
serverProviderCredential: serverProviderCredential,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
FilledCard(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title: 'tokens.dns_provider_tokens'.tr(),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 0,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
if (state.dnsProviderCredentials.isEmpty)
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title: 'tokens.no_tokens'.tr(),
|
||||||
|
leadingIcon: Icons.warning_amber_outlined,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: state.dnsProviderCredentials
|
||||||
|
.map(
|
||||||
|
(final dnsProviderCredential) => _DnsProviderListItem(
|
||||||
|
dnsProviderCredential: dnsProviderCredential,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
FilledCard(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title: 'tokens.backup_provider_tokens'.tr(),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 0,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
if (state.backupsCredentials.isEmpty)
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title: 'tokens.no_tokens'.tr(),
|
||||||
|
leadingIcon: Icons.warning_amber_outlined,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: state.backupsCredentials
|
||||||
|
.map(
|
||||||
|
(final backupProviderCredential) =>
|
||||||
|
_BackupProviderListItem(
|
||||||
|
backupProviderCredential: backupProviderCredential,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
ListTile(
|
||||||
|
title: Text('tokens.check_again'.tr()),
|
||||||
|
onTap: (state is TokensInitial)
|
||||||
|
? null
|
||||||
|
: () => context.read<TokensBloc>().add(const RevalidateTokens()),
|
||||||
|
leading: const Icon(Icons.refresh_outlined),
|
||||||
|
enabled: state is! TokensInitial,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServerProviderListItem extends StatelessWidget {
|
||||||
|
const _ServerProviderListItem({
|
||||||
|
required this.serverProviderCredential,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TokenStatusWrapper<ServerProviderCredential> serverProviderCredential;
|
||||||
|
|
||||||
|
String getSubtitle(final BuildContext context) {
|
||||||
|
String subtitle = '';
|
||||||
|
subtitle += serverProviderCredential.status.statusText;
|
||||||
|
if (serverProviderCredential.data.associatedServerIds.isNotEmpty) {
|
||||||
|
final String serverDomains =
|
||||||
|
serverProviderCredential.data.associatedServerIds
|
||||||
|
.map(
|
||||||
|
(final int serverId) => context
|
||||||
|
.read<TokensBloc>()
|
||||||
|
.state
|
||||||
|
.getServerById(serverId)
|
||||||
|
.domain
|
||||||
|
.domainName,
|
||||||
|
)
|
||||||
|
.join(', ');
|
||||||
|
subtitle +=
|
||||||
|
'. ${'tokens.used_by'.tr(namedArgs: {'servers': serverDomains})}';
|
||||||
|
}
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
children: [
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title:
|
||||||
|
'${serverProviderCredential.data.provider.displayName} (${serverProviderCredential.data.tokenPrefix}...)',
|
||||||
|
subtitle: getSubtitle(context),
|
||||||
|
leadingIcon: serverProviderCredential.status.icon,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DnsProviderListItem extends StatelessWidget {
|
||||||
|
const _DnsProviderListItem({
|
||||||
|
required this.dnsProviderCredential,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TokenStatusWrapper<DnsProviderCredential> dnsProviderCredential;
|
||||||
|
|
||||||
|
String getSubtitle(final BuildContext context) {
|
||||||
|
String subtitle = '';
|
||||||
|
subtitle += dnsProviderCredential.status.statusText;
|
||||||
|
if (dnsProviderCredential.data.associatedDomainNames.isNotEmpty) {
|
||||||
|
final String serverDomains =
|
||||||
|
dnsProviderCredential.data.associatedDomainNames.join(', ');
|
||||||
|
subtitle +=
|
||||||
|
'. ${'tokens.used_by'.tr(namedArgs: {'servers': serverDomains})}';
|
||||||
|
}
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
children: [
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title:
|
||||||
|
'${dnsProviderCredential.data.provider.displayName} (${dnsProviderCredential.data.tokenPrefix}...)',
|
||||||
|
subtitle: getSubtitle(context),
|
||||||
|
leadingIcon: dnsProviderCredential.status.icon,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackupProviderListItem extends StatelessWidget {
|
||||||
|
const _BackupProviderListItem({
|
||||||
|
required this.backupProviderCredential,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TokenStatusWrapper<BackupsCredential> backupProviderCredential;
|
||||||
|
|
||||||
|
String getSubtitle(final BuildContext context) {
|
||||||
|
String subtitle = '';
|
||||||
|
subtitle += backupProviderCredential.status.statusText;
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
children: [
|
||||||
|
ListTileOnSurfaceVariant(
|
||||||
|
title:
|
||||||
|
'${backupProviderCredential.data.provider.name} (${backupProviderCredential.data.tokenPrefix})',
|
||||||
|
subtitle: getSubtitle(context),
|
||||||
|
leadingIcon: backupProviderCredential.status.icon,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on TokenStatus {
|
||||||
|
String get statusText {
|
||||||
|
switch (this) {
|
||||||
|
case TokenStatus.valid:
|
||||||
|
return 'tokens.valid'.tr();
|
||||||
|
case TokenStatus.invalid:
|
||||||
|
return 'tokens.invalid'.tr();
|
||||||
|
case TokenStatus.noAccess:
|
||||||
|
return 'tokens.no_access'.tr();
|
||||||
|
case TokenStatus.loading:
|
||||||
|
return 'tokens.loading'.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case TokenStatus.valid:
|
||||||
|
return Icons.check_circle_outlined;
|
||||||
|
case TokenStatus.invalid:
|
||||||
|
return Icons.error_outline_outlined;
|
||||||
|
case TokenStatus.noAccess:
|
||||||
|
return Icons.no_encryption_outlined;
|
||||||
|
case TokenStatus.loading:
|
||||||
|
return Icons.sync_outlined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on ServerProviderCredential {
|
||||||
|
String get tokenPrefix => tokenId ?? token.substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on DnsProviderCredential {
|
||||||
|
String get tokenPrefix => tokenId ?? token.substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on BackupsCredential {
|
||||||
|
String get tokenPrefix => keyId;
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ class BrokenDomainOutlinedCard extends StatelessWidget {
|
||||||
child: InkResponse(
|
child: InkResponse(
|
||||||
highlightShape: BoxShape.rectangle,
|
highlightShape: BoxShape.rectangle,
|
||||||
onTap: () => context.read<SupportSystemCubit>().showArticle(
|
onTap: () => context.read<SupportSystemCubit>().showArticle(
|
||||||
article: dnsProvider.howToRegistar,
|
article: dnsProvider.howToRegister,
|
||||||
context: context,
|
context: context,
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:selfprivacy/ui/pages/more/app_settings/app_settings.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/app_settings/developer_settings.dart';
|
import 'package:selfprivacy/ui/pages/more/app_settings/developer_settings.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/console/console_page.dart';
|
import 'package:selfprivacy/ui/pages/more/console/console_page.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/more.dart';
|
import 'package:selfprivacy/ui/pages/more/more.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/more/tokens/tokens_page.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
||||||
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
|
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
|
||||||
|
@ -107,6 +108,7 @@ class RootRouter extends _$RootRouter {
|
||||||
AutoRoute(page: ExtendingVolumeRoute.page),
|
AutoRoute(page: ExtendingVolumeRoute.page),
|
||||||
AutoRoute(page: ServerSettingsRoute.page),
|
AutoRoute(page: ServerSettingsRoute.page),
|
||||||
AutoRoute(page: ServerLogsRoute.page),
|
AutoRoute(page: ServerLogsRoute.page),
|
||||||
|
AutoRoute(page: TokensRoute.page),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(page: ServicesMigrationRoute.page),
|
AutoRoute(page: ServicesMigrationRoute.page),
|
||||||
|
@ -162,6 +164,8 @@ String getRouteTitle(final String routeName) {
|
||||||
return 'storage.card_title';
|
return 'storage.card_title';
|
||||||
case 'ExtendingVolumeRoute':
|
case 'ExtendingVolumeRoute':
|
||||||
return 'storage.extending_volume_title';
|
return 'storage.extending_volume_title';
|
||||||
|
case 'TokensRoute':
|
||||||
|
return 'tokens.title';
|
||||||
default:
|
default:
|
||||||
return routeName;
|
return routeName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,12 @@ abstract class _$RootRouter extends RootStackRouter {
|
||||||
child: const ServicesPage(),
|
child: const ServicesPage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
TokensRoute.name: (routeData) {
|
||||||
|
return AutoRoutePage<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: const TokensPage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
UserDetailsRoute.name: (routeData) {
|
UserDetailsRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<UserDetailsRouteArgs>();
|
final args = routeData.argsAs<UserDetailsRouteArgs>();
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
|
@ -720,6 +726,20 @@ class ServicesRoute extends PageRouteInfo<void> {
|
||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [TokensPage]
|
||||||
|
class TokensRoute extends PageRouteInfo<void> {
|
||||||
|
const TokensRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(
|
||||||
|
TokensRoute.name,
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'TokensRoute';
|
||||||
|
|
||||||
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [UserDetailsPage]
|
/// [UserDetailsPage]
|
||||||
class UserDetailsRoute extends PageRouteInfo<UserDetailsRouteArgs> {
|
class UserDetailsRoute extends PageRouteInfo<UserDetailsRouteArgs> {
|
||||||
|
|
Loading…
Add table
Reference in a new issue