diff --git a/assets/images/logos/cloudflare.svg b/assets/images/logos/cloudflare.svg
index a8cbf3bc..03a60465 100644
--- a/assets/images/logos/cloudflare.svg
+++ b/assets/images/logos/cloudflare.svg
@@ -1,10 +1 @@
-
-
+
diff --git a/assets/images/logos/desec.svg b/assets/images/logos/desec.svg
new file mode 100644
index 00000000..cb54b268
--- /dev/null
+++ b/assets/images/logos/desec.svg
@@ -0,0 +1,89 @@
+
+
+
+
diff --git a/assets/markdown/how_desec-en.md b/assets/markdown/how_desec-en.md
new file mode 100644
index 00000000..c86d3855
--- /dev/null
+++ b/assets/markdown/how_desec-en.md
@@ -0,0 +1,9 @@
+### How to get deSEC API Token
+1. Log in at: https://desec.io/login
+2. Go to **Domains** page at: https://desec.io/domains
+3. Go to **Token management** tab.
+4. Click on the round "plus" button in the upper right corner.
+5. **"Generate New Token"** dialogue must be displayed. Enter any **Token name** you wish. *Advanced settings* are not required, so do not touch anything there.
+6. Click on **Save**.
+7. Make sure you **save** the token's **secret value** as it will only be displayed once.
+8. Now you can safely **close** the dialogue.
\ No newline at end of file
diff --git a/assets/markdown/how_desec-ru.md b/assets/markdown/how_desec-ru.md
new file mode 100644
index 00000000..a93acc77
--- /dev/null
+++ b/assets/markdown/how_desec-ru.md
@@ -0,0 +1,9 @@
+### Как получить deSEC API Токен
+1. Авторизуемся в deSEC: https://desec.io/login
+2. Переходим на страницу **Domains** по ссылке: https://desec.io/domains
+3. Переходим на вкладку **Token management**.
+4. Нажимаем на большую кнопку с плюсом в правом верхнем углу страницы.
+5. Должен был появиться **"Generate New Token"** диалог. Вводим любое имя токена в **Token name**. *Advanced settings* необязательны, так что ничего там не трогаем.
+6. Кликаем **Save**.
+7. Обязательно сохраняем "**secret value**" ключ токена, потому что он отображается исключительно один раз.
+8. Теперь спокойно закрываем диалог, нажав **close**.
\ No newline at end of file
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 327229bb..0ac7562e 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -323,7 +323,7 @@
"manage_domain_dns": "To manage your domain's DNS",
"use_this_domain": "Use this domain?",
"use_this_domain_text": "The token you provided gives access to the following domain",
- "cloudflare_api_token": "CloudFlare API Token",
+ "cloudflare_api_token": "DNS Provider API Token",
"connect_backblaze_storage": "Connect Backblaze storage",
"no_connected_domains": "No connected domains at the moment",
"loading_domain_list": "Loading domain list",
@@ -394,8 +394,8 @@
"modal_confirmation_dns_invalid": "Reverse DNS points to another domain",
"modal_confirmation_ip_valid": "IP is the same as in DNS record",
"modal_confirmation_ip_invalid": "IP is not the same as in DNS record",
- "confirm_cloudflare": "Connect to CloudFlare",
- "confirm_cloudflare_description": "Enter a Cloudflare token with access to {}:",
+ "confirm_cloudflare": "Connect to your DNS Provider",
+ "confirm_cloudflare_description": "Enter a token of your DNS Provider with access to {}:",
"confirm_backblaze": "Connect to Backblaze",
"confirm_backblaze_description": "Enter a Backblaze token with access to backup storage:"
},
diff --git a/assets/translations/ru.json b/assets/translations/ru.json
index 450be193..8fdf0054 100644
--- a/assets/translations/ru.json
+++ b/assets/translations/ru.json
@@ -286,8 +286,8 @@
"select_provider_price_text_do": "$17 в месяц за небольшой сервер и 50GB места на диске",
"select_provider_payment_title": "Методы оплаты",
"select_provider_payment_text_hetzner": "Банковские карты, SWIFT, SEPA, PayPal",
- "select_provider_payment_text_do": "Банковские карты, Google Pay, PayPal",
"select_provider_payment_text_cloudflare": "Банковские карты",
+ "select_provider_payment_text_do": "Банковские карты, Google Pay, PayPal",
"select_provider_email_notice": "Хостинг электронной почты недоступен для новых клиентов. Разблокировать можно будет после первой оплаты.",
"select_provider_site_button": "Посетить сайт",
"connect_to_server_provider": "Авторизоваться в ",
@@ -315,7 +315,7 @@
"manage_domain_dns": "Для управления DNS вашего домена",
"use_this_domain": "Используем этот домен?",
"use_this_domain_text": "Указанный вами токен даёт контроль над этим доменом",
- "cloudflare_api_token": "CloudFlare API ключ",
+ "cloudflare_api_token": "API ключ DNS провайдера",
"connect_backblaze_storage": "Подключите облачное хранилище Backblaze",
"no_connected_domains": "На данный момент подлюченных доменов нет",
"loading_domain_list": "Загружаем список доменов",
@@ -371,8 +371,8 @@
"modal_confirmation_dns_invalid": "Обратный DNS указывает на другой домен",
"modal_confirmation_ip_valid": "IP совпадает с указанным в DNS записи",
"modal_confirmation_ip_invalid": "IP не совпадает с указанным в DNS записи",
- "confirm_cloudflare": "Подключение к Cloudflare",
- "confirm_cloudflare_description": "Введите токен Cloudflare, который имеет права на {}:",
+ "confirm_cloudflare": "Подключение к DNS Провайдеру",
+ "confirm_cloudflare_description": "Введите токен DNS Провайдера, который имеет права на {}:",
"confirm_backblaze_description": "Введите токен Backblaze, который имеет права на хранилище резервных копий:",
"confirm_backblaze": "Подключение к Backblaze",
"server_provider_connected": "Подключение к вашему серверному провайдеру",
@@ -478,4 +478,4 @@
"length_not_equal": "Длина строки [], должна быть равна {}",
"length_longer": "Длина строки [], должна быть меньше либо равна {}"
}
-}
+}
\ No newline at end of file
diff --git a/lib/logic/api_maps/graphql_maps/api_map.dart b/lib/logic/api_maps/graphql_maps/api_map.dart
index a633866e..34e39b7a 100644
--- a/lib/logic/api_maps/graphql_maps/api_map.dart
+++ b/lib/logic/api_maps/graphql_maps/api_map.dart
@@ -56,7 +56,7 @@ class ResponseLoggingParser extends ResponseParser {
abstract class ApiMap {
Future getClient() async {
IOClient? ioClient;
- if (StagingOptions.stagingAcme) {
+ if (StagingOptions.stagingAcme || !StagingOptions.verifyCertificate) {
final HttpClient httpClient = HttpClient();
httpClient.badCertificateCallback = (
final cert,
diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql
index 81c703d1..bd05dfb5 100644
--- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql
+++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql
@@ -76,7 +76,8 @@ type DeviceApiTokenMutationReturn implements MutationReturnInterface {
enum DnsProvider {
CLOUDFLARE,
- DIGITALOCEAN
+ DIGITALOCEAN,
+ DESEC
}
type DnsRecord {
diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart
index a8318d9f..b0d0341d 100644
--- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart
@@ -1096,7 +1096,7 @@ class _CopyWithStubImpl$Input$UserMutationInput
_res;
}
-enum Enum$DnsProvider { CLOUDFLARE, DIGITALOCEAN, $unknown }
+enum Enum$DnsProvider { CLOUDFLARE, DIGITALOCEAN, DESEC, $unknown }
String toJson$Enum$DnsProvider(Enum$DnsProvider e) {
switch (e) {
@@ -1104,6 +1104,8 @@ String toJson$Enum$DnsProvider(Enum$DnsProvider e) {
return r'CLOUDFLARE';
case Enum$DnsProvider.DIGITALOCEAN:
return r'DIGITALOCEAN';
+ case Enum$DnsProvider.DESEC:
+ return r'DESEC';
case Enum$DnsProvider.$unknown:
return r'$unknown';
}
@@ -1115,6 +1117,8 @@ Enum$DnsProvider fromJson$Enum$DnsProvider(String value) {
return Enum$DnsProvider.CLOUDFLARE;
case r'DIGITALOCEAN':
return Enum$DnsProvider.DIGITALOCEAN;
+ case r'DESEC':
+ return Enum$DnsProvider.DESEC;
default:
return Enum$DnsProvider.$unknown;
}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart
index d9ac5c6c..5df05a48 100644
--- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart
+++ b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart
@@ -189,88 +189,6 @@ class CloudflareApi extends DnsProviderApi {
return allRecords;
}
- @override
- List getDesiredDnsRecords({
- final String? domainName,
- final String? ipAddress,
- final String? dkimPublicKey,
- }) {
- if (domainName == null || ipAddress == null) {
- return [];
- }
- return [
- DesiredDnsRecord(
- name: domainName,
- content: ipAddress,
- description: 'record.root',
- ),
- DesiredDnsRecord(
- name: 'api.$domainName',
- content: ipAddress,
- description: 'record.api',
- ),
- DesiredDnsRecord(
- name: 'cloud.$domainName',
- content: ipAddress,
- description: 'record.cloud',
- ),
- DesiredDnsRecord(
- name: 'git.$domainName',
- content: ipAddress,
- description: 'record.git',
- ),
- DesiredDnsRecord(
- name: 'meet.$domainName',
- content: ipAddress,
- description: 'record.meet',
- ),
- DesiredDnsRecord(
- name: 'social.$domainName',
- content: ipAddress,
- description: 'record.social',
- ),
- DesiredDnsRecord(
- name: 'password.$domainName',
- content: ipAddress,
- description: 'record.password',
- ),
- DesiredDnsRecord(
- name: 'vpn.$domainName',
- content: ipAddress,
- description: 'record.vpn',
- ),
- DesiredDnsRecord(
- name: domainName,
- content: domainName,
- description: 'record.mx',
- type: 'MX',
- category: DnsRecordsCategory.email,
- ),
- DesiredDnsRecord(
- name: '_dmarc.$domainName',
- content: 'v=DMARC1; p=none',
- description: 'record.dmarc',
- type: 'TXT',
- category: DnsRecordsCategory.email,
- ),
- DesiredDnsRecord(
- name: domainName,
- content: 'v=spf1 a mx ip4:$ipAddress -all',
- description: 'record.spf',
- type: 'TXT',
- category: DnsRecordsCategory.email,
- ),
- if (dkimPublicKey != null)
- DesiredDnsRecord(
- name: 'selector._domainkey.$domainName',
- content: dkimPublicKey,
- description: 'record.dkim',
- type: 'TXT',
- category: DnsRecordsCategory.email,
- ),
- ];
- }
-
@override
Future> createMultipleDnsRecords({
required final ServerDomain domain,
@@ -353,4 +271,147 @@ class CloudflareApi extends DnsProviderApi {
return domains;
}
+
+ @override
+ Future>> validateDnsRecords(
+ final ServerDomain domain,
+ final String ip4,
+ final String dkimPublicKey,
+ ) async {
+ final List records = await getDnsRecords(domain: domain);
+ final List foundRecords = [];
+ try {
+ final List desiredRecords =
+ getDesiredDnsRecords(domain.domainName, ip4, dkimPublicKey);
+ for (final DesiredDnsRecord record in desiredRecords) {
+ if (record.description == 'record.dkim') {
+ final DnsRecord foundRecord = records.firstWhere(
+ (final r) => (r.name == record.name) && r.type == record.type,
+ orElse: () => DnsRecord(
+ name: record.name,
+ type: record.type,
+ content: '',
+ ttl: 800,
+ proxied: false,
+ ),
+ );
+ // remove all spaces and tabulators from
+ // the foundRecord.content and the record.content
+ // to compare them
+ final String? foundContent =
+ foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
+ final String content = record.content.replaceAll(RegExp(r'\s+'), '');
+ if (foundContent == content) {
+ foundRecords.add(record.copyWith(isSatisfied: true));
+ } else {
+ foundRecords.add(record.copyWith(isSatisfied: false));
+ }
+ } else {
+ if (records.any(
+ (final r) =>
+ (r.name == record.name) &&
+ r.type == record.type &&
+ r.content == record.content,
+ )) {
+ foundRecords.add(record.copyWith(isSatisfied: true));
+ } else {
+ foundRecords.add(record.copyWith(isSatisfied: false));
+ }
+ }
+ }
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: [],
+ success: false,
+ message: e.toString(),
+ );
+ }
+ return GenericResult(
+ data: foundRecords,
+ success: true,
+ );
+ }
+
+ @override
+ List getDesiredDnsRecords(
+ final String? domainName,
+ final String? ip4,
+ final String? dkimPublicKey,
+ ) {
+ if (domainName == null || ip4 == null) {
+ return [];
+ }
+ return [
+ DesiredDnsRecord(
+ name: domainName,
+ content: ip4,
+ description: 'record.root',
+ ),
+ DesiredDnsRecord(
+ name: 'api.$domainName',
+ content: ip4,
+ description: 'record.api',
+ ),
+ DesiredDnsRecord(
+ name: 'cloud.$domainName',
+ content: ip4,
+ description: 'record.cloud',
+ ),
+ DesiredDnsRecord(
+ name: 'git.$domainName',
+ content: ip4,
+ description: 'record.git',
+ ),
+ DesiredDnsRecord(
+ name: 'meet.$domainName',
+ content: ip4,
+ description: 'record.meet',
+ ),
+ DesiredDnsRecord(
+ name: 'social.$domainName',
+ content: ip4,
+ description: 'record.social',
+ ),
+ DesiredDnsRecord(
+ name: 'password.$domainName',
+ content: ip4,
+ description: 'record.password',
+ ),
+ DesiredDnsRecord(
+ name: 'vpn.$domainName',
+ content: ip4,
+ description: 'record.vpn',
+ ),
+ DesiredDnsRecord(
+ name: domainName,
+ content: domainName,
+ description: 'record.mx',
+ type: 'MX',
+ category: DnsRecordsCategory.email,
+ ),
+ DesiredDnsRecord(
+ name: '_dmarc.$domainName',
+ content: 'v=DMARC1; p=none',
+ description: 'record.dmarc',
+ type: 'TXT',
+ category: DnsRecordsCategory.email,
+ ),
+ DesiredDnsRecord(
+ name: domainName,
+ content: 'v=spf1 a mx ip4:$ip4 -all',
+ description: 'record.spf',
+ type: 'TXT',
+ category: DnsRecordsCategory.email,
+ ),
+ if (dkimPublicKey != null)
+ DesiredDnsRecord(
+ name: 'selector._domainkey.$domainName',
+ content: dkimPublicKey,
+ description: 'record.dkim',
+ type: 'TXT',
+ category: DnsRecordsCategory.email,
+ ),
+ ];
+ }
}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart
new file mode 100644
index 00000000..8298b08d
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart
@@ -0,0 +1,475 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:selfprivacy/config/get_it_config.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
+import 'package:selfprivacy/logic/models/hive/server_domain.dart';
+import 'package:selfprivacy/logic/models/json/dns_records.dart';
+
+class DesecApi extends DnsProviderApi {
+ DesecApi({
+ this.hasLogger = false,
+ this.isWithToken = true,
+ this.customToken,
+ });
+ @override
+ final bool hasLogger;
+ @override
+ final bool isWithToken;
+
+ final String? customToken;
+
+ @override
+ RegExp getApiTokenValidation() =>
+ RegExp(r'\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
+
+ @override
+ BaseOptions get options {
+ final BaseOptions options = BaseOptions(baseUrl: rootAddress);
+ if (isWithToken) {
+ final String? token = getIt().dnsProviderKey;
+ assert(token != null);
+ options.headers = {'Authorization': 'Token $token'};
+ }
+
+ if (customToken != null) {
+ options.headers = {'Authorization': 'Token $customToken'};
+ }
+
+ if (validateStatus != null) {
+ options.validateStatus = validateStatus!;
+ }
+ return options;
+ }
+
+ @override
+ String rootAddress = 'https://desec.io/api/v1/domains/';
+
+ @override
+ Future> isApiTokenValid(final String token) async {
+ bool isValid = false;
+ Response? response;
+ String message = '';
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '',
+ options: Options(
+ followRedirects: false,
+ validateStatus: (final status) =>
+ status != null && (status >= 200 || status == 401),
+ headers: {'Authorization': 'Token $token'},
+ ),
+ );
+ await Future.delayed(const Duration(seconds: 1));
+ } catch (e) {
+ print(e);
+ isValid = false;
+ message = e.toString();
+ } finally {
+ close(client);
+ }
+
+ if (response == null) {
+ return GenericResult(
+ data: isValid,
+ success: false,
+ message: message,
+ );
+ }
+
+ if (response.statusCode == HttpStatus.ok) {
+ isValid = true;
+ } else if (response.statusCode == HttpStatus.unauthorized) {
+ isValid = false;
+ } else {
+ throw Exception('code: ${response.statusCode}');
+ }
+
+ return GenericResult(
+ data: isValid,
+ success: true,
+ message: response.statusMessage,
+ );
+ }
+
+ @override
+ Future getZoneId(final String domain) async => domain;
+
+ @override
+ Future> removeSimilarRecords({
+ required final ServerDomain domain,
+ final String? ip4,
+ }) async {
+ final String domainName = domain.domainName;
+ final String url = '/$domainName/rrsets/';
+ final List listDnsRecords = projectDnsRecords(domainName, ip4);
+
+ final Dio client = await getClient();
+ try {
+ final List bulkRecords = [];
+ for (final DnsRecord record in listDnsRecords) {
+ bulkRecords.add(
+ {
+ 'subname': record.name,
+ 'type': record.type,
+ 'ttl': record.ttl,
+ 'records': [],
+ },
+ );
+ }
+ bulkRecords.add(
+ {
+ 'subname': 'selector._domainkey',
+ 'type': 'TXT',
+ 'ttl': 18000,
+ 'records': [],
+ },
+ );
+ await client.put(url, data: bulkRecords);
+ await Future.delayed(const Duration(seconds: 1));
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ @override
+ Future> getDnsRecords({
+ required final ServerDomain domain,
+ }) async {
+ Response response;
+ final String domainName = domain.domainName;
+ final List allRecords = [];
+
+ final String url = '/$domainName/rrsets/';
+
+ final Dio client = await getClient();
+ try {
+ response = await client.get(url);
+ await Future.delayed(const Duration(seconds: 1));
+ final List records = response.data;
+
+ for (final record in records) {
+ final String? content = (record['records'] is List)
+ ? record['records'][0]
+ : record['records'];
+ allRecords.add(
+ DnsRecord(
+ name: record['subname'],
+ type: record['type'],
+ content: content,
+ ttl: record['ttl'],
+ ),
+ );
+ }
+ } catch (e) {
+ print(e);
+ } finally {
+ close(client);
+ }
+
+ return allRecords;
+ }
+
+ @override
+ Future> createMultipleDnsRecords({
+ required final ServerDomain domain,
+ final String? ip4,
+ }) async {
+ final String domainName = domain.domainName;
+ final List listDnsRecords = projectDnsRecords(domainName, ip4);
+
+ final Dio client = await getClient();
+ try {
+ final List bulkRecords = [];
+ for (final DnsRecord record in listDnsRecords) {
+ bulkRecords.add(
+ {
+ 'subname': record.name,
+ 'type': record.type,
+ 'ttl': record.ttl,
+ 'records': [extractContent(record)],
+ },
+ );
+ }
+ await client.post(
+ '/$domainName/rrsets/',
+ data: bulkRecords,
+ );
+ await Future.delayed(const Duration(seconds: 1));
+ } on DioError catch (e) {
+ print(e.message);
+ rethrow;
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ List projectDnsRecords(
+ final String? domainName,
+ final String? ip4,
+ ) {
+ final DnsRecord domainA = DnsRecord(type: 'A', name: '', content: ip4);
+
+ final DnsRecord mx =
+ DnsRecord(type: 'MX', name: '', content: '10 $domainName.');
+ final DnsRecord apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
+ final DnsRecord cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
+ final DnsRecord gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
+ final DnsRecord meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
+ final DnsRecord passwordA =
+ DnsRecord(type: 'A', name: 'password', content: ip4);
+ final DnsRecord socialA =
+ DnsRecord(type: 'A', name: 'social', content: ip4);
+ final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
+
+ final DnsRecord txt1 = DnsRecord(
+ type: 'TXT',
+ name: '_dmarc',
+ content: '"v=DMARC1; p=none"',
+ ttl: 18000,
+ );
+
+ final DnsRecord txt2 = DnsRecord(
+ type: 'TXT',
+ name: '',
+ content: '"v=spf1 a mx ip4:$ip4 -all"',
+ ttl: 18000,
+ );
+
+ return [
+ domainA,
+ apiA,
+ cloudA,
+ gitA,
+ meetA,
+ passwordA,
+ socialA,
+ mx,
+ txt1,
+ txt2,
+ vpn
+ ];
+ }
+
+ String? extractContent(final DnsRecord record) {
+ String? content = record.content;
+ if (record.type == 'TXT' && content != null && !content.startsWith('"')) {
+ content = '"$content"';
+ }
+
+ return content;
+ }
+
+ @override
+ Future setDnsRecord(
+ final DnsRecord record,
+ final ServerDomain domain,
+ ) async {
+ final String url = '/${domain.domainName}/rrsets/';
+
+ final Dio client = await getClient();
+ try {
+ await client.post(
+ url,
+ data: {
+ 'subname': record.name,
+ 'type': record.type,
+ 'ttl': record.ttl,
+ 'records': [extractContent(record)],
+ },
+ );
+ await Future.delayed(const Duration(seconds: 1));
+ } catch (e) {
+ print(e);
+ } finally {
+ close(client);
+ }
+ }
+
+ @override
+ Future> domainList() async {
+ List domains = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get(
+ '',
+ );
+ await Future.delayed(const Duration(seconds: 1));
+ domains = response.data
+ .map((final el) => el['name'] as String)
+ .toList();
+ } catch (e) {
+ print(e);
+ } finally {
+ close(client);
+ }
+
+ return domains;
+ }
+
+ @override
+ Future>> validateDnsRecords(
+ final ServerDomain domain,
+ final String ip4,
+ final String dkimPublicKey,
+ ) async {
+ final List records = await getDnsRecords(domain: domain);
+ final List foundRecords = [];
+ try {
+ final List desiredRecords =
+ getDesiredDnsRecords(domain.domainName, ip4, dkimPublicKey);
+ for (final DesiredDnsRecord record in desiredRecords) {
+ if (record.description == 'record.dkim') {
+ final DnsRecord foundRecord = records.firstWhere(
+ (final r) =>
+ ('${r.name}.${domain.domainName}' == record.name) &&
+ r.type == record.type,
+ orElse: () => DnsRecord(
+ name: record.name,
+ type: record.type,
+ content: '',
+ ttl: 800,
+ proxied: false,
+ ),
+ );
+ // remove all spaces and tabulators from
+ // the foundRecord.content and the record.content
+ // to compare them
+ final String? foundContent =
+ foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
+ final String content = record.content.replaceAll(RegExp(r'\s+'), '');
+ if (foundContent == content) {
+ foundRecords.add(record.copyWith(isSatisfied: true));
+ } else {
+ foundRecords.add(record.copyWith(isSatisfied: false));
+ }
+ } else {
+ if (records.any(
+ (final r) =>
+ ('${r.name}.${domain.domainName}' == record.name ||
+ record.name == '') &&
+ r.type == record.type &&
+ r.content == record.content,
+ )) {
+ foundRecords.add(record.copyWith(isSatisfied: true));
+ } else {
+ foundRecords.add(record.copyWith(isSatisfied: false));
+ }
+ }
+ }
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: [],
+ success: false,
+ message: e.toString(),
+ );
+ }
+ return GenericResult(
+ data: foundRecords,
+ success: true,
+ );
+ }
+
+ @override
+ List getDesiredDnsRecords(
+ final String? domainName,
+ final String? ip4,
+ final String? dkimPublicKey,
+ ) {
+ if (domainName == null || ip4 == null) {
+ return [];
+ }
+ return [
+ DesiredDnsRecord(
+ name: '',
+ content: ip4,
+ description: 'record.root',
+ ),
+ DesiredDnsRecord(
+ name: 'api.$domainName',
+ content: ip4,
+ description: 'record.api',
+ ),
+ DesiredDnsRecord(
+ name: 'cloud.$domainName',
+ content: ip4,
+ description: 'record.cloud',
+ ),
+ DesiredDnsRecord(
+ name: 'git.$domainName',
+ content: ip4,
+ description: 'record.git',
+ ),
+ DesiredDnsRecord(
+ name: 'meet.$domainName',
+ content: ip4,
+ description: 'record.meet',
+ ),
+ DesiredDnsRecord(
+ name: 'social.$domainName',
+ content: ip4,
+ description: 'record.social',
+ ),
+ DesiredDnsRecord(
+ name: 'password.$domainName',
+ content: ip4,
+ description: 'record.password',
+ ),
+ DesiredDnsRecord(
+ name: 'vpn.$domainName',
+ content: ip4,
+ description: 'record.vpn',
+ ),
+ DesiredDnsRecord(
+ name: '',
+ content: '10 $domainName.',
+ description: 'record.mx',
+ type: 'MX',
+ category: DnsRecordsCategory.email,
+ ),
+ DesiredDnsRecord(
+ name: '_dmarc.$domainName',
+ content: '"v=DMARC1; p=none"',
+ description: 'record.dmarc',
+ type: 'TXT',
+ category: DnsRecordsCategory.email,
+ ),
+ DesiredDnsRecord(
+ name: '',
+ content: '"v=spf1 a mx ip4:$ip4 -all"',
+ description: 'record.spf',
+ type: 'TXT',
+ category: DnsRecordsCategory.email,
+ ),
+ if (dkimPublicKey != null)
+ DesiredDnsRecord(
+ name: 'selector._domainkey.$domainName',
+ content: '"$dkimPublicKey"',
+ description: 'record.dkim',
+ type: 'TXT',
+ category: DnsRecordsCategory.email,
+ ),
+ ];
+ }
+}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart
new file mode 100644
index 00000000..6c10259b
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart
@@ -0,0 +1,16 @@
+import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desec/desec.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
+
+class DesecApiFactory extends DnsProviderApiFactory {
+ @override
+ DnsProviderApi getDnsProvider({
+ final DnsProviderApiSettings settings = const DnsProviderApiSettings(),
+ }) =>
+ DesecApi(
+ hasLogger: settings.hasLogger,
+ isWithToken: settings.isWithToken,
+ customToken: settings.customToken,
+ );
+}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns.dart b/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns.dart
index fd2b2e02..9a33bdfb 100644
--- a/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns.dart
+++ b/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns.dart
@@ -171,61 +171,67 @@ class DigitalOceanDnsApi extends DnsProviderApi {
return allRecords;
}
+ Future>> validateDnsRecords(
+ final ServerDomain domain,
+ final String ip4,
+ final String dkimPublicKey,
+ );
+
@override
- List getDesiredDnsRecords({
+ List getDesiredDnsRecords(
final String? domainName,
- final String? ipAddress,
+ final String? ip4,
final String? dkimPublicKey,
- }) {
- if (domainName == null || ipAddress == null) {
+ ) {
+ if (domainName == null || ip4 == null) {
return [];
}
return [
DesiredDnsRecord(
name: '@',
- content: ipAddress,
+ content: ip4,
description: 'record.root',
displayName: domainName,
),
DesiredDnsRecord(
name: 'api',
- content: ipAddress,
+ content: ip4,
description: 'record.api',
displayName: 'api.$domainName',
),
DesiredDnsRecord(
name: 'cloud',
- content: ipAddress,
+ content: ip4,
description: 'record.cloud',
displayName: 'cloud.$domainName',
),
DesiredDnsRecord(
name: 'git',
- content: ipAddress,
+ content: ip4,
description: 'record.git',
displayName: 'git.$domainName',
),
DesiredDnsRecord(
name: 'meet',
- content: ipAddress,
+ content: ip4,
description: 'record.meet',
displayName: 'meet.$domainName',
),
DesiredDnsRecord(
name: 'social',
- content: ipAddress,
+ content: ip4,
description: 'record.social',
displayName: 'social.$domainName',
),
DesiredDnsRecord(
name: 'password',
- content: ipAddress,
+ content: ip4,
description: 'record.password',
displayName: 'password.$domainName',
),
DesiredDnsRecord(
name: 'vpn',
- content: ipAddress,
+ content: ip4,
description: 'record.vpn',
displayName: 'vpn.$domainName',
),
@@ -245,7 +251,7 @@ class DigitalOceanDnsApi extends DnsProviderApi {
),
DesiredDnsRecord(
name: '@',
- content: 'v=spf1 a mx ip4:$ipAddress -all',
+ content: 'v=spf1 a mx ip4:$ip4 -all',
description: 'record.spf',
type: 'TXT',
category: DnsRecordsCategory.email,
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart
index 299928b0..af9d3e6b 100644
--- a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart
+++ b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart
@@ -3,6 +3,7 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.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/dns_records.dart';
+import 'package:selfprivacy/utils/network_utils.dart';
export 'package:selfprivacy/logic/api_maps/generic_result.dart';
export 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
@@ -16,11 +17,7 @@ abstract class DnsProviderApi extends ApiMap {
Future> getDnsRecords({
required final ServerDomain domain,
});
- List getDesiredDnsRecords({
- final String? domainName,
- final String? ipAddress,
- final String? dkimPublicKey,
- });
+
Future> removeSimilarRecords({
required final ServerDomain domain,
final String? ip4,
@@ -33,6 +30,16 @@ abstract class DnsProviderApi extends ApiMap {
final DnsRecord record,
final ServerDomain domain,
);
+ Future>> validateDnsRecords(
+ final ServerDomain domain,
+ final String ip4,
+ final String dkimPublicKey,
+ );
+ List getDesiredDnsRecords(
+ final String? domainName,
+ final String? ip4,
+ final String? dkimPublicKey,
+ );
Future getZoneId(final String domain);
Future> domainList();
diff --git a/lib/logic/api_maps/staging_options.dart b/lib/logic/api_maps/staging_options.dart
index 7d3084b7..a4e98fe8 100644
--- a/lib/logic/api_maps/staging_options.dart
+++ b/lib/logic/api_maps/staging_options.dart
@@ -1,8 +1,16 @@
-/// Controls staging environment for network, is used during manual
-/// integration testing and such
+/// Controls staging environment for network
class StagingOptions {
/// Whether we request for staging temprorary certificates.
/// Hardcode to 'true' in the middle of testing to not
/// get your domain banned by constant certificate renewal
+ ///
+ /// If set to 'true', the 'verifyCertificate' becomes useless
static bool get stagingAcme => false;
+
+ /// Should we consider CERTIFICATE_VERIFY_FAILED code an error
+ /// For now it's just a global variable and DNS API
+ /// classes can change it at will
+ ///
+ /// Doesn't matter if 'statingAcme' is set to 'true'
+ static bool verifyCertificate = false;
}
diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart
index 3905ff33..4a2deea4 100644
--- a/lib/logic/cubit/dns_records/dns_records_cubit.dart
+++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart
@@ -25,12 +25,14 @@ class DnsRecordsCubit
emit(
DnsRecordsState(
dnsState: DnsRecordsStatus.refreshing,
- dnsRecords:
- ProvidersController.currentDnsProvider!.getDesiredDnsRecords(
- domainName: serverInstallationCubit.state.serverDomain?.domainName,
- dkimPublicKey: '',
- ipAddress: '',
- ),
+ dnsRecords: ApiController.currentDnsProviderApiFactory
+ ?.getDnsProvider()
+ .getDesiredDnsRecords(
+ serverInstallationCubit.state.serverDomain?.domainName,
+ '',
+ '',
+ ) ??
+ [],
),
);
@@ -38,68 +40,32 @@ class DnsRecordsCubit
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
final String? ipAddress =
serverInstallationCubit.state.serverDetails?.ip4;
- if (domain != null && ipAddress != null) {
- final List records = await ProvidersController
- .currentDnsProvider!
- .getDnsRecords(domain: domain);
- final String? dkimPublicKey =
- extractDkimRecord(await api.getDnsRecords())?.content;
- final List desiredRecords =
- ProvidersController.currentDnsProvider!.getDesiredDnsRecords(
- domainName: domain.domainName,
- ipAddress: ipAddress,
- dkimPublicKey: dkimPublicKey,
- );
- final List foundRecords = [];
- for (final DesiredDnsRecord desiredRecord in desiredRecords) {
- if (desiredRecord.description == 'record.dkim') {
- final DnsRecord foundRecord = records.firstWhere(
- (final r) =>
- r.name == desiredRecord.name && r.type == desiredRecord.type,
- orElse: () => DnsRecord(
- name: desiredRecord.name,
- type: desiredRecord.type,
- content: '',
- ttl: 800,
- proxied: false,
- ),
- );
- // remove all spaces and tabulators from
- // the foundRecord.content and the record.content
- // to compare them
- final String? foundContent =
- foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
- final String content =
- desiredRecord.content.replaceAll(RegExp(r'\s+'), '');
- if (foundContent == content) {
- foundRecords.add(desiredRecord.copyWith(isSatisfied: true));
- } else {
- foundRecords.add(desiredRecord.copyWith(isSatisfied: false));
- }
- } else {
- if (records.any(
- (final r) =>
- r.name == desiredRecord.name &&
- r.type == desiredRecord.type &&
- r.content == desiredRecord.content,
- )) {
- foundRecords.add(desiredRecord.copyWith(isSatisfied: true));
- } else {
- foundRecords.add(desiredRecord.copyWith(isSatisfied: false));
- }
- }
- }
- emit(
- DnsRecordsState(
- dnsRecords: foundRecords,
- dnsState: foundRecords.any((final r) => r.isSatisfied == false)
- ? DnsRecordsStatus.error
- : DnsRecordsStatus.good,
- ),
- );
- } else {
+ if (domain == null && ipAddress == null) {
emit(const DnsRecordsState());
+ return;
}
+
+ final foundRecords = await ApiController.currentDnsProviderApiFactory!
+ .getDnsProvider()
+ .validateDnsRecords(
+ domain!,
+ ipAddress!,
+ extractDkimRecord(await api.getDnsRecords())?.content ?? '',
+ );
+
+ if (!foundRecords.success || foundRecords.data.isEmpty) {
+ emit(const DnsRecordsState());
+ return;
+ }
+
+ emit(
+ DnsRecordsState(
+ dnsRecords: foundRecords.data,
+ dnsState: foundRecords.data.any((final r) => r.isSatisfied == false)
+ ? DnsRecordsStatus.error
+ : DnsRecordsStatus.good,
+ ),
+ );
}
}
diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart
index ca8b0958..52740b64 100644
--- a/lib/logic/cubit/server_installation/server_installation_cubit.dart
+++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart
@@ -9,6 +9,9 @@ import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
import 'package:selfprivacy/logic/models/launch_installation_data.dart';
import 'package:selfprivacy/logic/providers/provider_settings.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
+import 'package:selfprivacy/logic/api_maps/staging_options.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
@@ -182,7 +185,7 @@ class ServerInstallationCubit extends Cubit {
void setDnsApiToken(final String dnsApiToken) async {
if (state is ServerInstallationRecovery) {
- await setAndValidateCloudflareToken(dnsApiToken);
+ await setAndValidateDnsApiToken(dnsApiToken);
return;
}
await repository.setDnsApiToken(dnsApiToken);
@@ -429,6 +432,7 @@ class ServerInstallationCubit extends Cubit {
emit(TimerState(dataState: dataState, isLoading: true));
final bool isServerWorking = await repository.isHttpServerWorking();
+ StagingOptions.verifyCertificate = true;
if (isServerWorking) {
bool dkimCreated = true;
@@ -534,21 +538,18 @@ class ServerInstallationCubit extends Cubit {
customToken: serverDetails.apiToken,
isWithToken: true,
).getServerProviderType();
- final DnsProviderType dnsProvider = await ServerApi(
+ final dnsProvider = await ServerApi(
customToken: serverDetails.apiToken,
isWithToken: true,
).getDnsProviderType();
- if (serverProvider == ServerProviderType.unknown) {
- getIt()
- .showSnackBar('recovering.generic_error'.tr());
- return;
- }
- if (dnsProvider == DnsProviderType.unknown) {
+ if (serverProvider == ServerProviderType.unknown ||
+ dnsProvider == DnsProviderType.unknown) {
getIt()
.showSnackBar('recovering.generic_error'.tr());
return;
}
await repository.saveServerDetails(serverDetails);
+ await repository.saveDnsProviderType(dnsProvider);
setServerProviderType(serverProvider);
setDnsProviderType(dnsProvider);
emit(
@@ -689,7 +690,7 @@ class ServerInstallationCubit extends Cubit {
);
}
- Future setAndValidateCloudflareToken(final String token) async {
+ Future setAndValidateDnsApiToken(final String token) async {
final ServerInstallationRecovery dataState =
state as ServerInstallationRecovery;
final ServerDomain? serverDomain = dataState.serverDomain;
@@ -703,11 +704,15 @@ class ServerInstallationCubit extends Cubit {
.showSnackBar('recovering.domain_not_available_on_token'.tr());
return;
}
+ final dnsProviderType = await ServerApi(
+ customToken: dataState.serverDetails!.apiToken,
+ isWithToken: true,
+ ).getDnsProviderType();
await repository.saveDomain(
ServerDomain(
domainName: serverDomain.domainName,
zoneId: zoneId,
- provider: DnsProviderType.cloudflare,
+ provider: dnsProviderType,
),
);
await repository.setDnsApiToken(token);
@@ -716,7 +721,7 @@ class ServerInstallationCubit extends Cubit {
serverDomain: ServerDomain(
domainName: serverDomain.domainName,
zoneId: zoneId,
- provider: DnsProviderType.cloudflare,
+ provider: dnsProviderType,
),
dnsApiToken: token,
currentStep: RecoveryStep.backblazeToken,
@@ -750,6 +755,7 @@ class ServerInstallationCubit extends Cubit {
void clearAppConfig() {
closeTimer();
ProvidersController.clearProviders();
+ StagingOptions.verifyCertificate = false;
repository.clearAppConfig();
emit(const ServerInstallationEmpty());
}
diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart
index 6c352a88..fb3470ed 100644
--- a/lib/logic/cubit/server_installation/server_installation_repository.dart
+++ b/lib/logic/cubit/server_installation/server_installation_repository.dart
@@ -10,17 +10,18 @@ import 'package:hive/hive.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/hive_config.dart';
+import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/providers/provider_settings.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
+import 'package:selfprivacy/logic/api_maps/staging_options.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/device_token.dart';
-import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/models/message.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart';
import 'package:selfprivacy/logic/models/server_type.dart';
@@ -45,7 +46,7 @@ class ServerInstallationRepository {
Future load() async {
final String? providerApiToken = getIt().serverProviderKey;
final String? location = getIt().serverLocation;
- final String? cloudflareToken = getIt().dnsProviderKey;
+ final String? dnsApiToken = getIt().dnsProviderKey;
final String? serverTypeIdentificator = getIt().serverType;
final ServerDomain? serverDomain = getIt().serverDomain;
final DnsProviderType? dnsProvider = getIt().dnsProvider;
@@ -78,10 +79,11 @@ class ServerInstallationRepository {
}
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
+ StagingOptions.verifyCertificate = true;
return ServerInstallationFinished(
providerApiToken: providerApiToken!,
serverTypeIdentificator: serverTypeIdentificator ?? '',
- dnsApiToken: cloudflareToken!,
+ dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!,
serverDetails: serverDetails!,
@@ -98,14 +100,14 @@ class ServerInstallationRepository {
serverDomain != null) {
return ServerInstallationRecovery(
providerApiToken: providerApiToken,
- dnsApiToken: cloudflareToken,
+ dnsApiToken: dnsApiToken,
serverDomain: serverDomain,
backblazeCredential: backblazeCredential,
serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser),
currentStep: _getCurrentRecoveryStep(
providerApiToken,
- cloudflareToken,
+ dnsApiToken,
serverDomain,
serverDetails,
),
@@ -115,7 +117,7 @@ class ServerInstallationRepository {
return ServerInstallationNotFinished(
providerApiToken: providerApiToken,
- dnsApiToken: cloudflareToken,
+ dnsApiToken: dnsApiToken,
serverDomain: serverDomain,
backblazeCredential: backblazeCredential,
serverDetails: serverDetails,
@@ -603,6 +605,10 @@ class ServerInstallationRepository {
getIt().init();
}
+ Future saveDnsProviderType(final DnsProvider type) async {
+ await getIt().storeDnsProviderType(type);
+ }
+
Future saveBackblazeKey(
final BackblazeCredential backblazeCredential,
) async {
@@ -618,7 +624,7 @@ class ServerInstallationRepository {
await getIt().storeDnsProviderKey(key);
}
- Future deleteCloudFlareKey() async {
+ Future deleteDnsProviderKey() async {
await box.delete(BNames.cloudFlareKey);
getIt().init();
}
diff --git a/lib/logic/models/hive/server_domain.dart b/lib/logic/models/hive/server_domain.dart
index 2d9a554b..1649be2a 100644
--- a/lib/logic/models/hive/server_domain.dart
+++ b/lib/logic/models/hive/server_domain.dart
@@ -31,12 +31,16 @@ enum DnsProviderType {
@HiveField(1)
cloudflare,
@HiveField(2)
+ desec,
+ @HiveField(3)
digitalOcean;
factory DnsProviderType.fromGraphQL(final Enum$DnsProvider provider) {
switch (provider) {
case Enum$DnsProvider.CLOUDFLARE:
return cloudflare;
+ case Enum$DnsProvider.DESEC:
+ return desec;
case Enum$DnsProvider.DIGITALOCEAN:
return digitalOcean;
default:
diff --git a/lib/logic/models/hive/server_domain.g.dart b/lib/logic/models/hive/server_domain.g.dart
index 19f1ef6f..303407bc 100644
--- a/lib/logic/models/hive/server_domain.g.dart
+++ b/lib/logic/models/hive/server_domain.g.dart
@@ -60,6 +60,8 @@ class DnsProviderTypeAdapter extends TypeAdapter {
case 1:
return DnsProviderType.cloudflare;
case 2:
+ return DnsProviderType.desec;
+ case 3:
return DnsProviderType.digitalOcean;
default:
return DnsProviderType.unknown;
@@ -75,9 +77,12 @@ class DnsProviderTypeAdapter extends TypeAdapter {
case DnsProviderType.cloudflare:
writer.writeByte(1);
break;
- case DnsProviderType.digitalOcean:
+ case DnsProviderType.desec:
writer.writeByte(2);
break;
+ case DnsProviderType.digitalOcean:
+ writer.writeByte(3);
+ break;
}
}
diff --git a/lib/logic/providers/dns_providers/desec.dart b/lib/logic/providers/dns_providers/desec.dart
new file mode 100644
index 00000000..c7a5bab8
--- /dev/null
+++ b/lib/logic/providers/dns_providers/desec.dart
@@ -0,0 +1,3 @@
+import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
+
+class DesecDnsProvider extends DnsProvider {}
diff --git a/lib/logic/providers/dns_providers/dns_provider_factory.dart b/lib/logic/providers/dns_providers/dns_provider_factory.dart
index a5adebc9..b42854b7 100644
--- a/lib/logic/providers/dns_providers/dns_provider_factory.dart
+++ b/lib/logic/providers/dns_providers/dns_provider_factory.dart
@@ -1,5 +1,6 @@
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/providers/dns_providers/cloudflare.dart';
+import 'package:selfprivacy/logic/providers/dns_providers/desec.dart';
import 'package:selfprivacy/logic/providers/dns_providers/digital_ocean.dart';
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
import 'package:selfprivacy/logic/providers/provider_settings.dart';
@@ -18,6 +19,8 @@ class DnsProviderFactory {
return CloudflareDnsProvider();
case DnsProviderType.digitalOcean:
return DigitalOceanDnsProvider();
+ case DnsProviderType.desec:
+ return DesecDnsProvider();
case DnsProviderType.unknown:
throw UnknownProviderException('Unknown server provider');
}
diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart
index 54e493de..1fa5c932 100644
--- a/lib/ui/pages/more/about_application.dart
+++ b/lib/ui/pages/more/about_application.dart
@@ -50,7 +50,7 @@ class AboutApplicationPage extends StatelessWidget {
children: [
TextButton(
onPressed: () => launchUrl(
- Uri.parse('https://selfprivacy.ru/privacy-policy'),
+ Uri.parse('https://selfprivacy.org/privacy-policy/'),
mode: LaunchMode.externalApplication,
),
child: Text('about_application_page.privacy_policy'.tr()),
diff --git a/lib/ui/pages/setup/initializing/dns_provider_picker.dart b/lib/ui/pages/setup/initializing/dns_provider_picker.dart
index 92f1a36d..fd839092 100644
--- a/lib/ui/pages/setup/initializing/dns_provider_picker.dart
+++ b/lib/ui/pages/setup/initializing/dns_provider_picker.dart
@@ -11,6 +11,8 @@ import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
import 'package:selfprivacy/utils/network_utils.dart';
+import 'package:selfprivacy/utils/launch_url.dart';
+import 'package:url_launcher/url_launcher_string.dart';
class DnsProviderPicker extends StatefulWidget {
const DnsProviderPicker({
@@ -69,6 +71,19 @@ class _DnsProviderPickerState extends State {
),
),
);
+
+ case DnsProviderType.desec:
+ return ProviderInputDataPage(
+ providerCubit: widget.formCubit,
+ providerInfo: ProviderPageInfo(
+ providerType: DnsProviderType.desec,
+ pathToHow: 'how_desec',
+ image: Image.asset(
+ 'assets/images/logos/desec.svg',
+ width: 150,
+ ),
+ ),
+ );
}
}
}
@@ -186,7 +201,7 @@ class ProviderSelectionPage extends StatelessWidget {
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
- color: const Color.fromARGB(255, 241, 215, 166),
+ color: const Color.fromARGB(255, 244, 128, 31),
),
child: SvgPicture.asset(
'assets/images/logos/cloudflare.svg',
@@ -230,7 +245,7 @@ class ProviderSelectionPage extends StatelessWidget {
// Outlined button that will open website
BrandOutlinedButton(
onPressed: () =>
- launchURL('https://dash.cloudflare.com/'),
+ launchUrlString('https://dash.cloudflare.com/'),
title: 'initializing.select_provider_site_button'.tr(),
),
],
@@ -295,7 +310,71 @@ class ProviderSelectionPage extends StatelessWidget {
// Outlined button that will open website
BrandOutlinedButton(
onPressed: () =>
- launchURL('https://www.digitalocean.com'),
+ launchUrlString('https://www.digitalocean.com'),
+ title: 'initializing.select_provider_site_button'.tr(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+ OutlinedCard(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Container(
+ width: 40,
+ height: 40,
+ padding: const EdgeInsets.all(10),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(40),
+ color: const Color.fromARGB(255, 245, 229, 82),
+ ),
+ child: SvgPicture.asset(
+ 'assets/images/logos/desec.svg',
+ ),
+ ),
+ const SizedBox(width: 16),
+ Text(
+ 'deSEC',
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'initializing.select_provider_price_title'.tr(),
+ style: Theme.of(context).textTheme.bodyLarge,
+ ),
+ Text(
+ 'initializing.select_provider_price_free'.tr(),
+ style: Theme.of(context).textTheme.bodySmall,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'initializing.select_provider_payment_title'.tr(),
+ style: Theme.of(context).textTheme.bodyLarge,
+ ),
+ Text(
+ 'initializing.select_provider_payment_text_do'.tr(),
+ style: Theme.of(context).textTheme.bodySmall,
+ ),
+ const SizedBox(height: 16),
+ BrandButton.rised(
+ text: 'basis.select'.tr(),
+ onPressed: () {
+ serverInstallationCubit
+ .setDnsProviderType(DnsProviderType.desec);
+ callback(DnsProviderType.desec);
+ },
+ ),
+ // Outlined button that will open website
+ BrandOutlinedButton(
+ onPressed: () => launchUrlString('https://desec.io/'),
title: 'initializing.select_provider_site_button'.tr(),
),
],
diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart b/lib/ui/pages/setup/recovering/recovery_confirm_dns.dart
similarity index 96%
rename from lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart
rename to lib/ui/pages/setup/recovering/recovery_confirm_dns.dart
index 93c889a5..9dcad056 100644
--- a/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart
+++ b/lib/ui/pages/setup/recovering/recovery_confirm_dns.dart
@@ -7,8 +7,8 @@ import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
-class RecoveryConfirmCloudflare extends StatelessWidget {
- const RecoveryConfirmCloudflare({super.key});
+class RecoveryConfirmDns extends StatelessWidget {
+ const RecoveryConfirmDns({super.key});
@override
Widget build(final BuildContext context) {
diff --git a/lib/ui/pages/setup/recovering/recovery_routing.dart b/lib/ui/pages/setup/recovering/recovery_routing.dart
index 0d11c4d8..be5eb2ea 100644
--- a/lib/ui/pages/setup/recovering/recovery_routing.dart
+++ b/lib/ui/pages/setup/recovering/recovery_routing.dart
@@ -12,7 +12,7 @@ import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart'
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart';
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_new_device_key.dart';
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_backblaze.dart';
-import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart';
+import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_dns.dart';
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_server.dart';
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_server_provider_connected.dart';
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
@@ -56,7 +56,7 @@ class RecoveryRouting extends StatelessWidget {
currentPage = const RecoveryConfirmServer();
break;
case RecoveryStep.dnsProviderToken:
- currentPage = const RecoveryConfirmCloudflare();
+ currentPage = const RecoveryConfirmDns();
break;
case RecoveryStep.backblazeToken:
currentPage = const RecoveryConfirmBackblaze();
diff --git a/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart
index 9b6cb09e..d8a9e8cb 100644
--- a/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart
+++ b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart
@@ -46,9 +46,8 @@ class RecoveryServerProviderConnected extends StatelessWidget {
),
const SizedBox(height: 16),
BrandButton.filled(
- onPressed: formCubitState.isSubmitting
- ? null
- : () => context.read().trySubmit(),
+ onPressed: () =>
+ context.read().trySubmit(),
child: Text('basis.continue'.tr()),
),
const SizedBox(height: 16),