mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-19 07:09:14 +00:00
feat: BackupsProvider and TokensBloc
This commit is contained in:
parent
1c7724347f
commit
74eb1135df
|
@ -668,5 +668,19 @@
|
|||
"australia": "Australia",
|
||||
"united_states": "United States",
|
||||
"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_logs/server_logs_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/volumes/volumes_bloc.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 ServerLogsBloc serverLogsBloc;
|
||||
late final OutdatedServerCheckerBloc outdatedServerCheckerBloc;
|
||||
late final TokensBloc tokensBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -58,6 +60,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
|||
volumesBloc = VolumesBloc();
|
||||
serverLogsBloc = ServerLogsBloc();
|
||||
outdatedServerCheckerBloc = OutdatedServerCheckerBloc();
|
||||
tokensBloc = TokensBloc();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -106,6 +109,9 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
|||
BlocProvider(
|
||||
create: (final _) => outdatedServerCheckerBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => tokensBloc,
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
|
|
|
@ -29,7 +29,20 @@ class BackblazeApplicationKey {
|
|||
}
|
||||
|
||||
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
|
||||
BaseOptions get options {
|
||||
|
@ -39,10 +52,8 @@ class BackblazeApi extends RestApiMap {
|
|||
responseType: ResponseType.json,
|
||||
);
|
||||
if (isWithToken) {
|
||||
final BackupsCredential? backblazeCredential =
|
||||
getIt<ResourcesModel>().backblazeCredential;
|
||||
final String token = backblazeCredential!.applicationKey;
|
||||
options.headers = {'Authorization': 'Basic $token'};
|
||||
final encodedApiKey = encodedBackblazeKey(tokenId, token);
|
||||
options.headers = {'Authorization': 'Basic $encodedApiKey'};
|
||||
}
|
||||
|
||||
if (validateStatus != null) {
|
||||
|
@ -59,14 +70,12 @@ class BackblazeApi extends RestApiMap {
|
|||
|
||||
Future<BackblazeApiAuth> getAuthorizationToken() async {
|
||||
final Dio client = await getClient();
|
||||
final BackupsCredential? backblazeCredential =
|
||||
getIt<ResourcesModel>().backblazeCredential;
|
||||
if (backblazeCredential == null) {
|
||||
if (token.isEmpty || tokenId.isEmpty) {
|
||||
throw Exception('Backblaze credential is null');
|
||||
}
|
||||
final String encodedApiKey = encodedBackblazeKey(
|
||||
backblazeCredential.keyId,
|
||||
backblazeCredential.applicationKey,
|
||||
tokenId,
|
||||
token,
|
||||
);
|
||||
final Response response = await client.get(
|
||||
'b2_authorize_account',
|
||||
|
@ -122,16 +131,14 @@ class BackblazeApi extends RestApiMap {
|
|||
}
|
||||
|
||||
// Create bucket
|
||||
Future<String> createBucket(final String bucketName) async {
|
||||
Future<GenericResult<String>> createBucket(final String bucketName) async {
|
||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||
final BackupsCredential? backblazeCredential =
|
||||
getIt<ResourcesModel>().backblazeCredential;
|
||||
final Dio client = await getClient();
|
||||
client.options.baseUrl = auth.apiUrl;
|
||||
final Response response = await client.post(
|
||||
'$apiPrefix/b2_create_bucket',
|
||||
data: {
|
||||
'accountId': backblazeCredential!.keyId,
|
||||
'accountId': tokenId,
|
||||
'bucketName': bucketName,
|
||||
'bucketType': 'allPrivate',
|
||||
'lifecycleRules': [
|
||||
|
@ -148,14 +155,23 @@ class BackblazeApi extends RestApiMap {
|
|||
);
|
||||
close(client);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return response.data['bucketId'];
|
||||
return GenericResult(
|
||||
data: response.data['bucketId'],
|
||||
success: true,
|
||||
);
|
||||
} 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
|
||||
Future<BackblazeApplicationKey> createKey(final String bucketId) async {
|
||||
Future<GenericResult<BackblazeApplicationKey>> createKey(
|
||||
final String bucketId,
|
||||
) async {
|
||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||
final Dio client = await getClient();
|
||||
client.options.baseUrl = auth.apiUrl;
|
||||
|
@ -173,16 +189,26 @@ class BackblazeApi extends RestApiMap {
|
|||
);
|
||||
close(client);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return BackblazeApplicationKey(
|
||||
applicationKeyId: response.data['applicationKeyId'],
|
||||
applicationKey: response.data['applicationKey'],
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: BackblazeApplicationKey(
|
||||
applicationKeyId: response.data['applicationKeyId'],
|
||||
applicationKey: response.data['applicationKey'],
|
||||
),
|
||||
);
|
||||
} 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 BackupConfiguration configuration,
|
||||
) async {
|
||||
|
@ -212,15 +238,16 @@ class BackblazeApi extends RestApiMap {
|
|||
);
|
||||
}
|
||||
}
|
||||
return bucket;
|
||||
return GenericResult(
|
||||
success: bucket != null,
|
||||
data: bucket,
|
||||
);
|
||||
} 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 '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/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';
|
||||
|
||||
class CloudflareApi extends RestApiMap {
|
||||
CloudflareApi({
|
||||
this.token = '',
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
this.customToken,
|
||||
});
|
||||
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||
|
||||
@override
|
||||
final bool hasLogger;
|
||||
@override
|
||||
final bool isWithToken;
|
||||
|
||||
final String token;
|
||||
final String? customToken;
|
||||
|
||||
@override
|
||||
|
@ -28,8 +29,7 @@ class CloudflareApi extends RestApiMap {
|
|||
responseType: ResponseType.json,
|
||||
);
|
||||
if (isWithToken) {
|
||||
final String? token = getIt<ResourcesModel>().dnsProviderKey;
|
||||
assert(token != null);
|
||||
assert(token.isNotEmpty);
|
||||
options.headers = {'Authorization': 'Bearer $token'};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/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';
|
||||
|
||||
class DesecApi extends RestApiMap {
|
||||
DesecApi({
|
||||
this.token = '',
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
this.customToken,
|
||||
});
|
||||
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||
|
||||
@override
|
||||
final bool hasLogger;
|
||||
@override
|
||||
final bool isWithToken;
|
||||
|
||||
final String token;
|
||||
final String? customToken;
|
||||
|
||||
@override
|
||||
|
@ -28,8 +29,7 @@ class DesecApi extends RestApiMap {
|
|||
responseType: ResponseType.json,
|
||||
);
|
||||
if (isWithToken) {
|
||||
final String? token = getIt<ResourcesModel>().dnsProviderKey;
|
||||
assert(token != null);
|
||||
assert(token.isNotEmpty);
|
||||
options.headers = {'Authorization': 'Token $token'};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/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';
|
||||
|
||||
class DigitalOceanDnsApi extends RestApiMap {
|
||||
DigitalOceanDnsApi({
|
||||
this.token = '',
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
this.customToken,
|
||||
});
|
||||
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||
|
||||
@override
|
||||
final bool hasLogger;
|
||||
@override
|
||||
final bool isWithToken;
|
||||
|
||||
final String token;
|
||||
final String? customToken;
|
||||
|
||||
@override
|
||||
|
@ -28,8 +29,7 @@ class DigitalOceanDnsApi extends RestApiMap {
|
|||
responseType: ResponseType.json,
|
||||
);
|
||||
if (isWithToken) {
|
||||
final String? token = getIt<ResourcesModel>().dnsProviderKey;
|
||||
assert(token != null);
|
||||
assert(token.isNotEmpty);
|
||||
options.headers = {'Authorization': 'Bearer $token'};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/rest_maps/rest_api_map.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/json/digital_ocean_server_info.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
@ -13,15 +11,18 @@ import 'package:selfprivacy/utils/password_generator.dart';
|
|||
class DigitalOceanApi extends RestApiMap {
|
||||
DigitalOceanApi({
|
||||
required this.region,
|
||||
this.token = '',
|
||||
this.hasLogger = true,
|
||||
this.isWithToken = true,
|
||||
});
|
||||
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||
|
||||
@override
|
||||
bool hasLogger;
|
||||
@override
|
||||
bool isWithToken;
|
||||
|
||||
final String? region;
|
||||
final String token;
|
||||
|
||||
@override
|
||||
BaseOptions get options {
|
||||
|
@ -31,8 +32,7 @@ class DigitalOceanApi extends RestApiMap {
|
|||
responseType: ResponseType.json,
|
||||
);
|
||||
if (isWithToken) {
|
||||
final String? token = getIt<ResourcesModel>().serverProviderKey;
|
||||
assert(token != null);
|
||||
assert(token.isNotEmpty);
|
||||
options.headers = {'Authorization': 'Bearer $token'};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/rest_maps/rest_api_map.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/hive/user.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 {
|
||||
HetznerApi({
|
||||
this.region,
|
||||
this.token = '',
|
||||
this.hasLogger = true,
|
||||
this.isWithToken = true,
|
||||
});
|
||||
}) : assert(isWithToken ? token.isNotEmpty : true);
|
||||
|
||||
@override
|
||||
bool hasLogger;
|
||||
@override
|
||||
bool isWithToken;
|
||||
|
||||
final String? region;
|
||||
final String token;
|
||||
|
||||
@override
|
||||
BaseOptions get options {
|
||||
|
@ -32,8 +33,7 @@ class HetznerApi extends RestApiMap {
|
|||
responseType: ResponseType.json,
|
||||
);
|
||||
if (isWithToken) {
|
||||
final String? token = getIt<ResourcesModel>().serverProviderKey;
|
||||
assert(token != null);
|
||||
assert(token.isNotEmpty);
|
||||
options.headers = {'Authorization': 'Bearer $token'};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.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/api_maps/rest_maps/backblaze.dart';
|
||||
import 'package:selfprivacy/logic/get_it/resources_model.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/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/json/server_job.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_state.dart';
|
||||
|
@ -103,8 +105,6 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
|||
}
|
||||
}
|
||||
|
||||
final BackblazeApi backblaze = BackblazeApi();
|
||||
|
||||
Future<void> _loadState(
|
||||
final BackupsServerLoaded event,
|
||||
final Emitter<BackupsState> emit,
|
||||
|
@ -166,6 +166,14 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
|||
final BackblazeBucket bucket;
|
||||
|
||||
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>()
|
||||
.serverDomain!
|
||||
.domainName
|
||||
|
@ -176,9 +184,30 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
|||
if (bucketName.length > 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(
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
|
|
|
@ -19,7 +19,11 @@ class BackupsServerReset extends BackupsEvent {
|
|||
}
|
||||
|
||||
class InitializeBackupsRepository extends BackupsEvent {
|
||||
const InitializeBackupsRepository();
|
||||
const InitializeBackupsRepository(
|
||||
this.credential,
|
||||
);
|
||||
|
||||
final BackupsCredential credential;
|
||||
|
||||
@override
|
||||
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: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/rest_maps/backblaze.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/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_provider_location.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/providers_controller.dart';
|
||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||
|
@ -234,8 +234,17 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
).getBackupsConfiguration();
|
||||
if (configuration != null) {
|
||||
try {
|
||||
bucket = await BackblazeApi()
|
||||
.fetchBucket(backblazeCredential, configuration);
|
||||
final settings = BackupsProviderSettings(
|
||||
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!);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
|
|
@ -66,6 +66,7 @@ class ServerInstallationRepository {
|
|||
provider: serverProvider ?? serverDetails!.provider,
|
||||
isAuthorized: providerApiToken != null,
|
||||
location: location,
|
||||
token: providerApiToken,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -77,6 +78,7 @@ class ServerInstallationRepository {
|
|||
DnsProviderSettings(
|
||||
isAuthorized: dnsApiToken != null,
|
||||
provider: dnsProvider ?? serverDomain!.provider,
|
||||
token: dnsApiToken,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -461,6 +463,14 @@ class ServerInstallationRepository {
|
|||
associatedServerIds: [],
|
||||
),
|
||||
);
|
||||
ProvidersController.initServerProvider(
|
||||
ServerProviderSettings(
|
||||
provider:
|
||||
getIt<WizardDataModel>().serverInstallation!.serverProviderType!,
|
||||
token: key,
|
||||
isAuthorized: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> saveServerType(final ServerType serverType) async {
|
||||
|
@ -488,6 +498,13 @@ class ServerInstallationRepository {
|
|||
associatedDomainNames: [],
|
||||
),
|
||||
);
|
||||
ProvidersController.initDnsProvider(
|
||||
DnsProviderSettings(
|
||||
provider: getIt<WizardDataModel>().serverInstallation!.dnsProviderType!,
|
||||
token: key,
|
||||
isAuthorized: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.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/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 {
|
||||
final Box _box = Hive.box(BNames.resourcesBox);
|
||||
|
||||
final _statusStreamController =
|
||||
StreamController<ResourcesModelEvent>.broadcast();
|
||||
|
||||
Stream<ResourcesModelEvent> get statusStream =>
|
||||
_statusStreamController.stream;
|
||||
|
||||
List<ServerProviderCredential> get serverProviderCredentials =>
|
||||
_serverProviderTokens;
|
||||
List<DnsProviderCredential> get dnsProviderCredentials => _dnsProviderTokens;
|
||||
|
@ -55,6 +91,7 @@ class ResourcesModel {
|
|||
) async {
|
||||
_serverProviderTokens.add(token);
|
||||
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
||||
_statusStreamController.add(const ChangedServerProviderCredentials());
|
||||
}
|
||||
|
||||
Future<void> associateServerWithToken(
|
||||
|
@ -68,6 +105,7 @@ class ResourcesModel {
|
|||
.associatedServerIds
|
||||
.add(serverId);
|
||||
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
||||
_statusStreamController.add(const ChangedServerProviderCredentials());
|
||||
}
|
||||
|
||||
Future<void> removeServerProviderToken(
|
||||
|
@ -75,6 +113,7 @@ class ResourcesModel {
|
|||
) async {
|
||||
_serverProviderTokens.remove(token);
|
||||
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
|
||||
_statusStreamController.add(const ChangedServerProviderCredentials());
|
||||
}
|
||||
|
||||
Future<void> addDnsProviderToken(final DnsProviderCredential token) async {
|
||||
|
@ -85,6 +124,7 @@ class ResourcesModel {
|
|||
}
|
||||
_dnsProviderTokens.add(token);
|
||||
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
||||
_statusStreamController.add(const ChangedDnsProviderCredentials());
|
||||
}
|
||||
|
||||
Future<void> associateDomainWithToken(
|
||||
|
@ -98,16 +138,19 @@ class ResourcesModel {
|
|||
.associatedDomainNames
|
||||
.add(domain);
|
||||
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
||||
_statusStreamController.add(const ChangedDnsProviderCredentials());
|
||||
}
|
||||
|
||||
Future<void> removeDnsProviderToken(final DnsProviderCredential token) async {
|
||||
_dnsProviderTokens.remove(token);
|
||||
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
|
||||
_statusStreamController.add(const ChangedDnsProviderCredentials());
|
||||
}
|
||||
|
||||
Future<void> addBackupsCredential(final BackupsCredential credential) async {
|
||||
_backupsCredentials.add(credential);
|
||||
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
|
||||
_statusStreamController.add(const ChangedBackupsCredentials());
|
||||
}
|
||||
|
||||
Future<void> removeBackupsCredential(
|
||||
|
@ -115,16 +158,19 @@ class ResourcesModel {
|
|||
) async {
|
||||
_backupsCredentials.remove(credential);
|
||||
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
|
||||
_statusStreamController.add(const ChangedBackupsCredentials());
|
||||
}
|
||||
|
||||
Future<void> addServer(final Server server) async {
|
||||
_servers.add(server);
|
||||
await _box.put(BNames.servers, _servers);
|
||||
_statusStreamController.add(const ChangedServers());
|
||||
}
|
||||
|
||||
Future<void> removeServer(final Server server) async {
|
||||
_servers.remove(server);
|
||||
await _box.put(BNames.servers, _servers);
|
||||
_statusStreamController.add(const ChangedServers());
|
||||
}
|
||||
|
||||
Future<void> setBackblazeBucket(final BackblazeBucket bucket) async {
|
||||
|
@ -146,6 +192,12 @@ class ResourcesModel {
|
|||
|
||||
_box.clear();
|
||||
_box.compact();
|
||||
|
||||
_statusStreamController.add(const ClearedModel());
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_statusStreamController.close();
|
||||
}
|
||||
|
||||
void init() {
|
||||
|
@ -180,6 +232,8 @@ class ResourcesModel {
|
|||
.map<Server>((final e) => e as Server)
|
||||
.toList();
|
||||
_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 {
|
||||
ApiAdapter({
|
||||
final bool isWithToken = true,
|
||||
final String? token,
|
||||
this.cachedDomain = '',
|
||||
this.cachedZoneId = '',
|
||||
}) : _api = CloudflareApi(
|
||||
isWithToken: isWithToken,
|
||||
token: token ?? '',
|
||||
);
|
||||
|
||||
CloudflareApi api({final bool getInitialized = true}) => getInitialized
|
||||
|
@ -28,8 +30,10 @@ class CloudflareDnsProvider extends DnsProvider {
|
|||
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
||||
CloudflareDnsProvider.load(
|
||||
final bool isAuthorized,
|
||||
final String? token,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthorized,
|
||||
token: token,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
|
@ -38,7 +42,7 @@ class CloudflareDnsProvider extends DnsProvider {
|
|||
DnsProviderType get type => DnsProviderType.cloudflare;
|
||||
|
||||
@override
|
||||
String get howToRegistar => 'how_fix_domain_cloudflare';
|
||||
String get howToRegister => 'how_fix_domain_cloudflare';
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
|
@ -47,8 +51,6 @@ class CloudflareDnsProvider extends DnsProvider {
|
|||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -306,6 +308,7 @@ class CloudflareDnsProvider extends DnsProvider {
|
|||
|
||||
_adapter = ApiAdapter(
|
||||
isWithToken: true,
|
||||
token: _adapter.api().token,
|
||||
cachedDomain: domain,
|
||||
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';
|
||||
|
||||
class ApiAdapter {
|
||||
ApiAdapter({final bool isWithToken = true})
|
||||
ApiAdapter({final bool isWithToken = true, final String? token})
|
||||
: _api = DesecApi(
|
||||
isWithToken: isWithToken,
|
||||
token: token ?? '',
|
||||
);
|
||||
|
||||
DesecApi api({final bool getInitialized = true}) => getInitialized
|
||||
|
@ -23,17 +24,19 @@ class DesecDnsProvider extends DnsProvider {
|
|||
DesecDnsProvider() : _adapter = ApiAdapter();
|
||||
DesecDnsProvider.load(
|
||||
final bool isAuthorized,
|
||||
final String? token,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthorized,
|
||||
token: token,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
final ApiAdapter _adapter;
|
||||
|
||||
@override
|
||||
DnsProviderType get type => DnsProviderType.desec;
|
||||
|
||||
@override
|
||||
String get howToRegistar => 'how_fix_domain_desec';
|
||||
String get howToRegister => 'how_fix_domain_desec';
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
|
@ -42,8 +45,6 @@ class DesecDnsProvider extends DnsProvider {
|
|||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(isWithToken: true);
|
||||
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';
|
||||
|
||||
class ApiAdapter {
|
||||
ApiAdapter({final bool isWithToken = true})
|
||||
ApiAdapter({final bool isWithToken = true, final String? token})
|
||||
: _api = DigitalOceanDnsApi(
|
||||
isWithToken: isWithToken,
|
||||
token: token ?? '',
|
||||
);
|
||||
|
||||
DigitalOceanDnsApi api({final bool getInitialized = true}) => getInitialized
|
||||
|
@ -23,17 +24,19 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
|||
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
||||
DigitalOceanDnsProvider.load(
|
||||
final bool isAuthorized,
|
||||
final String? token,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthorized,
|
||||
token: token,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
final ApiAdapter _adapter;
|
||||
|
||||
@override
|
||||
DnsProviderType get type => DnsProviderType.digitalOcean;
|
||||
|
||||
@override
|
||||
String get howToRegistar => 'how_fix_domain_digital_ocean';
|
||||
String get howToRegister => 'how_fix_domain_digital_ocean';
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
|
@ -42,8 +45,6 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
|||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,12 @@ abstract class DnsProvider {
|
|||
|
||||
/// Returns a full url to a guide on how to setup
|
||||
/// DNS provider nameservers
|
||||
String get howToRegistar;
|
||||
String get howToRegister;
|
||||
|
||||
/// Tries to access an account linked to the provided token.
|
||||
///
|
||||
/// To generate a token for your account follow instructions of your
|
||||
/// DNS provider respectfully.
|
||||
///
|
||||
/// If success, saves it for future usage.
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||
|
||||
/// Returns list of all available domain entries assigned to the account.
|
||||
|
|
|
@ -19,18 +19,21 @@ class DnsProviderFactory {
|
|||
return settings.isAuthorized
|
||||
? CloudflareDnsProvider.load(
|
||||
settings.isAuthorized,
|
||||
settings.token,
|
||||
)
|
||||
: CloudflareDnsProvider();
|
||||
case DnsProviderType.digitalOcean:
|
||||
return settings.isAuthorized
|
||||
? DigitalOceanDnsProvider.load(
|
||||
settings.isAuthorized,
|
||||
settings.token,
|
||||
)
|
||||
: DigitalOceanDnsProvider();
|
||||
case DnsProviderType.desec:
|
||||
return settings.isAuthorized
|
||||
? DesecDnsProvider.load(
|
||||
settings.isAuthorized,
|
||||
settings.token,
|
||||
)
|
||||
: DesecDnsProvider();
|
||||
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_domain.dart';
|
||||
|
||||
class ServerProviderSettings {
|
||||
ServerProviderSettings({
|
||||
required this.provider,
|
||||
this.token,
|
||||
this.isAuthorized = false,
|
||||
this.location,
|
||||
});
|
||||
|
||||
final bool isAuthorized;
|
||||
final ServerProviderType provider;
|
||||
final String? token;
|
||||
final String? location;
|
||||
}
|
||||
|
||||
class DnsProviderSettings {
|
||||
DnsProviderSettings({
|
||||
required this.provider,
|
||||
this.token,
|
||||
this.isAuthorized = false,
|
||||
});
|
||||
|
||||
final bool isAuthorized;
|
||||
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_factory.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 {
|
||||
static ServerProvider? get currentServerProvider => _serverProvider;
|
||||
static DnsProvider? get currentDnsProvider => _dnsProvider;
|
||||
static BackupsProvider? get currentBackupsProvider => _backupsProvider;
|
||||
|
||||
static void initServerProvider(
|
||||
final ServerProviderSettings settings,
|
||||
|
@ -24,11 +27,21 @@ class ProvidersController {
|
|||
);
|
||||
}
|
||||
|
||||
static void initBackupsProvider(
|
||||
final BackupsProviderSettings settings,
|
||||
) {
|
||||
_backupsProvider = BackupsProviderFactory.createBackupsProviderInterface(
|
||||
settings,
|
||||
);
|
||||
}
|
||||
|
||||
static void clearProviders() {
|
||||
_serverProvider = null;
|
||||
_dnsProvider = null;
|
||||
_backupsProvider = null;
|
||||
}
|
||||
|
||||
static ServerProvider? _serverProvider;
|
||||
static DnsProvider? _dnsProvider;
|
||||
static BackupsProvider? _backupsProvider;
|
||||
}
|
||||
|
|
|
@ -18,10 +18,14 @@ import 'package:selfprivacy/utils/network_utils.dart';
|
|||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class ApiAdapter {
|
||||
ApiAdapter({final String? region, final bool isWithToken = true})
|
||||
: _api = DigitalOceanApi(
|
||||
ApiAdapter({
|
||||
final String? region,
|
||||
final bool isWithToken = true,
|
||||
final String? token,
|
||||
}) : _api = DigitalOceanApi(
|
||||
region: region,
|
||||
isWithToken: isWithToken,
|
||||
token: token ?? '',
|
||||
);
|
||||
|
||||
DigitalOceanApi api({final bool getInitialized = true}) => getInitialized
|
||||
|
@ -39,9 +43,11 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
DigitalOceanServerProvider.load(
|
||||
final String? location,
|
||||
final bool isAuthorized,
|
||||
final String? token,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthorized,
|
||||
region: location,
|
||||
token: token,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
|
@ -408,10 +414,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
);
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(
|
||||
isWithToken: true,
|
||||
region: location,
|
||||
);
|
||||
// _adapter = ApiAdapter(
|
||||
// isWithToken: true,
|
||||
// region: location,
|
||||
// );
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,14 @@ import 'package:selfprivacy/utils/network_utils.dart';
|
|||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class ApiAdapter {
|
||||
ApiAdapter({final String? region, final bool isWithToken = true})
|
||||
: _api = HetznerApi(
|
||||
ApiAdapter({
|
||||
final String? region,
|
||||
final bool isWithToken = true,
|
||||
final String? token,
|
||||
}) : _api = HetznerApi(
|
||||
region: region,
|
||||
isWithToken: isWithToken,
|
||||
token: token ?? '',
|
||||
);
|
||||
|
||||
HetznerApi api({final bool getInitialized = true}) => getInitialized
|
||||
|
@ -39,9 +43,11 @@ class HetznerServerProvider extends ServerProvider {
|
|||
HetznerServerProvider.load(
|
||||
final String? location,
|
||||
final bool isAuthorized,
|
||||
final String? token,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthorized,
|
||||
region: location,
|
||||
token: token,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
|
@ -410,7 +416,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
return result;
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||
// _adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,8 +56,6 @@ abstract class ServerProvider {
|
|||
///
|
||||
/// To generate a token for your account follow instructions of your
|
||||
/// server provider respectfully.
|
||||
///
|
||||
/// If success, saves it for future usage.
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||
|
||||
/// Tries to assign the location shortcode for future usage.
|
||||
|
|
|
@ -19,6 +19,7 @@ class ServerProviderFactory {
|
|||
? HetznerServerProvider.load(
|
||||
settings.location,
|
||||
settings.isAuthorized,
|
||||
settings.token,
|
||||
)
|
||||
: HetznerServerProvider();
|
||||
case ServerProviderType.digitalOcean:
|
||||
|
@ -26,6 +27,7 @@ class ServerProviderFactory {
|
|||
? DigitalOceanServerProvider.load(
|
||||
settings.location,
|
||||
settings.isAuthorized,
|
||||
settings.token,
|
||||
)
|
||||
: DigitalOceanServerProvider();
|
||||
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/server_jobs/server_jobs_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/models/backup.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)
|
||||
.toList();
|
||||
|
||||
final TokensState tokensState = context.watch<TokensBloc>().state;
|
||||
|
||||
if (!isReady) {
|
||||
return BrandHeroScreen(
|
||||
heroIcon: BrandIcons.save,
|
||||
|
@ -69,7 +72,7 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
heroTitle: 'backup.card_title'.tr(),
|
||||
heroSubtitle: 'backup.description'.tr(),
|
||||
children: [
|
||||
if (preventActions)
|
||||
if (preventActions || tokensState.backupsCredentials.isEmpty)
|
||||
const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
|
@ -81,9 +84,11 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
onPressed: preventActions
|
||||
? null
|
||||
: () {
|
||||
context
|
||||
.read<BackupsBloc>()
|
||||
.add(const InitializeBackupsRepository());
|
||||
context.read<BackupsBloc>().add(
|
||||
InitializeBackupsRepository(
|
||||
tokensState.backupsCredentials.first.data,
|
||||
),
|
||||
);
|
||||
},
|
||||
text: 'backup.initialize'.tr(),
|
||||
),
|
||||
|
|
|
@ -59,6 +59,11 @@ class MorePage extends StatelessWidget {
|
|||
goTo: () => const DevicesRoute(),
|
||||
title: 'devices.main_screen.header'.tr(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
iconData: Icons.token_outlined,
|
||||
title: 'tokens.title'.tr(),
|
||||
goTo: () => const TokensRoute(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'application_settings.title'.tr(),
|
||||
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(
|
||||
highlightShape: BoxShape.rectangle,
|
||||
onTap: () => context.read<SupportSystemCubit>().showArticle(
|
||||
article: dnsProvider.howToRegistar,
|
||||
article: dnsProvider.howToRegister,
|
||||
context: context,
|
||||
),
|
||||
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/console/console_page.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/providers/providers.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: ServerSettingsRoute.page),
|
||||
AutoRoute(page: ServerLogsRoute.page),
|
||||
AutoRoute(page: TokensRoute.page),
|
||||
],
|
||||
),
|
||||
AutoRoute(page: ServicesMigrationRoute.page),
|
||||
|
@ -162,6 +164,8 @@ String getRouteTitle(final String routeName) {
|
|||
return 'storage.card_title';
|
||||
case 'ExtendingVolumeRoute':
|
||||
return 'storage.extending_volume_title';
|
||||
case 'TokensRoute':
|
||||
return 'tokens.title';
|
||||
default:
|
||||
return routeName;
|
||||
}
|
||||
|
|
|
@ -192,6 +192,12 @@ abstract class _$RootRouter extends RootStackRouter {
|
|||
child: const ServicesPage(),
|
||||
);
|
||||
},
|
||||
TokensRoute.name: (routeData) {
|
||||
return AutoRoutePage<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const TokensPage(),
|
||||
);
|
||||
},
|
||||
UserDetailsRoute.name: (routeData) {
|
||||
final args = routeData.argsAs<UserDetailsRouteArgs>();
|
||||
return AutoRoutePage<dynamic>(
|
||||
|
@ -720,6 +726,20 @@ class ServicesRoute extends PageRouteInfo<void> {
|
|||
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
|
||||
/// [UserDetailsPage]
|
||||
class UserDetailsRoute extends PageRouteInfo<UserDetailsRouteArgs> {
|
||||
|
|
Loading…
Reference in a new issue