diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart index ae015d9d..0ba88ee3 100644 --- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart +++ b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart @@ -4,8 +4,7 @@ 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/models/hive/server_domain.dart'; -import 'package:selfprivacy/logic/models/json/dns_records.dart'; +import 'package:selfprivacy/logic/models/json/cloudflare_dns_info.dart'; class CloudflareApi extends RestApiMap { CloudflareApi({ @@ -92,9 +91,9 @@ class CloudflareApi extends RestApiMap { ); } - Future> getDomains() async { + Future>> getZones() async { final String url = '$rootAddress/zones'; - List domains = []; + List domains = []; late final Response? response; final Dio client = await getClient(); @@ -103,7 +102,11 @@ class CloudflareApi extends RestApiMap { url, queryParameters: {'per_page': 50}, ); - domains = response.data['result']; + domains = response.data['result']! + .map( + (final json) => CloudflareZone.fromJson(json), + ) + .toList(); } catch (e) { print(e); return GenericResult( @@ -125,18 +128,17 @@ class CloudflareApi extends RestApiMap { } Future> createMultipleDnsRecords({ - required final ServerDomain domain, - required final List records, + required final String zoneId, + required final List records, }) async { - final String domainZoneId = domain.zoneId; final List allCreateFutures = []; final Dio client = await getClient(); try { - for (final DnsRecord record in records) { + for (final CloudflareDnsRecord record in records) { allCreateFutures.add( client.post( - '/zones/$domainZoneId/dns_records', + '/zones/$zoneId/dns_records', data: record.toJson(), ), ); @@ -160,11 +162,10 @@ class CloudflareApi extends RestApiMap { } Future> removeSimilarRecords({ - required final ServerDomain domain, - required final List records, + required final String zoneId, + required final List records, }) async { - final String domainZoneId = domain.zoneId; - final String url = '/zones/$domainZoneId/dns_records'; + final String url = '/zones/$zoneId/dns_records'; final Dio client = await getClient(); try { @@ -172,7 +173,7 @@ class CloudflareApi extends RestApiMap { for (final record in records) { allDeleteFutures.add( - client.delete('$url/${record["id"]}'), + client.delete('$url/${record.id}'), ); } await Future.wait(allDeleteFutures); @@ -190,26 +191,21 @@ class CloudflareApi extends RestApiMap { return GenericResult(success: true, data: null); } - Future> getDnsRecords({ - required final ServerDomain domain, + Future>> getDnsRecords({ + required final String zoneId, }) async { Response response; - final String domainName = domain.domainName; - final String domainZoneId = domain.zoneId; - final List allRecords = []; - - final String url = '/zones/$domainZoneId/dns_records'; + List allRecords = []; + final String url = '/zones/$zoneId/dns_records'; final Dio client = await getClient(); try { response = await client.get(url); - final List records = response.data['result'] ?? []; - - for (final record in records) { - if (record['zone_name'] == domainName) { - allRecords.add(record); - } - } + allRecords = response.data['result']! + .map( + (final json) => CloudflareDnsRecord.fromJson(json), + ) + .toList(); } catch (e) { print(e); return GenericResult( @@ -223,30 +219,4 @@ class CloudflareApi extends RestApiMap { return GenericResult(data: allRecords, success: true); } - - Future>> getZones(final String domain) async { - List zones = []; - - late final Response? response; - final Dio client = await getClient(); - try { - response = await client.get( - '/zones', - queryParameters: {'name': domain}, - ); - zones = response.data['result']; - } catch (e) { - print(e); - GenericResult( - success: false, - data: zones, - code: response?.statusCode, - message: response?.statusMessage, - ); - } finally { - close(client); - } - - return GenericResult(success: true, data: zones); - } } diff --git a/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart b/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart index 8c66deb7..f4597439 100644 --- a/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart @@ -25,23 +25,17 @@ class DomainSetupCubit extends Cubit { Future saveDomain() async { assert(state is Loaded, 'wrong state'); final String domainName = (state as Loaded).domain; - emit(Loading(LoadingTypes.saving)); final dnsProvider = ProvidersController.currentDnsProvider!; - final GenericResult zoneIdResult = - await dnsProvider.getZoneId(domainName); - if (zoneIdResult.success || zoneIdResult.data != null) { - final ServerDomain domain = ServerDomain( - domainName: domainName, - zoneId: zoneIdResult.data!, - provider: dnsProvider.type, - ); + final ServerDomain domain = ServerDomain( + domainName: domainName, + provider: dnsProvider.type, + ); - serverInstallationCubit.setDomain(domain); - emit(DomainSet()); - } + serverInstallationCubit.setDomain(domain); + emit(DomainSet()); } } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index c669cf75..f29f70c5 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -467,7 +467,6 @@ class ServerInstallationCubit extends Cubit { final ServerDomain serverDomain = ServerDomain( domainName: domain, provider: DnsProviderType.unknown, - zoneId: '', ); final ServerRecoveryCapabilities recoveryCapabilities = await repository.getRecoveryCapabilities(serverDomain); @@ -681,9 +680,9 @@ class ServerInstallationCubit extends Cubit { if (serverDomain == null) { return; } - final String? zoneId = - await repository.getDomainId(token, serverDomain.domainName); - if (zoneId == null) { + final isTokenValid = + await repository.validateDnsToken(token, serverDomain.domainName); + if (!isTokenValid) { getIt() .showSnackBar('recovering.domain_not_available_on_token'.tr()); return; @@ -695,16 +694,13 @@ class ServerInstallationCubit extends Cubit { await repository.saveDomain( ServerDomain( domainName: serverDomain.domainName, - zoneId: zoneId, provider: dnsProviderType, ), ); - // await repository.setDnsApiToken(token); emit( dataState.copyWith( serverDomain: ServerDomain( domainName: serverDomain.domainName, - zoneId: zoneId, provider: dnsProviderType, ), dnsApiToken: token, diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index dd3388c6..1518d7ac 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -193,17 +193,23 @@ class ServerInstallationRepository { return server; } - Future getDomainId(final String token, final String domain) async { + Future validateDnsToken( + final String token, + final String domain, + ) async { final result = await ProvidersController.currentDnsProvider!.tryInitApiByToken(token); if (!result.success) { - return null; + return false; } await setDnsApiToken(token); - return (await ProvidersController.currentDnsProvider!.getZoneId( - domain, - )) - .data; + final domainResult = + await ProvidersController.currentDnsProvider!.domainList(); + if (!domainResult.success || domainResult.data.isEmpty) { + return false; + } + + return domain == domainResult.data[0]; } Future> isDnsAddressesMatch( diff --git a/lib/logic/models/hive/server_domain.dart b/lib/logic/models/hive/server_domain.dart index bd755bbc..4415ab20 100644 --- a/lib/logic/models/hive/server_domain.dart +++ b/lib/logic/models/hive/server_domain.dart @@ -7,21 +7,16 @@ part 'server_domain.g.dart'; class ServerDomain { ServerDomain({ required this.domainName, - required this.zoneId, required this.provider, }); @HiveField(0) final String domainName; - @HiveField(1) - final String zoneId; + // @HiveField(1) @HiveField(2, defaultValue: DnsProviderType.cloudflare) final DnsProviderType provider; - - @override - String toString() => '$domainName: $zoneId'; } @HiveType(typeId: 100) diff --git a/lib/logic/models/hive/server_domain.g.dart b/lib/logic/models/hive/server_domain.g.dart index 303407bc..2557ec43 100644 --- a/lib/logic/models/hive/server_domain.g.dart +++ b/lib/logic/models/hive/server_domain.g.dart @@ -18,7 +18,6 @@ class ServerDomainAdapter extends TypeAdapter { }; return ServerDomain( domainName: fields[0] as String, - zoneId: fields[1] as String, provider: fields[2] == null ? DnsProviderType.cloudflare : fields[2] as DnsProviderType, @@ -28,11 +27,9 @@ class ServerDomainAdapter extends TypeAdapter { @override void write(BinaryWriter writer, ServerDomain obj) { writer - ..writeByte(3) + ..writeByte(2) ..writeByte(0) ..write(obj.domainName) - ..writeByte(1) - ..write(obj.zoneId) ..writeByte(2) ..write(obj.provider); } diff --git a/lib/logic/models/json/cloudflare_dns_info.dart b/lib/logic/models/json/cloudflare_dns_info.dart new file mode 100644 index 00000000..3cd11bcd --- /dev/null +++ b/lib/logic/models/json/cloudflare_dns_info.dart @@ -0,0 +1,86 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'cloudflare_dns_info.g.dart'; + +/// https://developers.cloudflare.com/api/operations/zones-get +@JsonSerializable() +class CloudflareZone { + CloudflareZone({ + required this.id, + required this.name, + }); + + /// Zone identifier + /// + /// `<= 32 characters` + /// + /// Example: 023e105f4ecef8ad9ca31a8372d0c353 + final String id; + + /// The domain name + /// + /// `<= 253 characters` + /// + /// Example: example.com + final String name; + + static CloudflareZone fromJson(final Map json) => + _$CloudflareZoneFromJson(json); +} + +/// https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-list-dns-records +@JsonSerializable() +class CloudflareDnsRecord { + CloudflareDnsRecord({ + required this.type, + required this.name, + required this.content, + required this.zoneName, + this.ttl = 3600, + this.priority = 10, + this.id, + }); + + /// Record identifier + /// + /// `<= 32 characters` + /// Example: 023e105f4ecef8ad9ca31a8372d0c353 + final String? id; + + /// Record type. + /// + /// Example: A + final String type; + + /// DNS record name (or @ for the zone apex) in Punycode. + /// + /// `<= 255 characters` + /// + /// Example: example.com + final String? name; + + /// Valid DNS Record string content. + /// + /// Example: A valid IPv4 address "198.51.100.4" + final String? content; + + /// The domain of the record. + /// + /// Example: example.com + @JsonKey(name: 'zone_name') + final String zoneName; + + /// Time To Live (TTL) of the DNS record in seconds. Setting to 1 means 'automatic'. + /// + /// Value must be between 60 and 86400, with the minimum reduced to 30 for Enterprise zones. + final int ttl; + + /// Required for MX, SRV and URI records; unused by other record types. Records with lower priorities are preferred. + /// + /// `>= 0 <= 65535` + final int priority; + + static CloudflareDnsRecord fromJson(final Map json) => + _$CloudflareDnsRecordFromJson(json); + Map toJson() => _$CloudflareDnsRecordToJson(this); +} diff --git a/lib/logic/models/json/cloudflare_dns_info.g.dart b/lib/logic/models/json/cloudflare_dns_info.g.dart new file mode 100644 index 00000000..d02fc2d2 --- /dev/null +++ b/lib/logic/models/json/cloudflare_dns_info.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'cloudflare_dns_info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CloudflareZone _$CloudflareZoneFromJson(Map json) => + CloudflareZone( + id: json['id'] as String, + name: json['name'] as String, + ); + +Map _$CloudflareZoneToJson(CloudflareZone instance) => + { + 'id': instance.id, + 'name': instance.name, + }; + +CloudflareDnsRecord _$CloudflareDnsRecordFromJson(Map json) => + CloudflareDnsRecord( + type: json['type'] as String, + name: json['name'] as String?, + content: json['content'] as String?, + zoneName: json['zone_name'] as String, + ttl: json['ttl'] as int? ?? 3600, + priority: json['priority'] as int? ?? 10, + id: json['id'] as String?, + ); + +Map _$CloudflareDnsRecordToJson( + CloudflareDnsRecord instance) => + { + 'id': instance.id, + 'type': instance.type, + 'name': instance.name, + 'content': instance.content, + 'zone_name': instance.zoneName, + 'ttl': instance.ttl, + 'priority': instance.priority, + }; diff --git a/lib/logic/providers/dns_providers/cloudflare.dart b/lib/logic/providers/dns_providers/cloudflare.dart index 83d665b3..596c03ab 100644 --- a/lib/logic/providers/dns_providers/cloudflare.dart +++ b/lib/logic/providers/dns_providers/cloudflare.dart @@ -1,12 +1,16 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/cloudflare_dns_info.dart'; 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}) - : _api = CloudflareApi( + ApiAdapter({ + final bool isWithToken = true, + this.cachedDomain = '', + this.cachedZoneId = '', + }) : _api = CloudflareApi( isWithToken: isWithToken, ); @@ -17,6 +21,8 @@ class ApiAdapter { ); final CloudflareApi _api; + final String cachedZoneId; + final String cachedDomain; } class CloudflareDnsProvider extends DnsProvider { @@ -47,7 +53,7 @@ class CloudflareDnsProvider extends DnsProvider { @override Future>> domainList() async { List domains = []; - final result = await _adapter.api().getDomains(); + final result = await _adapter.api().getZones(); if (result.data.isEmpty || !result.success) { return GenericResult( success: result.success, @@ -59,10 +65,18 @@ class CloudflareDnsProvider extends DnsProvider { domains = result.data .map( - (final el) => el['name'] as String, + (final el) => el.name, ) .toList(); + /// TODO: Remove when domain selection for more than one domain on account is implemented, move cachedZoneId writing to domain saving method + _adapter = ApiAdapter( + isWithToken: true, + cachedDomain: result.data[0].name, + cachedZoneId: result.data[0].id, + ); + print('cachedZoneId saved for the first time!!!'); + return GenericResult( success: true, data: domains, @@ -73,11 +87,27 @@ class CloudflareDnsProvider extends DnsProvider { Future> createDomainRecords({ required final ServerDomain domain, final String? ip4, - }) { + }) async { + final syncZoneIdResult = await syncZoneId(domain.domainName); + if (!syncZoneIdResult.success) { + return syncZoneIdResult; + } + final records = getProjectDnsRecords(domain.domainName, ip4); return _adapter.api().createMultipleDnsRecords( - domain: domain, - records: records, + zoneId: _adapter.cachedZoneId, + records: records + .map( + (final rec) => CloudflareDnsRecord( + content: rec.content, + name: rec.name, + type: rec.type, + zoneName: domain.domainName, + id: null, + ttl: rec.ttl, + ), + ) + .toList(), ); } @@ -86,7 +116,13 @@ class CloudflareDnsProvider extends DnsProvider { required final ServerDomain domain, final String? ip4, }) async { - final result = await _adapter.api().getDnsRecords(domain: domain); + final syncZoneIdResult = await syncZoneId(domain.domainName); + if (!syncZoneIdResult.success) { + return syncZoneIdResult; + } + + final result = + await _adapter.api().getDnsRecords(zoneId: _adapter.cachedZoneId); if (result.data.isEmpty || !result.success) { return GenericResult( success: result.success, @@ -97,7 +133,7 @@ class CloudflareDnsProvider extends DnsProvider { } return _adapter.api().removeSimilarRecords( - domain: domain, + zoneId: _adapter.cachedZoneId, records: result.data, ); } @@ -106,8 +142,19 @@ class CloudflareDnsProvider extends DnsProvider { Future>> getDnsRecords({ required final ServerDomain domain, }) async { + final syncZoneIdResult = await syncZoneId(domain.domainName); + if (!syncZoneIdResult.success) { + return GenericResult( + success: syncZoneIdResult.success, + data: [], + code: syncZoneIdResult.code, + message: syncZoneIdResult.message, + ); + } + final List records = []; - final result = await _adapter.api().getDnsRecords(domain: domain); + final result = + await _adapter.api().getDnsRecords(zoneId: _adapter.cachedZoneId); if (result.data.isEmpty || !result.success) { return GenericResult( success: result.success, @@ -120,11 +167,10 @@ class CloudflareDnsProvider extends DnsProvider { for (final rawRecord in result.data) { records.add( DnsRecord( - name: rawRecord['name'], - type: rawRecord['type'], - content: rawRecord['content'], - ttl: rawRecord['ttl'], - proxied: rawRecord['proxied'], + name: rawRecord.name, + type: rawRecord.type, + content: rawRecord.content, + ttl: rawRecord.ttl, ), ); } @@ -139,11 +185,26 @@ class CloudflareDnsProvider extends DnsProvider { Future> setDnsRecord( final DnsRecord record, final ServerDomain domain, - ) async => - _adapter.api().createMultipleDnsRecords( - domain: domain, - records: [record], - ); + ) async { + final syncZoneIdResult = await syncZoneId(domain.domainName); + if (!syncZoneIdResult.success) { + return syncZoneIdResult; + } + + return _adapter.api().createMultipleDnsRecords( + zoneId: _adapter.cachedZoneId, + records: [ + CloudflareDnsRecord( + content: record.content, + id: null, + name: record.name, + type: record.type, + zoneName: domain.domainName, + ttl: record.ttl, + ), + ], + ); + } @override Future>> validateDnsRecords( @@ -336,10 +397,41 @@ class CloudflareDnsProvider extends DnsProvider { ]; } - @override + Future> syncZoneId(final String domain) async { + if (domain == _adapter.cachedDomain && _adapter.cachedZoneId.isNotEmpty) { + return GenericResult( + success: true, + data: null, + ); + } + + print('syncZoneId!!!'); + + final getZoneIdResult = await getZoneId(domain); + if (!getZoneIdResult.success || getZoneIdResult.data == null) { + return GenericResult( + success: false, + data: null, + code: getZoneIdResult.code, + message: getZoneIdResult.message, + ); + } + + _adapter = ApiAdapter( + isWithToken: true, + cachedDomain: domain, + cachedZoneId: getZoneIdResult.data!, + ); + + return GenericResult( + success: true, + data: null, + ); + } + Future> getZoneId(final String domain) async { String? id; - final result = await _adapter.api().getZones(domain); + final result = await _adapter.api().getZones(); if (result.data.isEmpty || !result.success) { return GenericResult( success: result.success, @@ -349,8 +441,12 @@ class CloudflareDnsProvider extends DnsProvider { ); } - id = result.data[0]['id']; + for (final availableDomain in result.data) { + if (availableDomain.name == domain) { + id = availableDomain.id; + } + } - return GenericResult(success: true, data: id); + return GenericResult(success: id != null, data: id); } } diff --git a/lib/logic/providers/dns_providers/desec.dart b/lib/logic/providers/dns_providers/desec.dart index e20d82bf..a5e3e8d7 100644 --- a/lib/logic/providers/dns_providers/desec.dart +++ b/lib/logic/providers/dns_providers/desec.dart @@ -408,11 +408,4 @@ class DesecDnsProvider extends DnsProvider { ), ]; } - - @override - Future> getZoneId(final String domain) async => - GenericResult( - data: domain, - success: true, - ); } diff --git a/lib/logic/providers/dns_providers/digital_ocean_dns.dart b/lib/logic/providers/dns_providers/digital_ocean_dns.dart index 7397f41c..4fac4b65 100644 --- a/lib/logic/providers/dns_providers/digital_ocean_dns.dart +++ b/lib/logic/providers/dns_providers/digital_ocean_dns.dart @@ -369,11 +369,4 @@ class DigitalOceanDnsProvider extends DnsProvider { ), ]; } - - @override - Future> getZoneId(final String domain) async => - GenericResult( - data: domain, - success: true, - ); } diff --git a/lib/logic/providers/dns_providers/dns_provider.dart b/lib/logic/providers/dns_providers/dns_provider.dart index 28517a83..647e84ea 100644 --- a/lib/logic/providers/dns_providers/dns_provider.dart +++ b/lib/logic/providers/dns_providers/dns_provider.dart @@ -73,12 +73,4 @@ abstract class DnsProvider { final String? ip4, final String? dkimPublicKey, ); - - /// Tries to access zone of requested domain. - /// - /// If a DNS provider doesn't support zones, - /// will return domain without any changes. - /// - /// If success, returns an initializing string of zone id. - Future> getZoneId(final String domain); }