mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-07 00:24:18 +00:00
feat: Implement infrastructure for new DNS provider deSEC
This commit is contained in:
parent
e180c23cb7
commit
234064ed72
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 |
|
@ -93,6 +93,9 @@ class BNames {
|
||||||
/// A String field of [serverInstallationBox] box.
|
/// A String field of [serverInstallationBox] box.
|
||||||
static String serverProvider = 'serverProvider';
|
static String serverProvider = 'serverProvider';
|
||||||
|
|
||||||
|
/// A String field of [serverInstallationBox] box.
|
||||||
|
static String dnsProvider = 'dnsProvider';
|
||||||
|
|
||||||
/// A String field of [serverLocation] box.
|
/// A String field of [serverLocation] box.
|
||||||
static String serverLocation = 'serverLocation';
|
static String serverLocation = 'serverLocation';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart';
|
||||||
|
@ -30,6 +31,8 @@ class ApiFactoryCreator {
|
||||||
final DnsProviderApiFactorySettings settings,
|
final DnsProviderApiFactorySettings settings,
|
||||||
) {
|
) {
|
||||||
switch (settings.provider) {
|
switch (settings.provider) {
|
||||||
|
case DnsProvider.desec:
|
||||||
|
return DesecApiFactory();
|
||||||
case DnsProvider.cloudflare:
|
case DnsProvider.cloudflare:
|
||||||
return CloudflareApiFactory();
|
return CloudflareApiFactory();
|
||||||
case DnsProvider.unknown:
|
case DnsProvider.unknown:
|
||||||
|
|
287
lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart
Normal file
287
lib/logic/api_maps/rest_maps/dns_providers/desec/desec.dart
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
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>().cloudFlareKey;
|
||||||
|
assert(token != null);
|
||||||
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customToken != null) {
|
||||||
|
options.headers = {'Authorization': 'Bearer $customToken'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateStatus != null) {
|
||||||
|
options.validateStatus = validateStatus!;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String rootAddress = 'https://desec.io/api/v1/domains/';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APIGenericResult<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'},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
isValid = false;
|
||||||
|
message = e.toString();
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return APIGenericResult(
|
||||||
|
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 APIGenericResult(
|
||||||
|
data: isValid,
|
||||||
|
success: true,
|
||||||
|
message: response.statusMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getZoneId(final String domain) async => domain;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APIGenericResult<void>> removeSimilarRecords({
|
||||||
|
required final ServerDomain domain,
|
||||||
|
final String? ip4,
|
||||||
|
}) async {
|
||||||
|
final String domainName = domain.domainName;
|
||||||
|
final String url = '/$domainName/rrsets/';
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get(url);
|
||||||
|
|
||||||
|
final List records = response.data['result'] ?? [];
|
||||||
|
await client.put(url, data: records);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return APIGenericResult(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
message: e.toString(),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return APIGenericResult(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);
|
||||||
|
final List records = response.data['result'] ?? [];
|
||||||
|
|
||||||
|
for (final record in records) {
|
||||||
|
allRecords.add(
|
||||||
|
DnsRecord(
|
||||||
|
name: record['subname'],
|
||||||
|
type: record['type'],
|
||||||
|
content: record['records'],
|
||||||
|
ttl: record['ttl'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APIGenericResult<void>> createMultipleDnsRecords({
|
||||||
|
required final ServerDomain domain,
|
||||||
|
final String? ip4,
|
||||||
|
}) async {
|
||||||
|
final String domainName = domain.domainName;
|
||||||
|
final List<DnsRecord> listDnsRecords = projectDnsRecords(domainName, ip4);
|
||||||
|
final List<Future> allCreateFutures = <Future>[];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
for (final DnsRecord record in listDnsRecords) {
|
||||||
|
allCreateFutures.add(
|
||||||
|
client.post(
|
||||||
|
'/$domainName/rrsets/',
|
||||||
|
data: record.toJson(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Future.wait(allCreateFutures);
|
||||||
|
} on DioError catch (e) {
|
||||||
|
print(e.message);
|
||||||
|
rethrow;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return APIGenericResult(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
message: e.toString(),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return APIGenericResult(success: true, data: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DnsRecord> projectDnsRecords(
|
||||||
|
final String? domainName,
|
||||||
|
final String? ip4,
|
||||||
|
) {
|
||||||
|
final DnsRecord domainA =
|
||||||
|
DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||||
|
|
||||||
|
final DnsRecord mx = DnsRecord(type: 'MX', name: '@', content: 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: domainName,
|
||||||
|
content: 'v=spf1 a mx ip4:$ip4 -all',
|
||||||
|
ttl: 18000,
|
||||||
|
);
|
||||||
|
|
||||||
|
return <DnsRecord>[
|
||||||
|
domainA,
|
||||||
|
apiA,
|
||||||
|
cloudA,
|
||||||
|
gitA,
|
||||||
|
meetA,
|
||||||
|
passwordA,
|
||||||
|
socialA,
|
||||||
|
mx,
|
||||||
|
txt1,
|
||||||
|
txt2,
|
||||||
|
vpn
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setDnsRecord(
|
||||||
|
final DnsRecord record,
|
||||||
|
final ServerDomain domain,
|
||||||
|
) async {
|
||||||
|
final String domainZoneId = domain.zoneId;
|
||||||
|
final String url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
await client.post(
|
||||||
|
url,
|
||||||
|
data: record.toJson(),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<String>> domainList() async {
|
||||||
|
final String url = '$rootAddress/zones';
|
||||||
|
List<String> domains = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get(
|
||||||
|
url,
|
||||||
|
queryParameters: {'per_page': 50},
|
||||||
|
);
|
||||||
|
domains = response.data['result']
|
||||||
|
.map<String>((final el) => el['name'] as String)
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
|
@ -66,6 +66,15 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDnsProviderType(final DnsProvider providerType) async {
|
||||||
|
await repository.saveDnsProviderType(providerType);
|
||||||
|
ApiController.initDnsProviderApiFactory(
|
||||||
|
DnsProviderApiFactorySettings(
|
||||||
|
provider: providerType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ProviderApiTokenValidation serverProviderApiTokenValidation() =>
|
ProviderApiTokenValidation serverProviderApiTokenValidation() =>
|
||||||
ApiController.currentServerProviderApiFactory!
|
ApiController.currentServerProviderApiFactory!
|
||||||
.getServerProvider()
|
.getServerProvider()
|
||||||
|
|
|
@ -706,6 +706,10 @@ class ServerInstallationRepository {
|
||||||
getIt<ApiConfigModel>().init();
|
getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> saveDnsProviderType(final DnsProvider type) async {
|
||||||
|
await getIt<ApiConfigModel>().storeDnsProviderType(type);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> saveBackblazeKey(
|
Future<void> saveBackblazeKey(
|
||||||
final BackblazeCredential backblazeCredential,
|
final BackblazeCredential backblazeCredential,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
@ -14,6 +14,8 @@ class ApiConfigModel {
|
||||||
String? get serverType => _serverType;
|
String? get serverType => _serverType;
|
||||||
String? get cloudFlareKey => _cloudFlareKey;
|
String? get cloudFlareKey => _cloudFlareKey;
|
||||||
ServerProvider? get serverProvider => _serverProvider;
|
ServerProvider? get serverProvider => _serverProvider;
|
||||||
|
DnsProvider? get dnsProvider => _dnsProvider;
|
||||||
|
|
||||||
BackblazeCredential? get backblazeCredential => _backblazeCredential;
|
BackblazeCredential? get backblazeCredential => _backblazeCredential;
|
||||||
ServerDomain? get serverDomain => _serverDomain;
|
ServerDomain? get serverDomain => _serverDomain;
|
||||||
BackblazeBucket? get backblazeBucket => _backblazeBucket;
|
BackblazeBucket? get backblazeBucket => _backblazeBucket;
|
||||||
|
@ -23,6 +25,7 @@ class ApiConfigModel {
|
||||||
String? _cloudFlareKey;
|
String? _cloudFlareKey;
|
||||||
String? _serverType;
|
String? _serverType;
|
||||||
ServerProvider? _serverProvider;
|
ServerProvider? _serverProvider;
|
||||||
|
DnsProvider? _dnsProvider;
|
||||||
ServerHostingDetails? _serverDetails;
|
ServerHostingDetails? _serverDetails;
|
||||||
BackblazeCredential? _backblazeCredential;
|
BackblazeCredential? _backblazeCredential;
|
||||||
ServerDomain? _serverDomain;
|
ServerDomain? _serverDomain;
|
||||||
|
@ -33,6 +36,11 @@ class ApiConfigModel {
|
||||||
_serverProvider = value;
|
_serverProvider = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> storeDnsProviderType(final DnsProvider value) async {
|
||||||
|
await _box.put(BNames.dnsProvider, value);
|
||||||
|
_dnsProvider = value;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> storeServerProviderKey(final String value) async {
|
Future<void> storeServerProviderKey(final String value) async {
|
||||||
await _box.put(BNames.hetznerKey, value);
|
await _box.put(BNames.hetznerKey, value);
|
||||||
_serverProviderKey = value;
|
_serverProviderKey = value;
|
||||||
|
@ -75,6 +83,7 @@ class ApiConfigModel {
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_serverProviderKey = null;
|
_serverProviderKey = null;
|
||||||
|
_dnsProvider = null;
|
||||||
_serverLocation = null;
|
_serverLocation = null;
|
||||||
_cloudFlareKey = null;
|
_cloudFlareKey = null;
|
||||||
_backblazeCredential = null;
|
_backblazeCredential = null;
|
||||||
|
@ -95,5 +104,6 @@ class ApiConfigModel {
|
||||||
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
||||||
_serverType = _box.get(BNames.serverTypeIdentifier);
|
_serverType = _box.get(BNames.serverTypeIdentifier);
|
||||||
_serverProvider = _box.get(BNames.serverProvider);
|
_serverProvider = _box.get(BNames.serverProvider);
|
||||||
|
_dnsProvider = _box.get(BNames.dnsProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,4 +29,6 @@ enum DnsProvider {
|
||||||
unknown,
|
unknown,
|
||||||
@HiveField(1)
|
@HiveField(1)
|
||||||
cloudflare,
|
cloudflare,
|
||||||
|
@HiveField(2)
|
||||||
|
desec
|
||||||
}
|
}
|
||||||
|
|
307
lib/ui/pages/setup/initializing/dns_provider_picker.dart
Normal file
307
lib/ui/pages/setup/initializing/dns_provider_picker.dart
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
|
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/launch_url.dart';
|
||||||
|
|
||||||
|
class DnsProviderPicker extends StatefulWidget {
|
||||||
|
const DnsProviderPicker({
|
||||||
|
required this.formCubit,
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DnsProviderFormCubit formCubit;
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DnsProviderPicker> createState() => _DnsProviderPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DnsProviderPickerState extends State<DnsProviderPicker> {
|
||||||
|
DnsProvider selectedProvider = DnsProvider.unknown;
|
||||||
|
|
||||||
|
void setProvider(final DnsProvider provider) {
|
||||||
|
setState(() {
|
||||||
|
selectedProvider = provider;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
switch (selectedProvider) {
|
||||||
|
case DnsProvider.unknown:
|
||||||
|
return ProviderSelectionPage(
|
||||||
|
serverInstallationCubit: widget.serverInstallationCubit,
|
||||||
|
callback: setProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
case DnsProvider.cloudflare:
|
||||||
|
return ProviderInputDataPage(
|
||||||
|
providerCubit: widget.formCubit,
|
||||||
|
providerInfo: ProviderPageInfo(
|
||||||
|
providerType: DnsProvider.cloudflare,
|
||||||
|
pathToHow: 'how_cloudflare',
|
||||||
|
image: Image.asset(
|
||||||
|
'assets/images/logos/cloudflare.png',
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
case DnsProvider.desec:
|
||||||
|
return ProviderInputDataPage(
|
||||||
|
providerCubit: widget.formCubit,
|
||||||
|
providerInfo: ProviderPageInfo(
|
||||||
|
providerType: DnsProvider.desec,
|
||||||
|
pathToHow: 'how_digital_ocean_dns',
|
||||||
|
image: Image.asset(
|
||||||
|
'assets/images/logos/digital_ocean.png',
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderPageInfo {
|
||||||
|
const ProviderPageInfo({
|
||||||
|
required this.providerType,
|
||||||
|
required this.pathToHow,
|
||||||
|
required this.image,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String pathToHow;
|
||||||
|
final Image image;
|
||||||
|
final DnsProvider providerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderInputDataPage extends StatelessWidget {
|
||||||
|
const ProviderInputDataPage({
|
||||||
|
required this.providerInfo,
|
||||||
|
required this.providerCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ProviderPageInfo providerInfo;
|
||||||
|
final DnsProviderFormCubit providerCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'initializing.connect_to_dns'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'initializing.connect_to_server_provider_text'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
CubitFormTextField(
|
||||||
|
formFieldCubit: providerCubit.apiKey,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Provider API Token',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
BrandButton.rised(
|
||||||
|
text: 'basis.connect'.tr(),
|
||||||
|
onPressed: () => providerCubit.trySubmit(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
BrandOutlinedButton(
|
||||||
|
child: Text('initializing.how'.tr()),
|
||||||
|
onPressed: () => showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (final BuildContext context) => Padding(
|
||||||
|
padding: paddingH15V0,
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
children: [
|
||||||
|
BrandMarkdown(
|
||||||
|
fileName: providerInfo.pathToHow,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderSelectionPage extends StatelessWidget {
|
||||||
|
const ProviderSelectionPage({
|
||||||
|
required this.callback,
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Function callback;
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'initializing.select_dns'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'initializing.select_provider'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
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, 241, 215, 166),
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/images/logos/cloudflare.svg',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Text(
|
||||||
|
'Hetzner Cloud',
|
||||||
|
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_cloudflare'
|
||||||
|
.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
BrandButton.rised(
|
||||||
|
text: 'basis.select'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
serverInstallationCubit
|
||||||
|
.setDnsProviderType(DnsProvider.cloudflare);
|
||||||
|
callback(DnsProvider.cloudflare);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Outlined button that will open website
|
||||||
|
BrandOutlinedButton(
|
||||||
|
onPressed: () =>
|
||||||
|
launchURL('https://dash.cloudflare.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(DnsProvider.desec);
|
||||||
|
callback(DnsProvider.desec);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Outlined button that will open website
|
||||||
|
BrandOutlinedButton(
|
||||||
|
onPressed: () => launchURL('https://desec.io/'),
|
||||||
|
title: 'initializing.select_provider_site_button'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import 'package:selfprivacy/ui/components/drawers/progress_drawer.dart';
|
||||||
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
||||||
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
|
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
|
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/setup/initializing/dns_provider_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
||||||
|
@ -39,7 +40,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
actualInitializingPage = [
|
actualInitializingPage = [
|
||||||
() => _stepServerProviderToken(cubit),
|
() => _stepServerProviderToken(cubit),
|
||||||
() => _stepServerType(cubit),
|
() => _stepServerType(cubit),
|
||||||
() => _stepCloudflare(cubit),
|
() => _stepDnsProviderToken(cubit),
|
||||||
() => _stepBackblaze(cubit),
|
() => _stepBackblaze(cubit),
|
||||||
() => _stepDomain(cubit),
|
() => _stepDomain(cubit),
|
||||||
() => _stepUser(cubit),
|
() => _stepUser(cubit),
|
||||||
|
@ -238,56 +239,19 @@ class InitializingPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _stepCloudflare(final ServerInstallationCubit initializingCubit) =>
|
Widget _stepDnsProviderToken(
|
||||||
|
final ServerInstallationCubit initializingCubit,
|
||||||
|
) =>
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final context) => DnsProviderFormCubit(initializingCubit),
|
create: (final context) => DnsProviderFormCubit(initializingCubit),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (final context) => ResponsiveLayoutWithInfobox(
|
builder: (final context) {
|
||||||
topChild: Column(
|
final providerCubit = context.watch<DnsProviderFormCubit>();
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
return DnsProviderPicker(
|
||||||
children: [
|
formCubit: providerCubit,
|
||||||
Text(
|
serverInstallationCubit: initializingCubit,
|
||||||
'${'initializing.connect_to_server_provider'.tr()}Cloudflare',
|
);
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
},
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'initializing.manage_domain_dns'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
primaryColumn: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
CubitFormTextField(
|
|
||||||
formFieldCubit: context.read<DnsProviderFormCubit>().apiKey,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'initializing.cloudflare_api_token'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
BrandButton.filled(
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<DnsProviderFormCubit>().trySubmit(),
|
|
||||||
text: 'basis.connect'.tr(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandOutlinedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<SupportSystemCubit>().showArticle(
|
|
||||||
article: 'how_cloudflare',
|
|
||||||
context: context,
|
|
||||||
);
|
|
||||||
Scaffold.of(context).openEndDrawer();
|
|
||||||
},
|
|
||||||
title: 'initializing.how'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue