diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..7e7e7f67 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 7dff72f6..539f36a1 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -168,25 +168,19 @@ class DnsRecordsCubit Future fix() async { emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing)); + final List records = await api.getDnsRecords(); + + /// TODO: Error handling? final ServerDomain? domain = serverInstallationCubit.state.serverDomain; - final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4; await ProvidersController.currentDnsProvider!.removeDomainRecords( + records: records, domain: domain!, ); await ProvidersController.currentDnsProvider!.createDomainRecords( + records: records, domain: domain, - ip4: ipAddress, ); - final List records = await api.getDnsRecords(); - final DnsRecord? dkimRecord = extractDkimRecord(records); - if (dkimRecord != null) { - await ProvidersController.currentDnsProvider!.setDnsRecord( - dkimRecord, - domain, - ); - } - await load(); } } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 0b5b6eed..95726fb4 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -264,12 +264,22 @@ class ServerInstallationCubit extends Cubit { final ServerHostingDetails serverDetails, ) async { await repository.saveServerDetails(serverDetails); + + /// TODO: Error handling? await ProvidersController.currentDnsProvider!.removeDomainRecords( - ip4: serverDetails.ip4, + records: getProjectDnsRecords( + state.serverDomain!.domainName, + serverDetails.ip4, + false, + ), domain: state.serverDomain!, ); await ProvidersController.currentDnsProvider!.createDomainRecords( - ip4: serverDetails.ip4, + records: getProjectDnsRecords( + state.serverDomain!.domainName, + serverDetails.ip4, + true, + ), domain: state.serverDomain!, ); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index d69f5b89..36cb8743 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -558,14 +558,30 @@ class ServerInstallationRepository { } Future deleteServer(final ServerDomain serverDomain) async { + final ServerApi api = ServerApi(); + final dnsRecords = await api.getDnsRecords(); + final GenericResult removalResult = + await ProvidersController.currentDnsProvider!.removeDomainRecords( + domain: serverDomain, + records: dnsRecords, + ); + + if (!removalResult.success) { + getIt().showSnackBar( + 'modals.dns_removal_error'.tr(), + ); + return false; + } + final deletionResult = await ProvidersController.currentServerProvider!.deleteServer( serverDomain.domainName, ); if (!deletionResult.success) { - getIt() - .showSnackBar('modals.server_validators_error'.tr()); + getIt().showSnackBar( + 'modals.server_validators_error'.tr(), + ); return false; } @@ -576,13 +592,6 @@ class ServerInstallationRepository { await box.put(BNames.isLoading, false); await box.put(BNames.serverDetails, null); - final GenericResult removalResult = await ProvidersController - .currentDnsProvider! - .removeDomainRecords(domain: serverDomain); - - if (!removalResult.success) { - getIt().showSnackBar('modals.dns_removal_error'.tr()); - } return true; } diff --git a/lib/logic/models/json/dns_providers/cloudflare_dns_adapter.dart b/lib/logic/models/json/dns_providers/cloudflare_dns_adapter.dart index 3a22c89e..8046555c 100644 --- a/lib/logic/models/json/dns_providers/cloudflare_dns_adapter.dart +++ b/lib/logic/models/json/dns_providers/cloudflare_dns_adapter.dart @@ -4,14 +4,18 @@ CloudflareDnsRecord _fromDnsRecord( final DnsRecord dnsRecord, final String rootDomain, ) { + final String type = dnsRecord.type; String name = dnsRecord.name ?? ''; if (name != rootDomain && name != '@') { name = '$name.$rootDomain'; } + if (type == 'MX' && name == '@') { + name = rootDomain; + } return CloudflareDnsRecord( content: dnsRecord.content, name: name, - type: dnsRecord.type, + type: type, zoneName: rootDomain, id: null, ttl: dnsRecord.ttl, diff --git a/lib/logic/providers/dns_providers/cloudflare.dart b/lib/logic/providers/dns_providers/cloudflare.dart index d655cfbb..e40292a9 100644 --- a/lib/logic/providers/dns_providers/cloudflare.dart +++ b/lib/logic/providers/dns_providers/cloudflare.dart @@ -3,7 +3,6 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; -import 'package:selfprivacy/utils/network_utils.dart'; class ApiAdapter { ApiAdapter({ @@ -80,15 +79,14 @@ class CloudflareDnsProvider extends DnsProvider { @override Future> createDomainRecords({ + required final List records, 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( zoneId: _adapter.cachedZoneId, records: records @@ -102,16 +100,17 @@ class CloudflareDnsProvider extends DnsProvider { @override Future> removeDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }) async { final syncZoneIdResult = await syncZoneId(domain.domainName); if (!syncZoneIdResult.success) { return syncZoneIdResult; } - final result = - await _adapter.api().getDnsRecords(zoneId: _adapter.cachedZoneId); + final result = await _adapter.api().getDnsRecords( + zoneId: _adapter.cachedZoneId, + ); if (result.data.isEmpty || !result.success) { return GenericResult( success: result.success, @@ -121,9 +120,29 @@ class CloudflareDnsProvider extends DnsProvider { ); } + final List selfprivacyRecords = records + .map( + (final record) => CloudflareDnsRecord.fromDnsRecord( + record, + domain.domainName, + ), + ) + .toList(); + + final List cloudflareRecords = result.data; + + /// Remove all records that do not match with SelfPrivacy + cloudflareRecords.removeWhere( + (final cloudflareRecord) => !selfprivacyRecords.any( + (final selfprivacyRecord) => + selfprivacyRecord.type == cloudflareRecord.type && + selfprivacyRecord.name == cloudflareRecord.name, + ), + ); + return _adapter.api().removeSimilarRecords( zoneId: _adapter.cachedZoneId, - records: result.data, + records: cloudflareRecords, ); } diff --git a/lib/logic/providers/dns_providers/desec.dart b/lib/logic/providers/dns_providers/desec.dart index ea139039..188045bf 100644 --- a/lib/logic/providers/dns_providers/desec.dart +++ b/lib/logic/providers/dns_providers/desec.dart @@ -3,7 +3,6 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/desec_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; -import 'package:selfprivacy/utils/network_utils.dart'; class ApiAdapter { ApiAdapter({final bool isWithToken = true}) @@ -75,16 +74,11 @@ class DesecDnsProvider extends DnsProvider { @override Future> createDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }) async { - final List listDnsRecords = getProjectDnsRecords( - domain.domainName, - ip4, - ); - final List bulkRecords = []; - for (final DnsRecord record in listDnsRecords) { + for (final DnsRecord record in records) { bulkRecords.add(DesecDnsRecord.fromDnsRecord(record, domain.domainName)); } @@ -96,21 +90,19 @@ class DesecDnsProvider extends DnsProvider { @override Future> removeDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }) async { - final List listDnsRecords = getProjectDnsRecords( - domain.domainName, - ip4, - ); - final List bulkRecords = []; - for (final DnsRecord record in listDnsRecords) { + for (final DnsRecord record in records) { final desecRecord = DesecDnsRecord.fromDnsRecord( record, domain.domainName, ); bulkRecords.add( + /// Yes, it looks weird, but exactly forcing 'records' field + /// to empty array signals deSEC to remove the DNS record completely + /// https://desec.readthedocs.io/en/latest/dns/rrsets.html#deleting-an-rrset DesecDnsRecord( subname: desecRecord.subname, type: desecRecord.type, @@ -119,14 +111,6 @@ class DesecDnsProvider extends DnsProvider { ), ); } - bulkRecords.add( - DesecDnsRecord( - subname: 'selector._domainkey', - type: 'TXT', - ttl: 18000, - records: [], - ), - ); return _adapter.api().removeSimilarRecords( domainName: domain.domainName, diff --git a/lib/logic/providers/dns_providers/digital_ocean_dns.dart b/lib/logic/providers/dns_providers/digital_ocean_dns.dart index e07b63b1..f111c5f3 100644 --- a/lib/logic/providers/dns_providers/digital_ocean_dns.dart +++ b/lib/logic/providers/dns_providers/digital_ocean_dns.dart @@ -3,7 +3,6 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; -import 'package:selfprivacy/utils/network_utils.dart'; class ApiAdapter { ApiAdapter({final bool isWithToken = true}) @@ -75,15 +74,12 @@ class DigitalOceanDnsProvider extends DnsProvider { @override Future> createDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }) async => _adapter.api().createMultipleDnsRecords( domainName: domain.domainName, - records: getProjectDnsRecords( - domain.domainName, - ip4, - ) + records: records .map( (final e) => DigitalOceanDnsRecord.fromDnsRecord(e, domain.domainName), @@ -93,8 +89,8 @@ class DigitalOceanDnsProvider extends DnsProvider { @override Future> removeDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }) async { final result = await _adapter.api().getDnsRecords(domain.domainName); if (result.data.isEmpty || !result.success) { @@ -106,17 +102,29 @@ class DigitalOceanDnsProvider extends DnsProvider { ); } - const ignoreType = 'SOA'; - final List filteredRecords = []; - for (final record in result.data) { - if (record.type != ignoreType) { - filteredRecords.add(record); - } - } + final List selfprivacyRecords = records + .map( + (final record) => DigitalOceanDnsRecord.fromDnsRecord( + record, + domain.domainName, + ), + ) + .toList(); + + final List oceanRecords = result.data; + + /// Remove all records that do not match with SelfPrivacy + oceanRecords.removeWhere( + (final oceanRecord) => !selfprivacyRecords.any( + (final selfprivacyRecord) => + selfprivacyRecord.type == oceanRecord.type && + selfprivacyRecord.name == oceanRecord.name, + ), + ); return _adapter.api().removeSimilarRecords( domainName: domain.domainName, - records: filteredRecords, + records: oceanRecords, ); } diff --git a/lib/logic/providers/dns_providers/dns_provider.dart b/lib/logic/providers/dns_providers/dns_provider.dart index 98bd2199..406368b0 100644 --- a/lib/logic/providers/dns_providers/dns_provider.dart +++ b/lib/logic/providers/dns_providers/dns_provider.dart @@ -23,23 +23,21 @@ abstract class DnsProvider { /// Returns list of all available domain entries assigned to the account. Future>> domainList(); - /// Tries to create all main domain records needed - /// for SelfPrivacy to launch on requested domain by ip4. + /// Tries to create domain records + /// by our records list. /// /// Doesn't check for duplication, cleaning has /// to be done beforehand by [removeDomainRecords] Future> createDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }); - /// Tries to remove all domain records of requested domain by ip4. - /// - /// Will remove all entries, including the ones - /// that weren't created by SelfPrivacy. + /// Tries to remove all records of requested + /// domain that match our records list. Future> removeDomainRecords({ + required final List records, required final ServerDomain domain, - final String? ip4, }); /// Returns list of all [DnsRecord] entries assigned to requested domain. diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart index bfd8a82d..c155d6a0 100644 --- a/lib/utils/network_utils.dart +++ b/lib/utils/network_utils.dart @@ -93,6 +93,7 @@ void launchURL(final url) async { List getProjectDnsRecords( final String? domainName, final String? ip4, + final bool isCreating, ) { final DnsRecord domainA = DnsRecord(type: 'A', name: domainName, content: ip4); @@ -121,6 +122,16 @@ List getProjectDnsRecords( ttl: 18000, ); + /// We never create this record! + /// This declaration is only for removal + /// as we need to compare by 'type' and 'name' + final DnsRecord txt3 = DnsRecord( + type: 'TXT', + name: 'selector._domainkey', + content: 'v=DKIM1; k=rsa; p=none', + ttl: 18000, + ); + return [ domainA, apiA, @@ -132,6 +143,7 @@ List getProjectDnsRecords( mx, txt1, txt2, + if (!isCreating) txt3, vpn, ]; }