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 CloudflareApi extends DnsProviderApi { CloudflareApi({ this.hasLogger = false, this.isWithToken = true, this.customToken, }); @override final bool hasLogger; @override final bool isWithToken; final String? customToken; @override RegExp getApiTokenValidation() => RegExp(r'\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); @override BaseOptions get options { final BaseOptions options = BaseOptions(baseUrl: rootAddress); if (isWithToken) { final String? token = getIt().dnsProviderKey; assert(token != null); options.headers = {'Authorization': 'Bearer $token'}; } if (customToken != null) { options.headers = {'Authorization': 'Bearer $customToken'}; } if (validateStatus != null) { options.validateStatus = validateStatus!; } return options; } @override String rootAddress = 'https://api.cloudflare.com/client/v4'; @override Future> isApiTokenValid(final String token) async { bool isValid = false; Response? response; String message = ''; final Dio client = await getClient(); try { response = await client.get( '/user/tokens/verify', options: Options( followRedirects: false, validateStatus: (final status) => status != null && (status >= 200 || status == 401), headers: {'Authorization': 'Bearer $token'}, ), ); } catch (e) { print(e); isValid = false; message = e.toString(); } finally { close(client); } if (response == null) { return GenericResult( data: isValid, success: false, message: message, ); } if (response.statusCode == HttpStatus.ok) { isValid = true; } else if (response.statusCode == HttpStatus.unauthorized) { isValid = false; } else { throw Exception('code: ${response.statusCode}'); } return GenericResult( data: isValid, success: true, message: response.statusMessage, ); } @override Future getZoneId(final String domain) async { String? zoneId; final Dio client = await getClient(); try { final Response response = await client.get( '/zones', queryParameters: {'name': domain}, ); zoneId = response.data['result'][0]['id']; } catch (e) { print(e); } finally { close(client); } return zoneId; } @override Future> removeSimilarRecords({ required final ServerDomain domain, final String? ip4, }) async { final String domainName = domain.domainName; final String domainZoneId = domain.zoneId; final String url = '/zones/$domainZoneId/dns_records'; final Dio client = await getClient(); try { final Response response = await client.get(url); final List records = response.data['result'] ?? []; final List allDeleteFutures = []; for (final record in records) { if (record['zone_name'] == domainName) { allDeleteFutures.add( client.delete('$url/${record["id"]}'), ); } } await Future.wait(allDeleteFutures); } catch (e) { print(e); return GenericResult( success: false, data: null, message: e.toString(), ); } finally { close(client); } return GenericResult(success: true, data: null); } @override Future> getDnsRecords({ required final ServerDomain domain, }) async { Response response; final String domainName = domain.domainName; final String domainZoneId = domain.zoneId; final List allRecords = []; final String url = '/zones/$domainZoneId/dns_records'; final Dio client = await getClient(); try { response = await client.get(url); final List records = response.data['result'] ?? []; for (final record in records) { if (record['zone_name'] == domainName) { allRecords.add( DnsRecord( name: record['name'], type: record['type'], content: record['content'], ttl: record['ttl'], proxied: record['proxied'], ), ); } } } catch (e) { print(e); } finally { close(client); } return allRecords; } @override List getDesiredDnsRecords({ final String? domainName, final String? ipAddress, final String? dkimPublicKey, }) { if (domainName == null || ipAddress == null) { return []; } return [ DesiredDnsRecord( name: domainName, content: ipAddress, description: 'record.root', ), DesiredDnsRecord( name: 'api.$domainName', content: ipAddress, description: 'record.api', ), DesiredDnsRecord( name: 'cloud.$domainName', content: ipAddress, description: 'record.cloud', ), DesiredDnsRecord( name: 'git.$domainName', content: ipAddress, description: 'record.git', ), DesiredDnsRecord( name: 'meet.$domainName', content: ipAddress, description: 'record.meet', ), DesiredDnsRecord( name: 'social.$domainName', content: ipAddress, description: 'record.social', ), DesiredDnsRecord( name: 'password.$domainName', content: ipAddress, description: 'record.password', ), DesiredDnsRecord( name: 'vpn.$domainName', content: ipAddress, description: 'record.vpn', ), DesiredDnsRecord( name: domainName, content: domainName, description: 'record.mx', type: 'MX', category: DnsRecordsCategory.email, ), DesiredDnsRecord( name: '_dmarc.$domainName', content: 'v=DMARC1; p=none', description: 'record.dmarc', type: 'TXT', category: DnsRecordsCategory.email, ), DesiredDnsRecord( name: domainName, content: 'v=spf1 a mx ip4:$ipAddress -all', description: 'record.spf', type: 'TXT', category: DnsRecordsCategory.email, ), if (dkimPublicKey != null) DesiredDnsRecord( name: 'selector._domainkey.$domainName', content: dkimPublicKey, description: 'record.dkim', type: 'TXT', category: DnsRecordsCategory.email, ), ]; } @override Future> createMultipleDnsRecords({ required final ServerDomain domain, final String? ip4, }) async { final String domainName = domain.domainName; final String domainZoneId = domain.zoneId; final List listDnsRecords = getProjectDnsRecords(domainName, ip4); final List allCreateFutures = []; final Dio client = await getClient(); try { for (final DnsRecord record in listDnsRecords) { allCreateFutures.add( client.post( '/zones/$domainZoneId/dns_records', data: record.toJson(), ), ); } await Future.wait(allCreateFutures); } 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); } @override Future 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> domainList() async { final String url = '$rootAddress/zones'; List domains = []; final Dio client = await getClient(); try { final Response response = await client.get( url, queryParameters: {'per_page': 50}, ); domains = response.data['result'] .map((final el) => el['name'] as String) .toList(); } catch (e) { print(e); } finally { close(client); } return domains; } }