mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-20 03:19:15 +00:00
chore: Merge desec into refactoring
This commit is contained in:
commit
4260152081
|
@ -1,10 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="116px" viewBox="0 0 256 116" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<g transform="translate(0.000000, -1.000000)">
|
||||
<path d="M202.3569,50.394 L197.0459,48.27 C172.0849,104.434 72.7859,70.289 66.8109,86.997 C65.8149,98.283 121.0379,89.143 160.5169,91.056 C172.5559,91.639 178.5929,100.727 173.4809,115.54 L183.5499,115.571 C195.1649,79.362 232.2329,97.841 233.7819,85.891 C231.2369,78.034 191.1809,85.891 202.3569,50.394 Z" fill="#FFFFFF"></path>
|
||||
<path d="M176.332,109.3483 C177.925,104.0373 177.394,98.7263 174.739,95.5393 C172.083,92.3523 168.365,90.2283 163.585,89.6973 L71.17,88.6343 C70.639,88.6343 70.108,88.1033 69.577,88.1033 C69.046,87.5723 69.046,87.0413 69.577,86.5103 C70.108,85.4483 70.639,84.9163 71.701,84.9163 L164.647,83.8543 C175.801,83.3233 187.486,74.2943 191.734,63.6723 L197.046,49.8633 C197.046,49.3313 197.577,48.8003 197.046,48.2693 C191.203,21.1823 166.772,0.9993 138.091,0.9993 C111.535,0.9993 88.697,17.9953 80.73,41.8963 C75.419,38.1783 69.046,36.0533 61.61,36.5853 C48.863,37.6473 38.772,48.2693 37.178,61.0163 C36.647,64.2033 37.178,67.3903 37.71,70.5763 C16.996,71.1073 0,88.1033 0,109.3483 C0,111.4723 0,113.0663 0.531,115.1903 C0.531,116.2533 1.593,116.7843 2.125,116.7843 L172.614,116.7843 C173.676,116.7843 174.739,116.2533 174.739,115.1903 L176.332,109.3483 Z" fill="#F4811F"></path>
|
||||
<path d="M205.5436,49.8628 L202.8876,49.8628 C202.3566,49.8628 201.8256,50.3938 201.2946,50.9248 L197.5766,63.6718 C195.9836,68.9828 196.5146,74.2948 199.1706,77.4808 C201.8256,80.6678 205.5436,82.7918 210.3236,83.3238 L229.9756,84.3858 C230.5066,84.3858 231.0376,84.9168 231.5686,84.9168 C232.0996,85.4478 232.0996,85.9788 231.5686,86.5098 C231.0376,87.5728 230.5066,88.1038 229.4436,88.1038 L209.2616,89.1658 C198.1076,89.6968 186.4236,98.7258 182.1746,109.3478 L181.1116,114.1288 C180.5806,114.6598 181.1116,115.7218 182.1746,115.7218 L252.2826,115.7218 C253.3446,115.7218 253.8756,115.1908 253.8756,114.1288 C254.9376,109.8798 255.9996,105.0998 255.9996,100.3188 C255.9996,72.7008 233.1616,49.8628 205.5436,49.8628" fill="#FAAD3F"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 209.51 94.74"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M143.05,93.42l1.07-3.71c1.27-4.41.8-8.48-1.34-11.48-2-2.76-5.26-4.38-9.25-4.57L58,72.7a1.47,1.47,0,0,1-1.35-2,2,2,0,0,1,1.75-1.34l76.26-1c9-.41,18.84-7.75,22.27-16.71l4.34-11.36a2.68,2.68,0,0,0,.18-1,3.31,3.31,0,0,0-.06-.54,49.67,49.67,0,0,0-95.49-5.14,22.35,22.35,0,0,0-35,23.42A31.73,31.73,0,0,0,.34,93.45a1.47,1.47,0,0,0,1.45,1.27l139.49,0h0A1.83,1.83,0,0,0,143.05,93.42Z"/><path class="cls-1" d="M168.22,41.15q-1,0-2.1.06a.88.88,0,0,0-.32.07,1.17,1.17,0,0,0-.76.8l-3,10.26c-1.28,4.41-.81,8.48,1.34,11.48a11.65,11.65,0,0,0,9.24,4.57l16.11,1a1.44,1.44,0,0,1,1.14.62,1.5,1.5,0,0,1,.17,1.37,2,2,0,0,1-1.75,1.34l-16.73,1c-9.09.42-18.88,7.75-22.31,16.7l-1.21,3.16a.9.9,0,0,0,.79,1.22h57.63A1.55,1.55,0,0,0,208,93.63a41.34,41.34,0,0,0-39.76-52.48Z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 923 B |
89
assets/images/logos/desec.svg
Normal file
89
assets/images/logos/desec.svg
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="7.4053912mm"
|
||||
height="7.5173831mm"
|
||||
viewBox="0 0 7.4053913 7.5173831"
|
||||
version="1.1"
|
||||
id="svg1262"
|
||||
sodipodi:docname="logo.notext.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<defs
|
||||
id="defs1256" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="101.86078"
|
||||
inkscape:cy="8.9271745"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="g3885"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1365"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata1259">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-254.94057,-266.78298)">
|
||||
<g
|
||||
id="g3885"
|
||||
transform="matrix(0.26519825,0,0,0.26519825,228.89366,215.69135)"
|
||||
style="fill:#000000">
|
||||
<g
|
||||
style="fill:#000000;stroke:#ffffff;stroke-opacity:1"
|
||||
id="layer1-9"
|
||||
transform="matrix(0.22901929,0,0,0.22901929,26.296508,84.906304)"
|
||||
inkscape:export-filename="/home/nils/git/desec-stack/webapp/src/assets/logo.png"
|
||||
inkscape:export-xdpi="567.52002"
|
||||
inkscape:export-ydpi="567.52002">
|
||||
<g
|
||||
style="fill:#000000;stroke:#ffffff;stroke-opacity:1"
|
||||
transform="translate(-194.13584,150.8067)"
|
||||
id="g3933">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 509.13584,366.2239 c 8.87906,-33.13708 42.93987,-52.8021 76.07695,-43.92304 21.43594,5.74374 38.17931,22.48711 43.92305,43.92304 0,0 -6.09923,-6.07815 -10,-6.07815 -3.90077,0 -10,6.07815 -10,6.07815 0,0 -6.09923,-6.07815 -10,-6.07815 -3.90077,0 -10,6.07815 -10,6.07815 0,0 -6.09923,-6.07815 -10,-6.07815 -3.90077,0 -10,6.07815 -10,6.07815 0,0 -6.09923,-6.07815 -10,-6.07815 -3.90077,0 -10,6.07815 -10,6.07815 0,0 -6.09923,-6.07815 -10,-6.07815 -3.90077,0 -10,6.07815 -10,6.07815 0,0 -6.09923,-6.07815 -10,-6.07815 -3.90077,0 -10,6.07815 -10,6.07815 z"
|
||||
id="path2985-6-3"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 567.42674,364.89583 v 61.87321 c 0,9.34738 5.48085,16.17306 12.23879,16.17306 6.75795,0 12.23635,-6.83606 12.23635,-16.18344 0,0 -1.07806,-1.02674 -1.75904,-1.03964 -0.64261,-0.0122 -1.69589,0.91753 -1.69589,0.91753 0,6.70817 -3.93157,13.01592 -8.78142,13.01592 -4.84984,0 -8.78142,-6.30775 -8.78142,-13.01592 l -7.6e-4,-61.74072 z"
|
||||
id="path3775-7-4-6"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:117.14173126;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4 KiB |
9
assets/markdown/how_desec-en.md
Normal file
9
assets/markdown/how_desec-en.md
Normal file
|
@ -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.
|
9
assets/markdown/how_desec-ru.md
Normal file
9
assets/markdown/how_desec-ru.md
Normal file
|
@ -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**.
|
|
@ -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:"
|
||||
},
|
||||
|
|
|
@ -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": "Длина строки [], должна быть меньше либо равна {}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ class ResponseLoggingParser extends ResponseParser {
|
|||
abstract class ApiMap {
|
||||
Future<GraphQLClient> getClient() async {
|
||||
IOClient? ioClient;
|
||||
if (StagingOptions.stagingAcme) {
|
||||
if (StagingOptions.stagingAcme || !StagingOptions.verifyCertificate) {
|
||||
final HttpClient httpClient = HttpClient();
|
||||
httpClient.badCertificateCallback = (
|
||||
final cert,
|
||||
|
|
|
@ -76,7 +76,8 @@ type DeviceApiTokenMutationReturn implements MutationReturnInterface {
|
|||
|
||||
enum DnsProvider {
|
||||
CLOUDFLARE,
|
||||
DIGITALOCEAN
|
||||
DIGITALOCEAN,
|
||||
DESEC
|
||||
}
|
||||
|
||||
type DnsRecord {
|
||||
|
|
|
@ -1096,7 +1096,7 @@ class _CopyWithStubImpl$Input$UserMutationInput<TRes>
|
|||
_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;
|
||||
}
|
||||
|
|
|
@ -189,88 +189,6 @@ class CloudflareApi extends DnsProviderApi {
|
|||
return allRecords;
|
||||
}
|
||||
|
||||
@override
|
||||
List<DesiredDnsRecord> 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<GenericResult<void>> createMultipleDnsRecords({
|
||||
required final ServerDomain domain,
|
||||
|
@ -353,4 +271,147 @@ class CloudflareApi extends DnsProviderApi {
|
|||
|
||||
return domains;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<DesiredDnsRecord>>> validateDnsRecords(
|
||||
final ServerDomain domain,
|
||||
final String ip4,
|
||||
final String dkimPublicKey,
|
||||
) async {
|
||||
final List<DnsRecord> records = await getDnsRecords(domain: domain);
|
||||
final List<DesiredDnsRecord> foundRecords = [];
|
||||
try {
|
||||
final List<DesiredDnsRecord> 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<DesiredDnsRecord> 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,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
475
lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart
Normal file
475
lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart
Normal file
|
@ -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<ApiConfigModel>().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<GenericResult<bool>> 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<String?> getZoneId(final String domain) async => domain;
|
||||
|
||||
@override
|
||||
Future<GenericResult<void>> removeSimilarRecords({
|
||||
required final ServerDomain domain,
|
||||
final String? ip4,
|
||||
}) async {
|
||||
final String domainName = domain.domainName;
|
||||
final String url = '/$domainName/rrsets/';
|
||||
final List<DnsRecord> listDnsRecords = projectDnsRecords(domainName, ip4);
|
||||
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
final List<dynamic> 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<List<DnsRecord>> getDnsRecords({
|
||||
required final ServerDomain domain,
|
||||
}) async {
|
||||
Response response;
|
||||
final String domainName = domain.domainName;
|
||||
final List<DnsRecord> allRecords = <DnsRecord>[];
|
||||
|
||||
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<dynamic>)
|
||||
? 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<GenericResult<void>> createMultipleDnsRecords({
|
||||
required final ServerDomain domain,
|
||||
final String? ip4,
|
||||
}) async {
|
||||
final String domainName = domain.domainName;
|
||||
final List<DnsRecord> listDnsRecords = projectDnsRecords(domainName, ip4);
|
||||
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
final List<dynamic> 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<DnsRecord> 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 <DnsRecord>[
|
||||
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<void> 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<List<String>> domainList() async {
|
||||
List<String> domains = [];
|
||||
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
final Response response = await client.get(
|
||||
'',
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
domains = response.data
|
||||
.map<String>((final el) => el['name'] as String)
|
||||
.toList();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
|
||||
return domains;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<DesiredDnsRecord>>> validateDnsRecords(
|
||||
final ServerDomain domain,
|
||||
final String ip4,
|
||||
final String dkimPublicKey,
|
||||
) async {
|
||||
final List<DnsRecord> records = await getDnsRecords(domain: domain);
|
||||
final List<DesiredDnsRecord> foundRecords = [];
|
||||
try {
|
||||
final List<DesiredDnsRecord> 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<DesiredDnsRecord> 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,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -171,61 +171,67 @@ class DigitalOceanDnsApi extends DnsProviderApi {
|
|||
return allRecords;
|
||||
}
|
||||
|
||||
Future<GenericResult<List<DesiredDnsRecord>>> validateDnsRecords(
|
||||
final ServerDomain domain,
|
||||
final String ip4,
|
||||
final String dkimPublicKey,
|
||||
);
|
||||
|
||||
@override
|
||||
List<DesiredDnsRecord> getDesiredDnsRecords({
|
||||
List<DesiredDnsRecord> 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,
|
||||
|
|
|
@ -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<List<DnsRecord>> getDnsRecords({
|
||||
required final ServerDomain domain,
|
||||
});
|
||||
List<DesiredDnsRecord> getDesiredDnsRecords({
|
||||
final String? domainName,
|
||||
final String? ipAddress,
|
||||
final String? dkimPublicKey,
|
||||
});
|
||||
|
||||
Future<GenericResult<void>> removeSimilarRecords({
|
||||
required final ServerDomain domain,
|
||||
final String? ip4,
|
||||
|
@ -33,6 +30,16 @@ abstract class DnsProviderApi extends ApiMap {
|
|||
final DnsRecord record,
|
||||
final ServerDomain domain,
|
||||
);
|
||||
Future<GenericResult<List<DesiredDnsRecord>>> validateDnsRecords(
|
||||
final ServerDomain domain,
|
||||
final String ip4,
|
||||
final String dkimPublicKey,
|
||||
);
|
||||
List<DesiredDnsRecord> getDesiredDnsRecords(
|
||||
final String? domainName,
|
||||
final String? ip4,
|
||||
final String? dkimPublicKey,
|
||||
);
|
||||
Future<String?> getZoneId(final String domain);
|
||||
Future<List<String>> domainList();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<DnsRecord> records = await ProvidersController
|
||||
.currentDnsProvider!
|
||||
.getDnsRecords(domain: domain);
|
||||
final String? dkimPublicKey =
|
||||
extractDkimRecord(await api.getDnsRecords())?.content;
|
||||
final List<DesiredDnsRecord> desiredRecords =
|
||||
ProvidersController.currentDnsProvider!.getDesiredDnsRecords(
|
||||
domainName: domain.domainName,
|
||||
ipAddress: ipAddress,
|
||||
dkimPublicKey: dkimPublicKey,
|
||||
);
|
||||
final List<DesiredDnsRecord> 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ServerInstallationState> {
|
|||
|
||||
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<ServerInstallationState> {
|
|||
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<ServerInstallationState> {
|
|||
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<NavigationService>()
|
||||
.showSnackBar('recovering.generic_error'.tr());
|
||||
return;
|
||||
}
|
||||
if (dnsProvider == DnsProviderType.unknown) {
|
||||
if (serverProvider == ServerProviderType.unknown ||
|
||||
dnsProvider == DnsProviderType.unknown) {
|
||||
getIt<NavigationService>()
|
||||
.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<ServerInstallationState> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> setAndValidateCloudflareToken(final String token) async {
|
||||
Future<void> setAndValidateDnsApiToken(final String token) async {
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
final ServerDomain? serverDomain = dataState.serverDomain;
|
||||
|
@ -703,11 +704,15 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
.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<ServerInstallationState> {
|
|||
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<ServerInstallationState> {
|
|||
void clearAppConfig() {
|
||||
closeTimer();
|
||||
ProvidersController.clearProviders();
|
||||
StagingOptions.verifyCertificate = false;
|
||||
repository.clearAppConfig();
|
||||
emit(const ServerInstallationEmpty());
|
||||
}
|
||||
|
|
|
@ -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<ServerInstallationState> load() async {
|
||||
final String? providerApiToken = getIt<ApiConfigModel>().serverProviderKey;
|
||||
final String? location = getIt<ApiConfigModel>().serverLocation;
|
||||
final String? cloudflareToken = getIt<ApiConfigModel>().dnsProviderKey;
|
||||
final String? dnsApiToken = getIt<ApiConfigModel>().dnsProviderKey;
|
||||
final String? serverTypeIdentificator = getIt<ApiConfigModel>().serverType;
|
||||
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
|
||||
final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().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<ApiConfigModel>().init();
|
||||
}
|
||||
|
||||
Future<void> saveDnsProviderType(final DnsProvider type) async {
|
||||
await getIt<ApiConfigModel>().storeDnsProviderType(type);
|
||||
}
|
||||
|
||||
Future<void> saveBackblazeKey(
|
||||
final BackblazeCredential backblazeCredential,
|
||||
) async {
|
||||
|
@ -618,7 +624,7 @@ class ServerInstallationRepository {
|
|||
await getIt<ApiConfigModel>().storeDnsProviderKey(key);
|
||||
}
|
||||
|
||||
Future<void> deleteCloudFlareKey() async {
|
||||
Future<void> deleteDnsProviderKey() async {
|
||||
await box.delete(BNames.cloudFlareKey);
|
||||
getIt<ApiConfigModel>().init();
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -60,6 +60,8 @@ class DnsProviderTypeAdapter extends TypeAdapter<DnsProviderType> {
|
|||
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<DnsProviderType> {
|
|||
case DnsProviderType.cloudflare:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case DnsProviderType.digitalOcean:
|
||||
case DnsProviderType.desec:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case DnsProviderType.digitalOcean:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
3
lib/logic/providers/dns_providers/desec.dart
Normal file
3
lib/logic/providers/dns_providers/desec.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||
|
||||
class DesecDnsProvider extends DnsProvider {}
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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<DnsProviderPicker> {
|
|||
),
|
||||
),
|
||||
);
|
||||
|
||||
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(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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) {
|
|
@ -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();
|
||||
|
|
|
@ -46,9 +46,8 @@ class RecoveryServerProviderConnected extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.filled(
|
||||
onPressed: formCubitState.isSubmitting
|
||||
? null
|
||||
: () => context.read<ServerProviderFormCubit>().trySubmit(),
|
||||
onPressed: () =>
|
||||
context.read<ServerProviderFormCubit>().trySubmit(),
|
||||
child: Text('basis.continue'.tr()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
Loading…
Reference in a new issue