// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
define(
[
"angular",
"lodash",
"cjt/util/locale",
"shared/js/email_deliverability/services/domainFactory",
"shared/js/email_deliverability/services/DKIMRecordProcessor",
"shared/js/email_deliverability/services/DMARCRecordProcessor",
"shared/js/email_deliverability/services/SPFRecordProcessor",
"shared/js/email_deliverability/services/PTRRecordProcessor",
"cjt/modules",
"cjt/services/alertService",
"cjt/modules",
"cjt/services/APICatcher",
],
function(angular, _, LOCALE, domainFactory, DKIMRecordProcessor, DMARCRecordProcessor, SPFRecordProcessor, PTRRecordProcessor) {
"use strict";
var MODULE_NAMESPACE = "shared.emailDeliverability.services.domains";
var SERVICE_NAME = "DomainsService";
var MODULE_REQUIREMENTS = [ "cjt2.services.apicatcher", "cjt2.services.alert" ];
var SERVICE_INJECTABLES = ["$q", "$log", "$interval", "alertService", "APIInitializer", "APICatcher", "DOMAIN_TYPE_CONSTANTS", "PAGE"];
// UAPI’s EmailAuth::validate_current_dkims returns the DKIM
// record name as “domain”. This function intends to strip
// the subdomain from that name to arrive at the domain name
// for which the record exists. For example,
// “newyork._domainkey.bob.com” becomes just “bob.com”.
function _STRIP_DKIM_SUBDOMAIN(d) {
return d.replace(/^.+\._domainkey\./, "");
}
var Zone = function(zoneName) {
this.zone = zoneName;
this.domains = [];
this.lockDomain = null;
};
Zone.prototype.addDomain = function addDomainToZone(domain) {
this.domains.push(domain);
};
// Returns a Domain object or null.
Zone.prototype.getLockDomain = function getLockDomain() {
return this.lockDomain;
};
// Requires a Domain object.
Zone.prototype.lock = function lockZone(domainObj) {
if (typeof domainObj !== "object") {
throw new Error("Give a domain object!");
}
if (this.lockDomain) {
throw new Error("Zone " + this.zone + " cannot lock for " + domainObj.domain + " because the zone is already locked for " + this.lockDomain + "!");
}
this.lockDomain = domainObj;
};
Zone.prototype.unlock = function unlockZone() {
this.lockDomain = null;
};
/**
*
* Service Factory to generate the Domains service
*
* @module DomainsService
* @memberof cpanel.emailDeliverability
*
* @param {ng.$q} $q
* @param {ng.$log} $log
* @param {Object} APICatcher base service
* @param {Object} DOMAIN_TYPE_CONSTANTS constants lookup of domain types
* @param {Object} PAGE window PAGE object created in template
* @returns {Domains} instance of the Domains service
*/
var SERVICE_FACTORY = function($q, $log, $interval, $alertService, $apiInit, APICatcher, DOMAIN_TYPE_CONSTANTS, PAGE) {
var _flattenedDomains;
var _domainLookupMap = {};
var _domainZoneMap = {};
var _zones = [];
var Domains = function() {};
Domains.prototype = Object.create(APICatcher);
/**
*
* Cache domains for ease of later lookup
*
* @private
*
* @param {Domain} domain to cache for later lookup
* @returns {Domain} stored domain
*/
Domains.prototype._cacheDomain = function _cacheDomain(domain) {
if (!_flattenedDomains) {
_flattenedDomains = [];
}
_domainLookupMap[domain.domain] = domain;
// Cache DKIM Key Domain too
_domainLookupMap["default._domainkey." + domain.domain] = domain;
// Store Domain in flattened domains
_flattenedDomains.push(domain);
return domain;
};
/**
*
* Remove a domain from the cache lookup
*
* @private
*
* @param {Domain} domain to remove from cache
* @returns {Boolean} success of domain removal
*/
Domains.prototype._uncacheDomain = function _uncacheDomain(domain) {
var self = this;
if (!_flattenedDomains) {
return false;
}
var domainObject = self._getDomainObject(domain);
for (var i = _flattenedDomains.length - 1; i >= 0; i--) {
if (_flattenedDomains[i].domain === domainObject.domain) {
_flattenedDomains.splice(i, 1);
return true;
}
}
return false;
};
/**
*
* Return a domain object. This is used to ensure you're always working with a Domain()
* even when a string might have been passed to a function.
*
* @public
*
* @param {string|Domain} wantedDomain - domain name or domain object
* @returns {Domain} matching wantedDomain
*/
Domains.prototype._getDomainObject = function _getDomainObject(wantedDomain) {
var self = this;
var domain = wantedDomain;
if (typeof domain === "string") {
domain = self.findDomainByName(domain);
}
if (domain instanceof domainFactory.getClass() !== true) {
$log.warn("Could not find domain “" + wantedDomain + "”");
}
return domain;
};
/**
*
* Find a domain using the domain name string
*
* @public
*
* @param {string} domainName - domain name to find
* @returns {Domain} matching domainName
*/
Domains.prototype.findDomainByName = function _findDomainByName(domainName) {
return _domainLookupMap[domainName];
};
/**
*
* Returns all currently loaded and stored domains
*
* @public
*
* @returns Array<Domain> returns a list of all loaded domains
*/
Domains.prototype.getAll = function getAllDomains() {
return _flattenedDomains;
};
/**
*
* API Wrapper to fetch the all domains (main, addon, subdomain, alias) and cache them
*
* @public
*
* @returns Promise<Array<Domain>>
*/
Domains.prototype.fetchAll = function fetchAllDomains() {
var self = this;
if (self.getAll()) {
return $q.resolve(self.getAll());
}
_flattenedDomains = [];
var domains = PAGE.domains || [];
domains.forEach(function(domain) {
if (domain.substring(0, 2) === "*.") {
return;
}
var domainObj = domainFactory.create({
domain: domain,
type: domain === PAGE.mainDomain ? DOMAIN_TYPE_CONSTANTS.MAIN : DOMAIN_TYPE_CONSTANTS.DOMAIN,
});
self._cacheDomain(domainObj);
});
return $q.resolve(self.getAll());
};
/**
*
* Extract Mail IPS from a validate_current_ptrs API Result
*
* @private
*
* @param {Object} results object from API with .data array
*/
Domains.prototype._extractMailIPs = function _extractMailIPs(results) {
var data = results.data;
data.forEach(function(ptrResult) {
var domain = this._getDomainObject(ptrResult.domain);
if (domain) {
domain.setMailIP(ptrResult.ip_version, ptrResult.ip_address);
}
}, this);
};
/**
* Find a domain zone by the domain name
*
* @param {String} zoneName name of the zone to find
* @returns {Zone}
*/
Domains.prototype.findZoneByName = function findZoneByName(zoneName) {
for (var i = 0; i < _zones.length; i++) {
if (_zones[i].zone === zoneName) {
return _zones[i];
}
}
return null;
};
/**
* Find domain zone by domain or domain name
*
* @param {Domain|String} domain
* @returns {Zone}
*/
Domains.prototype.findZoneByDomain = function findZoneByDomain(domain) {
var domainObj = this._getDomainObject(domain);
return _domainLookupMap[domainObj.domain];
};
/**
* Add a domain to an existing zone, or create it
*
* @private
*
* @param {Domain} domain
* @param {String} zone
*/
Domains.prototype._addDomainToZone = function _addDomainToZone(domain, zone) {
var zoneObj = this.findZoneByName(zone);
if (!zoneObj) {
zoneObj = new Zone(zone);
_zones.push(zoneObj);
}
domain.zone = zone;
zoneObj.addDomain(domain);
_domainZoneMap[domain.domain] = zoneObj;
};
/**
*
* Process the results of a has_ns_authority results item
*
* @private
*
* @param {Object} resultItem
* @returns {Domain} the updated Domain
*/
Domains.prototype._processNSAuthResultItem = function _processNSAuthResultItem(resultItem) {
var domainObj = this._getDomainObject(resultItem.domain);
if (domainObj) {
domainObj.soaError = resultItem.error || undefined;
domainObj.hasNSAuthority = resultItem.local_authority.toString() === "1";
domainObj.nameservers = resultItem.nameservers;
this._addDomainToZone(domainObj, resultItem.zone);
}
return domainObj;
};
/**
*
* Process a results item based on recordType and add records and suggested records to domains
*
* @private
*
* @param {Object} processor for handling recordType example services/DKIMRecordProcessor
* @param {Object} recordTypeResults api results for the specific api call {data:...}
* @param {string} recordType record type being processed
*/
Domains.prototype._processRecordTypeResults = function _processRecordTypeResults(processor, recordTypeResults, recordType) {
var normalizedResults = processor.normalizeResults(recordTypeResults);
if (recordType === "ptr") {
this._extractMailIPs(recordTypeResults);
}
var normalizedResultsData = normalizedResults.data;
var processedResults = processor.processResultItems(normalizedResultsData);
normalizedResultsData.forEach(function(normalizedResultItem, index) {
var domainObj = this._getDomainObject(normalizedResultItem.domain);
if (!domainObj) {
$log.debug("domain not found", normalizedResultItem);
return;
}
var domainRecords = processedResults[index];
var suggestedRecord = processor.generateSuggestedRecord(normalizedResultItem);
domainObj.setSuggestedRecord(recordType, suggestedRecord);
domainObj.setRecordDetails(recordType, normalizedResultItem.details || normalizedResultItem);
if (recordType === "spf") {
domainObj.setExpectedMatch(recordType, normalizedResultItem.expected);
}
domainRecords.forEach( domainObj.addRecord.bind(domainObj) );
}, this);
};
function _reportDKIMValidityCacheUpdates(recordTypeResults) {
var validityCacheUpdated = recordTypeResults.data.filter( function(item) {
return item.validity_cache_update === "set";
} );
if (validityCacheUpdated.length) {
var domains = validityCacheUpdated.map( function(item) {
return _.escape( _STRIP_DKIM_SUBDOMAIN(item.domain) );
} );
domains = _.sortBy( domains, function(d) {
return d.length;
} );
// “domains” shouldn’t be a long list. If it becomes
// so, we’ll need to chop this up so that it gives just
// a few domains and then says something like
// “… and N others”. That will avoid creating a big blob
// in what should normally be just an informative alert.
$alertService.add({
type: "info",
replace: false,
message: LOCALE.maketext("The system detected [quant,_1,domain,domains] whose [output,acronym,DKIM,DomainKeys Identified Mail] signatures were inactive despite valid [asis,DKIM] configuration. The system has automatically enabled [asis,DKIM] signatures for the following [numerate,_1,domain,domains]: [list_and_quoted,_2]", domains.length, domains),
});
}
}
/**
*
* Process the results of the fetchMailRecords API
*
* @private
*
* @param {Array} results batch api results [{data:...},{data:...}] where the first is the result of the has_ns_authority call, and the rest are recordType related
* @param {Array<String>} recordTypes record types requested during the fetchMailRecords API call
*/
Domains.prototype._processMailRecordResults = function _processMailRecordResults(results, recordTypes) {
var batchResultItems = results.data;
var nsAuthResult = batchResultItems.shift();
if (nsAuthResult.data) {
nsAuthResult.data.forEach(this._processNSAuthResultItem, this);
}
recordTypes = recordTypes ? recordTypes : this.getSupportedRecordTypes();
var processors = this.getRecordProcessors();
recordTypes.forEach(function(recordType, index) {
var recordTypeResults = batchResultItems[index];
var processor = processors[recordType];
this._processRecordTypeResults(processor, recordTypeResults, recordType);
if (recordType === "dkim") {
_reportDKIMValidityCacheUpdates(recordTypeResults);
}
}, this);
};
/**
*
* API Wrapper to fetch the status of all mail records for a set of domains
*
* @public
*
* @param {Array<Domain>} domains array of Domain() objects for which to fetch records
* @param {Array<String>} recordTypes array of record types for which to fetch records
* @returns {Promise<void>} returns the promise, but does not return an results object. That data is updated on the domain objects.
*/
Domains.prototype.fetchMailRecords = function fetchMailRecords(domains, recordTypes) {
var self = this;
if (domains.length === 0) {
return $q.resolve();
}
var start = new Date();
var startTime = start.getTime();
var apiCall = $apiInit.init("Batch", "strict");
var flatDomains = {};
domains.forEach(function(domain, index) {
return flatDomains["domain-" + index] = domain.domain;
});
var commands = [];
commands.push($apiInit.buildBatchCommandItem("DNS", "has_local_authority", flatDomains));
recordTypes = recordTypes ? recordTypes : this.getSupportedRecordTypes();
var processors = this.getRecordProcessors();
recordTypes.forEach(function(recordType) {
var processor = processors[recordType];
var apiName = processor.getValidateAPI();
commands.push($apiInit.buildBatchCommandItem(apiName.module, apiName.func, flatDomains));
}, this);
apiCall.addArgument("command", commands);
// If there’s an in-progress fetch already, then abort it
// because we know we don’t need its data.
if (this._fetchMailRecordsPromise) {
$log.debug("Canceling prior record status load");
this._fetchMailRecordsPromise.cancelCpCall();
}
this._fetchMailRecordsPromise = this.promise(apiCall);
return this._fetchMailRecordsPromise.then(function(result) {
var end = new Date();
var endTime = end.getTime();
$log.debug("Updating record statuses load took " + (endTime - startTime) + "ms for " + domains.length + " domains");
startTime = endTime;
self._processMailRecordResults(result, recordTypes);
}).finally(function() {
delete self._fetchMailRecordsPromise;
var end = new Date();
var endTime = end.getTime();
$log.debug("Updating record statuses parsing took " + (endTime - startTime) + "ms for " + domains.length + " domains");
});
};
/**
*
* Reset the records for a given domain, and poll for the records
*
* @public
*
* @param {Array<Domain>} domains array of domains for which to fetch records
* @param {Array<string>} recordTypes array of record types for which to fetch records
* @returns {Promise<void>} returns the promise, but does not return an results object. That data is updated on the domain objects.
*/
Domains.prototype.validateAllRecords = function validateAllRecords(domains, recordTypes) {
var self = this;
recordTypes = recordTypes ? recordTypes : self.getSupportedRecordTypes();
// Filter out domains already queued for a reload
domains = domains.filter(function(domain) {
if (domain.reloadingIn) {
return false;
}
return true;
});
domains.forEach(function(domain) {
domain.resetRecordLoaded();
}, self);
return self.fetchMailRecords(domains, recordTypes).then(function() {
domains.forEach(function(domain) {
domain.setRecordsLoaded(recordTypes);
}, self);
});
};
/**
*
* Returns a list of available processors for available record types. This is the key function for disabling record types.
*
* @public
*
* @returns {Object} returns object with keys representing record types, and value representing processor objects
*/
Domains.prototype.getRecordProcessors = function recordProcessors() {
var processors = {
"dkim": DKIMRecordProcessor,
"spf": SPFRecordProcessor,
"dmarc": DMARCRecordProcessor,
};
if ( PAGE.skipPTRLookups === undefined || !PAGE.skipPTRLookups ) {
processors.ptr = PTRRecordProcessor;
}
return processors;
};
/**
*
* Returns the currently supported record types.
*
* @public
*
* @returns {Array<strings>} array of supported record type strings
*/
Domains.prototype.getSupportedRecordTypes = function getSupportedRecordTypes() {
return Object.keys(this.getRecordProcessors());
};
/**
*
* Repair SPF records for a set of domains
*
* @public
*
* @param {Array<Domain>} domains array of domains to update records for
* @param {Array<string>} records new SPF record strings to update
* @returns <Promise<Object>> returns a promise and then an API results object
*/
Domains.prototype.repairSPF = function repairSPF(domains, records) {
var processors = this.getRecordProcessors();
var processor = processors["spf"];
var apiName = processor.getInstallAPI();
var flatDomains = domains.map(function(domain) {
return domain.domain;
});
var apiCall = $apiInit.init(apiName.module, apiName.func);
apiCall.addArgument("domain", flatDomains);
apiCall.addArgument("record", records);
return this.promise(apiCall);
};
/**
* Present a warning that the change may not be reflected
*
* @param {string} domain
*/
Domains.prototype.unreflectedChangeMessage = function unreflectedchangeMessage(domain) {
var zoneEditorURL = PAGE.zoneEditorUrl;
var message = "";
message += LOCALE.maketext("Because this is not an authoritative nameserver for the domain “[_1]”, the current or suggested records will not reflect your changes.", domain);
if (zoneEditorURL) {
message += " ";
message += LOCALE.maketext("Use the [output,url,_1,Zone Editor] to ensure that the system applied your changes.", zoneEditorURL + domain);
}
$alertService.add({
message: message,
type: "warning",
});
};
/**
*
* Repair DKIM records for a set of domains
*
* @public
*
* @param {Array<Domain>} domains array of domains to update records for
* @returns <Promise<Object>> returns a promise and then an API results object
*/
Domains.prototype.repairDKIM = function repairDKIM(domains) {
var processors = this.getRecordProcessors();
var processor = processors["dkim"];
var apiName = processor.getInstallAPI();
var flatDomains = domains.map(function(domain) {
return domain.domain;
});
var apiCall = $apiInit.init(apiName.module, apiName.func);
apiCall.addArgument("domain", flatDomains);
return this.promise(apiCall);
};
/**
*
* Repair DMARC records for a set of domains **current unsupported**
*
* @public
*
*/
Domains.prototype.repairDMARC = function repairDMARC(domain) {
throw new Error("Repairing DMARC Records are not currently supported in this interface.");
};
/**
*
* Repair PTR records for a set of domains **current unsupported**
*
* @public
*
*/
Domains.prototype.repairPTR = function repairPTR(domain) {
throw new Error("Installing PTR Records are not currently supported in this interface.");
};
/**
*
* Repair a record type for a single domain
*
* @public
*
* @param {Domain} domain domain to repair the record for
* @param {String} recordType record type to repair
* @param {String} newRecord new record, if setting a new record for the domain (SPF)
* @returns {Promise<Object>} returns a promise and then the API result object
*/
Domains.prototype.repairRecord = function repairRecord(domain, recordType, newRecord) {
if (recordType === "spf") {
return this.repairSPF([domain], [newRecord]);
} else if (recordType === "dkim") {
return this.repairDKIM([domain], [newRecord]);
} else if (recordType === "ptr") {
return this.repairPTR([domain], [newRecord]);
} else if (recordType === "dmarc") {
return this.repairDMARC([domain], [newRecord]);
}
};
/**
*
* Called on success of a record repair
*
* @private
*
* @param {Domain} domainObj domain updated during the repair call
* @param {String} recordType record type repaired during the repair call
* @param {String} record resulting updated record
*/
Domains.prototype._repairRecordSuccess = function _repairRecordSuccess(domainObj, recordType, record) {
$alertService.success({
replace: false,
message: LOCALE.maketext("The system updated the “[_1]” record for “[_2]” to the following: [_3]", recordType.toUpperCase(), _.escape(domainObj.domain), "<pre>" + _.escape(record) + "</pre>"),
});
};
/**
*
* Called on failure of a record repair
*
* @private
*
* @param {Domain} domainObj domain updated during the repair call
* @param {String} recordType record type repaired during the repair call
* @param {String} error
*/
Domains.prototype._repairRecordFailure = function _repairRecordSuccess(domainObj, recordType, error) {
$alertService.add({
type: "error",
replace: false,
message: LOCALE.maketext("The system failed to update the “[_1]” record for “[_2]” because of an error: [_3]", recordType.toUpperCase(), _.escape(domainObj.domain), _.escape(error)),
});
};
Domains.prototype._interval = function _interval(func, interval, count) {
return $interval(func, interval, count);
};
Domains.prototype._validateUntilSuccessComplete = function _validateUntilSuccessComplete(domainObj, successTypeRecords, waitAfter, startTime) {
var self = this;
$log.debug("[" + domainObj.domain + "] fetchMailRecords completed");
var recordTypes = self.getSupportedRecordTypes();
domainObj.setRecordsLoaded(recordTypes);
var someFailed = successTypeRecords.some(function(recordType) {
return !domainObj.isRecordValid(recordType);
});
var timePassed = (new Date().getTime() - startTime) / 1000;
$log.debug("[" + domainObj.domain + "] time passed since records set: " + timePassed);
// If not, double waitAfter and wait that long to check again
if (someFailed && timePassed < 120) {
$log.debug("[" + domainObj.domain + "] some failed, wait " + (waitAfter) + "s, and then try again.");
// Only bother alerting if it's been more than 5 seconds since the first call.
// A safety for the I/O issue found in COBRA-8775
if (timePassed > 5) {
$alertService.add({
type: "info",
replace: true,
message: LOCALE.maketext("The server records have not updated after [quant,_1,second,seconds]. The system will try again in [quant,_2,second,seconds].", Math.floor(timePassed), Math.floor(waitAfter)),
});
}
domainObj.resetRecordLoaded();
domainObj.reloadingIn = waitAfter;
return self._interval(function() {
domainObj.reloadingIn--;
}, 1000, waitAfter).then(function() {
domainObj.reloadingIn = 0;
waitAfter *= 2;
$log.debug("[" + domainObj.domain + "] done waiting, trying again.");
return self.validateUntilSuccess(domainObj, successTypeRecords, waitAfter, startTime);
});
} else {
if (!someFailed) {
$log.debug("[" + domainObj.domain + "] all records fixed.");
$alertService.success({
replace: true,
message: LOCALE.maketext("The system successfully updated the [asis,DNS] records."),
});
} else if (timePassed > 120) {
$log.debug("[" + domainObj.domain + "] more than 120s was taken to validate the change.");
$alertService.add({
type: "warning",
replace: true,
message: LOCALE.maketext("The system cannot verify that the record updated after 120 seconds."),
});
}
// If records are valid, we're done
domainObj.setRecordsLoaded(self.getSupportedRecordTypes());
}
};
Domains.prototype.validateUntilSuccess = function validateUntilSuccess(domain, successTypeRecords, waitAfter, startTime) {
var self = this;
var domainObj = this._getDomainObject(domain);
var recordTypes = self.getSupportedRecordTypes();
$log.debug("[" + domain.domain + "] begin validateUntilSuccess @" + (waitAfter) + "s");
return self.fetchMailRecords([domainObj], recordTypes).then(function() {
return self._validateUntilSuccessComplete(domainObj, successTypeRecords, waitAfter, startTime);
});
};
Domains.prototype.getDomainZoneObject = function getDomainZoneObject(domain) {
var domainObj = this._getDomainObject(domain);
return this.findZoneByName(domainObj.zone) || false;
};
Domains.prototype._repairDomainComplete = function _repairDomainComplete(result, domain, recordTypes, records, skipValidation) {
$log.debug("[" + domain.domain + "] beginning _repairDomainComplete.");
var self = this;
if (result.data) {
var recordsThatSucceeded = [];
result.data.forEach(function _processRecordResultObj(recordResultObj, index) {
var recordType = recordTypes[index];
var record = records[index];
var domainResultObj = recordResultObj.data[0];
$log.debug("[" + domainResultObj.domain + "] being parsed in _repairDomainComplete status: [" + domainResultObj.status + "].");
if (domainResultObj.status.toString() !== "1") {
this._repairRecordFailure(domain, recordType, domainResultObj.msg);
} else {
recordsThatSucceeded.push(recordType);
this._repairRecordSuccess(domain, recordType, record);
}
}, this);
if (skipValidation) {
$log.debug("[" + domain.domain + "] does not have NS Authority. Do not continue validation check");
return;
}
if (recordsThatSucceeded.length) {
$log.debug("[" + domain.domain + "] some records succeeeded.", recordsThatSucceeded.join(","));
var startTime = new Date().getTime();
return self.validateUntilSuccess(domain, recordsThatSucceeded, 5, startTime);
} else {
return self.validateAllRecords([domain]);
}
}
};
Domains.prototype.repairDomain = function repairDomain(domain, recordTypes, records, skipValidation) {
recordTypes = recordTypes.slice();
records = records.slice();
var self = this;
var domainObj = this._getDomainObject(domain);
// Lock the Zone
var zoneObj = this.findZoneByName(domainObj.zone);
zoneObj.lock(domainObj);
$log.debug("[" + domainObj.domain + "] locking zone: ", zoneObj.zone);
$log.debug("[" + domainObj.domain + "] beginning repairDomain.");
// Only reset it if we have auth, because otherwise it won't come back
if (!skipValidation) {
domainObj.resetRecordLoaded();
}
$log.debug("[" + domainObj.domain + "] resetting loaded records.");
// Start the Repair - Batch is serial, so this is fine to do
var apiCall = $apiInit.init("Batch", "strict");
var processors = self.getRecordProcessors();
var commands = recordTypes.map(function(recordType, index) {
var record = records[index];
var processor = processors[recordType];
var apiName = processor.getInstallAPI();
$log.debug("[" + domainObj.domain + "] adding batch command for “" + recordType + "”.");
if ( window.mixpanel ) {
window.mixpanel.track("Email-Deliverability-Repair-Record", { type: recordType });
}
if (recordType === "dmarc") {
return $apiInit.buildBatchCommandItem(apiName.module, apiName.func, { "domain": domainObj.domain, policy: record });
}
return $apiInit.buildBatchCommandItem(apiName.module, apiName.func, { "domain": domainObj.domain, record: record });
});
apiCall.addArgument("command", commands);
return self.promise(apiCall).then(function(result) {
$log.debug("[" + domainObj.domain + "] batch command completed.");
zoneObj.unlock();
return self._repairDomainComplete(result, domainObj, recordTypes, records, skipValidation);
});
};
/**
*
* Process the API result for fetchPrivateDKIMKey
*
* @private
*
* @param {Object} result API results object {data:...}
* @returns first result of API results.data array
*/
Domains.prototype._processGetPrivateDKIMKey = function _processGetPrivateDKIMKey(result) {
return result.data.pop();
};
/**
*
* Get the private DKIM key for a domain
*
* @public
*
* @param {Domain} domain domain to request the DKIM key for
* @returns {Promise<Object>} DKIM Key object {pem:...,domain:...}
*/
Domains.prototype.fetchPrivateDKIMKey = function fetchPrivateDKIMKey(domain) {
var domainObj = this._getDomainObject(domain);
var apiCall = $apiInit.init("EmailAuth", "fetch_dkim_private_keys");
apiCall.addArgument("domain", domainObj.domain);
return this.promise(apiCall).then(this._processGetPrivateDKIMKey);
};
/**
*
* Get the top most current record for a domain and record type
*
* @public
*
* @param {Domain} domain domain to get the record from
* @param {String} recordType record type to fetch the record for
* @returns {String} returns the string record or an empty string
*/
Domains.prototype.getCurrentRecord = function getCurrentRecord(domain, recordType) {
var domainObj = this._getDomainObject(domain);
return domainObj.getCurrentRecord(recordType);
};
Domains.prototype.getNoAuthorityMessage = function getNoAuthorityMessage(domainObj, recordType) {
var nameserversHtml = domainObj.nameservers.map( _.escape );
var message;
if (domainObj.nameservers.length) {
message = LOCALE.maketext("This system does not control [asis,DNS] for the “[_1]” domain.", domainObj.domain);
} else {
message = LOCALE.maketext("This system does not control [asis,DNS] for the “[_1]” domain and the system did not find any authoritative nameservers for this domain.", domainObj.domain);
}
if (recordType === "spf" || recordType === "dkim") {
// Handle differently for spf
message += " ";
message += "<strong>";
message += LOCALE.maketext("You can install the suggested “[_1]” record locally. However, this server is not the authoritative nameserver. If you install this record, this change will not be effective.", recordType.toUpperCase());
message += "</strong>";
}
message += " ";
if (domainObj.nameservers.length) {
message += LOCALE.maketext("Contact the person responsible for the [list_and_quoted,_3] [numerate,_2,nameserver,nameservers] and request that they update the “[_1]” record with the following:", recordType.toUpperCase(), domainObj.nameservers.length, nameserversHtml);
} else {
message += LOCALE.maketext("Contact your domain registrar to verify this domain’s registration.");
}
return message;
};
/**
*
* API Wrapper to obtain the helo record for a domain
*
* @param {Domain} domain domain to request the helo record for
* @returns {Promise} get_mail_helo_ip promise
*/
Domains.prototype.fetchMailHeloIP = function fetchMailHeloIP(domain) {
var domainObj = this._getDomainObject(domain);
var apiCall = $apiInit.init("EmailAuth", "get_mail_helo_ip");
apiCall.addArgument("domain", domainObj.domain);
return this.promise(apiCall).then(this._processMailHeloResult);
};
Domains.prototype.localDKIMExists = function localDKIMExists(domain) {
var recordType = "dkim";
if (domain.isRecordValid(recordType)) {
return true;
}
var record = domain.getSuggestedRecord(recordType);
if (record.value) {
return true;
}
return false;
};
Domains.prototype.ensureLocalDKIMKeyExists = function ensureLocalDKIMKeyExists(domain) {
var self = this;
var apiCall = $apiInit.init("EmailAuth", "ensure_dkim_keys_exist");
apiCall.addArgument("domain", domain.domain);
return self.promise(apiCall).then(function(results) {
var data = results.data;
var domainResult = data.pop();
if (domainResult.status.toString() === "1") {
return self.fetchMailRecords([domain]);
} else {
return $alertService.add({
type: "error",
message: _.escape(domainResult.msg),
});
}
});
};
// To be called whenever a view changes so that pending
// “load” API calls can be canceled or abandoned.
Domains.prototype.markViewLoad = function() {
if (this._fetchMailRecordsPromise) {
this._fetchMailRecordsPromise.cancelCpCall();
}
};
return new Domains();
};
SERVICE_INJECTABLES.push(SERVICE_FACTORY);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.value("PAGE", PAGE);
app.value("DOMAIN_TYPE_CONSTANTS", domainFactory.getTypeConstants());
app.factory(SERVICE_NAME, SERVICE_INJECTABLES);
return {
"class": SERVICE_FACTORY,
"namespace": MODULE_NAMESPACE,
};
}
);