mirror of
https://github.com/tiennm99/is-a-dev.git
synced 2026-09-03 18:16:53 +00:00
Merge pull request #1093 from is-a-dev/feat/mx-records
feat: adds support for MX and TXT records
This commit is contained in:
+27
-5
@@ -21,7 +21,7 @@ In the owner object, the fields `username` and `email` are required. You can how
|
||||
"owner": {
|
||||
"username": "github-username",
|
||||
"email": "any@email"
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -32,7 +32,7 @@ If you don't wish to share your email address here, please share your twitter or
|
||||
"username": "github-username",
|
||||
"email": "",
|
||||
"twitter": "twitter-handle"
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -46,14 +46,14 @@ This is a link to your website repository or your github account. This is purely
|
||||
|
||||
|
||||
### record (required)
|
||||
This is where you specify how you want to link to your server/webpage.
|
||||
This is where you specify the DNS records you wish to use.
|
||||
|
||||
Currently, only `CNAME`, `A`, `URL` record types are supported.
|
||||
The supported record types are: `CNAME`, `A`, `URL`, `MX` and `TXT`
|
||||
|
||||
Here's a few different use cases for the given record types -
|
||||
|
||||
* **CNAME**
|
||||
CNAME must be a host name (Eg - `something.tld`)
|
||||
CNAME must be a host name (Eg - `something.tld`). CNAME cannot be used in conjunction with any other record types.
|
||||
```json
|
||||
{
|
||||
"record": {
|
||||
@@ -86,3 +86,25 @@ A record must be a list of ips
|
||||
}
|
||||
```
|
||||
|
||||
* **MX**
|
||||
MX must be a list of host names
|
||||
```json
|
||||
{
|
||||
"record": {
|
||||
"MX": [
|
||||
"mx1.improvmx.com",
|
||||
"mx2.improvmx.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **TXT**
|
||||
TXT can be any string value
|
||||
```json
|
||||
{
|
||||
"record": {
|
||||
"TXT": "hello world"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"description": "Akshay's email alias",
|
||||
"repo": "https://github.com/phenax/phenax.github.io",
|
||||
"owner": {
|
||||
"username": "phenax",
|
||||
"email": "phenax5@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"TXT": "v=spf1 include:spf.improvmx.com ~all",
|
||||
"MX": [
|
||||
"mx1.improvmx.com",
|
||||
"mx2.improvmx.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -8,4 +8,4 @@
|
||||
"record": {
|
||||
"CNAME": "phenax.github.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,6 +6,6 @@
|
||||
"email": "dipanroy@mindwebs.org"
|
||||
},
|
||||
"record": {
|
||||
"A": "115.187.62.14"
|
||||
"A": ["115.187.62.14"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,25 @@ const { domainService: dc } = require('../utils/domain-service');
|
||||
const { getDomains: gd } = require('../utils/get-domain');
|
||||
|
||||
// Allow TXT records while publishing (for pcl validation)
|
||||
const getRecords = R.compose(R.toPairs, R.pick(VALID_RECORD_TYPES.concat(['TXT'])));
|
||||
const getRecords = R.compose(R.toPairs, R.pick(VALID_RECORD_TYPES));
|
||||
|
||||
const toHostList = R.chain(data => {
|
||||
const rs = getRecords(data.record);
|
||||
|
||||
return R.chain(([recordType, urls]) =>
|
||||
(Array.isArray(urls) ? urls : [urls]).map(url => ({
|
||||
(Array.isArray(urls) ? urls : [urls]).map((url, index) => ({
|
||||
name: data.name,
|
||||
type: recordType,
|
||||
address: (recordType === 'CNAME' ? `${url}`.toLowerCase() : `${url}`).replace(/\/$/g, ''),
|
||||
ttl: TTL,
|
||||
...(recordType === 'MX' ? { priority: index + 20 } : {})
|
||||
}))
|
||||
, rs);
|
||||
, rs);
|
||||
});
|
||||
|
||||
const registerDomains = async ({ domainService, getDomains, log = () => {} }) => {
|
||||
const registerDomains = async ({ domainService, getDomains, log = () => { } }) => {
|
||||
const domains = await getDomains().then(toHostList);
|
||||
|
||||
|
||||
if (domains.length === 0)
|
||||
return Promise.reject(new Error('Nothing to register'));
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const R = require('ramda');
|
||||
const { getDomainService, diffRecords } = require('../utils/domain-service');
|
||||
const {DOMAIN_DOMAIN} = require('../utils/constants');
|
||||
const { DOMAIN_DOMAIN } = require('../utils/constants');
|
||||
|
||||
const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir } = {}) => ({
|
||||
const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir, addEmail, removeEmail } = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
@@ -13,6 +13,10 @@ const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir } =
|
||||
add: (rec) => addRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
email: {
|
||||
add: (rec) => addEmail(rec),
|
||||
remove: (rec) => removeEmail(rec),
|
||||
},
|
||||
});
|
||||
|
||||
describe('diffRecords', () => {
|
||||
@@ -114,23 +118,33 @@ describe('Domain service', () => {
|
||||
const removeZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const removeRedir = jest.fn(async () => ({}));
|
||||
const addEmail = jest.fn(async () => ({}));
|
||||
const removeEmail = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
}) });
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({
|
||||
cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addEmail,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
removeEmail,
|
||||
})
|
||||
});
|
||||
|
||||
const getRecordCalls = recfn => recfn.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address', 'redirect', 'domain', 'line']));
|
||||
const getRecordCalls = recfn => recfn.mock.calls
|
||||
.map(R.head)
|
||||
.map(R.pick(['name', 'type', 'address', 'redirect', 'domain', 'line', 'priority', 'exchanger']));
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
removeZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
removeRedir.mockClear();
|
||||
addEmail.mockClear();
|
||||
removeEmail.mockClear();
|
||||
});
|
||||
|
||||
describe('getHosts', () => {
|
||||
@@ -187,13 +201,21 @@ describe('Domain service', () => {
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
{ name: 'c', type: 'MX', address: 'foobar.com', priority: 2 },
|
||||
]);
|
||||
|
||||
expect(addZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
]);
|
||||
|
||||
expect(addEmail).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(addEmail)).toEqual([
|
||||
{ domain: 'c.is-a.dev', exchanger: 'foobar.com', priority: 2 },
|
||||
]);
|
||||
|
||||
expect(removeZone).toBeCalledTimes(0);
|
||||
expect(removeEmail).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
@@ -252,6 +274,9 @@ describe('Domain service', () => {
|
||||
{ line: 2, name: 'b', type: 'A', address: '1' },
|
||||
{ line: 3, name: 'b', type: 'A', address: '2' },
|
||||
{ line: 4, name: 'c', type: 'CNAME', address: 'hello.com' },
|
||||
{ line: 5, name: 'c', type: 'MX', address: 'mx1.hello.com', priority: 20 },
|
||||
{ line: 6, name: 'c', type: 'MX', address: 'mx2.hello.com', priority: 21 },
|
||||
{ line: 7, name: 'b', type: 'MX', address: 'foo.bar', priority: 20 },
|
||||
];
|
||||
const redirections = [
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, destination: 'https://foobar.com' },
|
||||
@@ -271,6 +296,8 @@ describe('Domain service', () => {
|
||||
{ name: 'd', type: 'CNAME', address: 'helo.com' },
|
||||
{ name: 'd', type: 'URL', address: 'https://hhh.com' },
|
||||
{ name: 'x', type: 'URL', address: 'https://example69.com' },
|
||||
{ name: 'c', type: 'MX', address: 'mx2.hello.com', priority: 21 },
|
||||
{ name: 'a', type: 'MX', address: 'example.com', priority: 20 },
|
||||
]);
|
||||
|
||||
expect(addZone).toBeCalledTimes(3);
|
||||
@@ -283,6 +310,17 @@ describe('Domain service', () => {
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 1 },
|
||||
]);
|
||||
|
||||
expect(addEmail).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(addEmail)).toEqual([
|
||||
{ domain: 'a.is-a.dev', exchanger: 'example.com', priority: 20 },
|
||||
]);
|
||||
expect(removeEmail).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(removeEmail)).toEqual([
|
||||
{ domain: 'c.is-a.dev', exchanger: 'mx1.hello.com', priority: 20 },
|
||||
{ domain: 'b.is-a.dev', exchanger: 'foo.bar', priority: 20 },
|
||||
]);
|
||||
|
||||
expect(addRedir).toBeCalledTimes(3);
|
||||
expect(getRecordCalls(addRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://wowow.com' },
|
||||
|
||||
+24
-8
@@ -14,6 +14,10 @@ const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir } =
|
||||
add: (rec) => addRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
email: {
|
||||
add: (rec) => addEmail(rec),
|
||||
remove: (rec) => removeEmail(rec),
|
||||
},
|
||||
});
|
||||
|
||||
describe('toHostList', () => {
|
||||
@@ -22,6 +26,7 @@ describe('toHostList', () => {
|
||||
{ name: 'akshay', record: { CNAME: 'phenax.github.io' } },
|
||||
{ name: 'foobar', record: { CNAME: 'v.io' } },
|
||||
{ name: 'xx', record: { A: ['1.2.3.4', '5.6.3.2', '1.2.31.1'] } },
|
||||
{ name: 'xx', record: { CNAME: 'foobar.com', MX: ['as.com', 'f.com'] } },
|
||||
]);
|
||||
|
||||
expect(res).toEqual([
|
||||
@@ -30,6 +35,9 @@ describe('toHostList', () => {
|
||||
{ name: 'xx', type: 'A', address: '1.2.3.4', ttl: TTL },
|
||||
{ name: 'xx', type: 'A', address: '5.6.3.2', ttl: TTL },
|
||||
{ name: 'xx', type: 'A', address: '1.2.31.1', ttl: TTL },
|
||||
{ name: 'xx', type: 'CNAME', address: 'foobar.com', ttl: TTL },
|
||||
{ name: 'xx', type: 'MX', address: 'as.com', priority: 20, ttl: TTL },
|
||||
{ name: 'xx', type: 'MX', address: 'f.com', priority: 21, ttl: TTL },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -39,21 +47,29 @@ describe('registerDomains', () => {
|
||||
const removeZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const removeRedir = jest.fn(async () => ({}));
|
||||
const addEmail = jest.fn(async () => ({}));
|
||||
const removeEmail = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
}) });
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({
|
||||
cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addEmail,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
removeEmail,
|
||||
})
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
removeZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
removeRedir.mockClear();
|
||||
addEmail.mockClear();
|
||||
removeEmail.mockClear();
|
||||
});
|
||||
|
||||
it('should register the new set of hosts generated from domains list', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { validateDomainData } = require('../utils/validations');
|
||||
const { validateDomainData, isValidDomain } = require('../utils/validations');
|
||||
const INVALID_NAMES = require('../utils/invalid-domains.json');
|
||||
|
||||
const defaultDomain = {
|
||||
name: 'aaa',
|
||||
@@ -13,6 +14,25 @@ const defaultDomain = {
|
||||
|
||||
const getstroflen = len => Array(len).fill('a').join('');
|
||||
|
||||
describe('isValidMX', () => {
|
||||
it('should be valid mx record', () => {
|
||||
const cases = [
|
||||
{ mx: 'foobar.com', result: true },
|
||||
{ mx: 'as.as', result: true },
|
||||
{ mx: 'ASPMX.L.GOOGLE.COM', result: true },
|
||||
{ mx: 'ALT4.ASPMX.L.GOOGLE.COM', result: true },
|
||||
{ mx: 'hello', result: false },
|
||||
{ mx: 'helalsds-asd5sjdsd.com', result: true },
|
||||
{ mx: 'helalsds?asd5sjdsd.com', result: false },
|
||||
{ mx: 'helalsds_asd5sjdsd.com', result: false },
|
||||
];
|
||||
|
||||
cases.forEach(({ mx, result }) => {
|
||||
expect(isValidDomain(mx)).toBe(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateDomainData', () => {
|
||||
const invalidCases = [
|
||||
{},
|
||||
@@ -32,6 +52,10 @@ describe('validateDomainData', () => {
|
||||
{ ...defaultDomain, record: { CNAME: 'http://foobar.com' } },
|
||||
{ ...defaultDomain, record: { CNAME: 'https://foobar.com' } },
|
||||
{ ...defaultDomain, record: { URL: 'foobar.com' } },
|
||||
{ ...defaultDomain, record: { CNAME: 'foobar.com', A: ['11.22.22.33'] } },
|
||||
{ ...defaultDomain, record: { CNAME: 'foobar.com', MX: ['ALT4.ASPMX.L.GOOGLE.COM'] } },
|
||||
...INVALID_NAMES.map(name => ({ ...defaultDomain, name })).slice(0, 1),
|
||||
{ ...defaultDomain, record: { TXT: ['foobar wow nice!!!'] } },
|
||||
];
|
||||
|
||||
const validCases = [
|
||||
@@ -44,9 +68,12 @@ describe('validateDomainData', () => {
|
||||
...defaultDomain,
|
||||
description: getstroflen(99),
|
||||
},
|
||||
{ ...defaultDomain, record: { CNAME: 'aa.sd', URL: '121,3213' } },
|
||||
{ ...defaultDomain, record: { CNAME: 'aa.sd' } },
|
||||
{ ...defaultDomain, record: { URL: 'https://foobar.com' } },
|
||||
{ ...defaultDomain, record: { URL: 'http://foobar.com/foobar/' } },
|
||||
{ ...defaultDomain, record: { MX: ['ALT4.ASPMX.L.GOOGLE.COM'] } },
|
||||
{ ...defaultDomain, record: { TXT: 'foobar wow nice!!!' } },
|
||||
{ ...defaultDomain, record: { A: ['1.1.1.1'], MX: ['mx1.example.com'] } },
|
||||
];
|
||||
|
||||
it('should return false for invalid data', () => {
|
||||
@@ -60,7 +87,7 @@ describe('validateDomainData', () => {
|
||||
it('should return true if the name is valid', () => {
|
||||
validCases.forEach(data => {
|
||||
const { valid, errors } = validateDomainData(data);
|
||||
if (!valid) console.log(errors);
|
||||
if (!valid) console.log(JSON.stringify(errors, null, 2));
|
||||
expect(valid).toBe(true);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
+2
-2
@@ -21,12 +21,12 @@ const DOMAINS_PATH = require('path').resolve('domains');
|
||||
module.exports = {
|
||||
ENV,
|
||||
IS_TEST,
|
||||
VALID_RECORD_TYPES: ['CNAME', 'A', 'URL'],
|
||||
VALID_RECORD_TYPES: ['CNAME', 'A', 'URL', 'MX', 'TXT'],
|
||||
DOMAIN_DOMAIN: DOMAIN_DOMAIN || 'booboo.xyz',
|
||||
DOMAIN_USER: IS_TEST ? 'testuser' : DOMAIN_USER,
|
||||
DOMAIN_API_KEY: IS_TEST ? 'testkey' : DOMAIN_API_KEY,
|
||||
DOMAIN_API_HOST: IS_TEST ? 'example.com' : DOMAIN_API_HOST,
|
||||
DOMAIN_API_PORT: IS_TEST ? 6969 : DOMAIN_API_PORT,
|
||||
DOMAINS_PATH,
|
||||
TTL: 5*60*60,
|
||||
TTL: 5 * 60 * 60,
|
||||
};
|
||||
|
||||
+19
-6
@@ -12,22 +12,24 @@ const recordToRedirection = ({ name, address }) => ({
|
||||
redirect_wildcard: 1,
|
||||
redirect_www: 1,
|
||||
});
|
||||
const recordToZone = ({ name, type, address, id }) => ({
|
||||
const recordToZone = ({ name, type, address, id, priority }) => ({
|
||||
line: id,
|
||||
name,
|
||||
type,
|
||||
address,
|
||||
...(type === 'MX' ? { priority } : {}),
|
||||
...(type === 'CNAME' ? { cname: address } : {}),
|
||||
...(type === 'TXT' ? { txtdata: address } : {}),
|
||||
});
|
||||
|
||||
const cleanName = name => name === DOMAIN_DOMAIN ? '@' : `${name}`.replace(new RegExp(`\\.${DOMAIN_DOMAIN}\\.?$`), '').toLowerCase();
|
||||
|
||||
const zoneToRecord = ({ name, type, cname, address, record, line: id }) => ({
|
||||
const zoneToRecord = ({ name, type, cname, address, priority, preference, exchange, record, line: id }) => ({
|
||||
id,
|
||||
name: cleanName(name),
|
||||
type: `${type}`,
|
||||
address: `${cname || address || record}`.replace(/\.$/g, '').toLowerCase(),
|
||||
address: `${exchange || cname || address || record}`.replace(/\.$/g, '').toLowerCase(),
|
||||
priority: priority || preference,
|
||||
});
|
||||
const redirectionToRecord = ({ domain, destination }) => ({
|
||||
id: domain,
|
||||
@@ -36,6 +38,12 @@ const redirectionToRecord = ({ domain, destination }) => ({
|
||||
address: `${destination}`.replace(/\/$/g, ''),
|
||||
});
|
||||
|
||||
const recordToEmailMx = ({ name, address, priority }) => ({
|
||||
domain: `${name}.is-a.dev`,
|
||||
exchanger: address,
|
||||
priority,
|
||||
})
|
||||
|
||||
const getHostKey = host => `${host.name}##${host.type}##${host.address}`;
|
||||
|
||||
const diffRecords = (oldRecords, newRecords) => {
|
||||
@@ -69,13 +77,18 @@ const getDomainService = ({ cpanel }) => {
|
||||
const fetchRedirections = R.compose(then(R.map(redirectionToRecord)), cpanel.redirection.fetch);
|
||||
|
||||
const addZoneRecord = lazyTask(R.compose(
|
||||
cpanel.zone.add,
|
||||
R.ifElse(R.propEq('type', 'MX'),
|
||||
R.compose(cpanel.email.add, recordToEmailMx),
|
||||
cpanel.zone.add
|
||||
),
|
||||
recordToZone,
|
||||
print(({ name }) => `Adding zone for ${name}...`),
|
||||
));
|
||||
const removeZoneRecord = lazyTask(R.compose(
|
||||
cpanel.zone.remove,
|
||||
R.pick(['line']),
|
||||
R.ifElse(R.propEq('type', 'MX'),
|
||||
R.compose(cpanel.email.remove, recordToEmailMx),
|
||||
R.compose(cpanel.zone.remove, R.pick(['line']))
|
||||
),
|
||||
recordToZone,
|
||||
print(({ name }) => `Deleting zone for ${name}...`),
|
||||
));
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
"help",
|
||||
"support",
|
||||
"no-reply",
|
||||
"noreply",
|
||||
"notifications",
|
||||
"notification",
|
||||
"ww1",
|
||||
"ww2",
|
||||
"ww3",
|
||||
"ww4"
|
||||
]
|
||||
+13
-3
@@ -4,7 +4,6 @@ const qs = require('qs');
|
||||
const { DOMAIN_API_HOST, DOMAIN_API_PORT, DOMAIN_USER, DOMAIN_API_KEY, DOMAIN_DOMAIN } = require('../constants');
|
||||
|
||||
const CpanelClient = (options) => {
|
||||
// TODO: Make defaultQuery functional
|
||||
const api = ({ basePath = '', action = '' }) => (module, func, defaultQuery = {}) => (q = {}) => {
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
@@ -36,7 +35,7 @@ const CpanelClient = (options) => {
|
||||
return {
|
||||
zone: {
|
||||
// { customonly, domain }
|
||||
// -> { cpanelresult: { data[{ class, ttl, name, line, Line, cname, type, record }] } }
|
||||
// -> [{ class, ttl, name, line, Line, cname, type, record }]
|
||||
fetch: R.compose(
|
||||
p => p.then(R.pathOr([], ['cpanelresult', 'data'])),
|
||||
api2('ZoneEdit', 'fetchzone_records', { customonly: 1, domain: options.domain })
|
||||
@@ -45,13 +44,14 @@ const CpanelClient = (options) => {
|
||||
// { name, type(A|CNAME), cname, address, ttl }
|
||||
// -> {}
|
||||
add: api2('ZoneEdit', 'add_zone_record', { domain: options.domain }),
|
||||
|
||||
// { line }
|
||||
// -> {}
|
||||
remove: api2('ZoneEdit', 'remove_zone_record', { domain: options.domain }),
|
||||
},
|
||||
redirection: {
|
||||
// {}
|
||||
// -> { domain, destination }
|
||||
// -> [{ domain, destination }]
|
||||
fetch: R.compose(
|
||||
p => p.then(R.pathOr([], ['data'])),
|
||||
uapi('Mime', 'list_redirects'),
|
||||
@@ -60,6 +60,7 @@ const CpanelClient = (options) => {
|
||||
// { domain, redirect, type(permanent|tmp), redirect_wildcard(0|1), redirect(0|1|2) }
|
||||
// -> {}
|
||||
add: uapi('Mime', 'add_redirect'),
|
||||
|
||||
// { domain }
|
||||
// -> {}
|
||||
remove: uapi('Mime', 'delete_redirect'),
|
||||
@@ -67,6 +68,15 @@ const CpanelClient = (options) => {
|
||||
file: {
|
||||
write: uapi('Fileman', 'save_file_content', { from_charset: 'UTF-8', to_charset: 'UTF-8', fallback: 1 }),
|
||||
},
|
||||
email: {
|
||||
// { domain, exchanger, priority }
|
||||
// -> {}
|
||||
add: uapi('Email', 'add_mx', { alwaysaccept: 'auto' }),
|
||||
|
||||
// { domain, exchanger, priority }
|
||||
// -> {}
|
||||
remove: uapi('Email', 'delete_mx', { alwaysaccept: 'auto' }),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+32
-14
@@ -1,19 +1,34 @@
|
||||
const R = require('ramda');
|
||||
const { VALID_RECORD_TYPES } = require('./constants');
|
||||
const { or, and, validate, between, testRegex, withLengthEq, withLengthGte } = require('./helpers');
|
||||
const INVALID_NAMES = require('./invalid-domains.json');
|
||||
|
||||
const isValidURL = testRegex(/^https?:\/\//ig);
|
||||
const isValidURL = and([R.is(String), testRegex(/^https?:\/\//ig)]);
|
||||
|
||||
const validateCnameRecord = key => and([
|
||||
R.propSatisfies(R.is(String), key),
|
||||
R.compose(withLengthEq(1), R.reject(R.equals('URL')), R.keys),
|
||||
R.propSatisfies(withLengthGte(3), key),
|
||||
R.propSatisfies(R.complement(isValidURL), key),
|
||||
const isValidDomain = and([R.is(String), testRegex(/^(([a-z0-9\-]+)\.)+[a-z]+$/ig)]);
|
||||
|
||||
const allowMXRecord = R.compose(
|
||||
R.ifElse(R.includes('MX'), withLengthEq(2), withLengthEq(1)),
|
||||
R.keys,
|
||||
);
|
||||
|
||||
const validateCnameRecord = type => and([
|
||||
R.propIs(String, type),
|
||||
R.compose(withLengthEq(1), R.keys), // CNAME cannot be used with any other record
|
||||
R.propSatisfies(withLengthGte(4), type),
|
||||
R.propSatisfies(isValidDomain, type),
|
||||
]);
|
||||
|
||||
const validateARecord = key => and([
|
||||
R.compose(withLengthEq(1), R.keys),
|
||||
R.propSatisfies(withLengthGte(1), key),
|
||||
const validateARecord = type => and([
|
||||
R.propIs(Array, type),
|
||||
allowMXRecord,
|
||||
R.propSatisfies(withLengthGte(1), type),
|
||||
]);
|
||||
|
||||
const validateMXRecord = type => and([
|
||||
R.propIs(Array, type),
|
||||
R.propSatisfies(withLengthGte(1), type),
|
||||
R.propSatisfies(R.all(isValidDomain), type),
|
||||
]);
|
||||
|
||||
const validateDomainData = validate({
|
||||
@@ -24,6 +39,7 @@ const validateDomainData = validate({
|
||||
and([
|
||||
R.compose(between(2, 100), R.length),
|
||||
testRegex(/^[a-z0-9-]+$/g),
|
||||
R.complement(R.includes(R.__, INVALID_NAMES)),
|
||||
])
|
||||
]),
|
||||
},
|
||||
@@ -35,7 +51,7 @@ const validateDomainData = validate({
|
||||
R.is(Object),
|
||||
R.complement(R.isEmpty),
|
||||
R.where({
|
||||
username: and([ R.is(String), withLengthGte(1) ]),
|
||||
username: and([R.is(String), withLengthGte(1)]),
|
||||
email: R.is(String),
|
||||
}),
|
||||
]),
|
||||
@@ -46,13 +62,15 @@ const validateDomainData = validate({
|
||||
R.is(Object),
|
||||
R.compose(R.isEmpty, R.difference(R.__, VALID_RECORD_TYPES), R.keys),
|
||||
R.cond([
|
||||
[R.has('CNAME'), validateCnameRecord('CNAME')],
|
||||
[R.has('A'), validateARecord('A')],
|
||||
[R.has('URL'), R.propSatisfies(isValidURL, 'URL')],
|
||||
[R.has('CNAME'), validateCnameRecord('CNAME')],
|
||||
[R.has('A'), validateARecord('A')],
|
||||
[R.has('URL'), R.propSatisfies(isValidURL, 'URL')],
|
||||
[R.has('MX'), validateMXRecord('MX')],
|
||||
[R.has('TXT'), R.propSatisfies(R.is(String), 'TXT')],
|
||||
[R.T, R.T],
|
||||
]),
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = { validateDomainData };
|
||||
module.exports = { validateDomainData, isValidDomain };
|
||||
|
||||
Reference in New Issue
Block a user