164 lines
4.7 KiB
JavaScript
164 lines
4.7 KiB
JavaScript
'use strict'
|
|
|
|
let request = require('@root/request')
|
|
request = require('util').promisify(request)
|
|
|
|
const Joi = require('@hapi/joi')
|
|
|
|
const schema = Joi.object().keys({
|
|
authKey: Joi.string().alphanum(),
|
|
bearerTokens: Joi.object({
|
|
list: Joi.string().alphanum().required(),
|
|
zone: Joi.string().alphanum().required()
|
|
}),
|
|
authEmail: Joi.string().email({ minDomainSegments: 2 }).required(),
|
|
baseUrl: Joi.string()
|
|
}).with('username', 'birthyear').without('password', 'access_token').nand(
|
|
'authKey', 'bearerTokens'
|
|
)
|
|
|
|
function formatError (msg, resp) {
|
|
const e = new Error(`${resp.statusCode}: ${msg}! (Check the Credentials)`)
|
|
e.body = resp.body
|
|
if (resp.body && resp.body.errors) {
|
|
e.errors = resp.body.errors
|
|
console.log(e.errors[0].error_chain)
|
|
}
|
|
e.statusCode = resp.statusCode
|
|
throw e
|
|
}
|
|
|
|
var defaults = {
|
|
baseUrl: 'https://api.cloudflare.com/client/v4/'
|
|
}
|
|
|
|
module.exports.create = function (config) {
|
|
Joi.validate(config, schema)
|
|
const baseUrl = (config.baseUrl || defaults.baseUrl).replace(/\/$/, '')
|
|
|
|
const defaultHeaders = {
|
|
'Content-Type': 'application/json',
|
|
'X-Auth-Email': config.authEmail
|
|
}
|
|
if (config.authKey) {
|
|
defaultHeaders['X-Auth-Key'] = config.authKey
|
|
}
|
|
function api (method, path, body, tokenType) {
|
|
const headers = defaultHeaders;
|
|
if (tokenType && config.bearerTokens) {
|
|
if (!(tokenType in config.bearerTokens)) {
|
|
throw new Error('Unrecognized token type');
|
|
}
|
|
headers['Authorization'] = 'Bearer ' + config.bearerTokens[tokenType];
|
|
}
|
|
return request({
|
|
url: baseUrl + path,
|
|
json: true,
|
|
method,
|
|
headers,
|
|
body
|
|
})
|
|
}
|
|
|
|
async function zones (domain) {
|
|
const resp = await api('GET', '/zones?per_page=1000' + (domain ? '&name=' + domain : ''), undefined, 'list') // TODO: use proper pagination?!
|
|
if (resp.statusCode !== 200) {
|
|
formatError('Could not get list of Zones', resp)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
async function getZone (domain) {
|
|
const res = await zones(domain)
|
|
return res.body.result.filter(zone => (zone.name === domain))[0]
|
|
}
|
|
|
|
return {
|
|
zones: async function (data) {
|
|
const resp = await zones()
|
|
return resp.body.result.map(x => x.name)
|
|
},
|
|
set: async function (data) {
|
|
const {
|
|
challenge: {
|
|
dnsZone: domain,
|
|
dnsAuthorization: txtRecord,
|
|
dnsPrefix
|
|
}
|
|
} = data
|
|
|
|
const zone = await getZone(domain)
|
|
if (zone.permissions.indexOf('#zone:edit') === -1) {
|
|
throw new Error('Can not edit zone ' + JSON.stringify(domain) + ' from this account')
|
|
}
|
|
|
|
const resp = await api('POST', `/zones/${zone.id}/dns_records`, {type: 'TXT', name: dnsPrefix, content: txtRecord, ttl: 300}, 'zone')
|
|
if (resp.statusCode !== 200) {
|
|
formatError('Could not add record', resp)
|
|
}
|
|
|
|
return true
|
|
},
|
|
remove: async function (data) {
|
|
const {
|
|
challenge: {
|
|
dnsZone: domain,
|
|
dnsAuthorization: txtRecord,
|
|
dnsPrefix
|
|
}
|
|
} = data
|
|
|
|
const zone = await getZone(domain)
|
|
if (zone.permissions.indexOf('#zone:edit') === -1) {
|
|
throw new Error('Can not edit zone ' + JSON.stringify(domain) + ' from this account')
|
|
}
|
|
|
|
const resp = await api('GET', `/zones/${zone.id}/dns_records?name=${encodeURI(dnsPrefix + '.' + domain)}`, undefined,'zone')
|
|
if (resp.statusCode !== 200) {
|
|
formatError('Could not read record', resp)
|
|
}
|
|
|
|
let {result} = resp.body
|
|
|
|
let record = result.filter(record => (record.type === 'TXT' && record.content === txtRecord))[0]
|
|
|
|
if (record) {
|
|
const resp = await api('DELETE', `/zones/${zone.id}/dns_records/${record.id}`, undefined, 'zone')
|
|
if (resp.statusCode !== 200) {
|
|
formatError('Could not delete record', resp)
|
|
}
|
|
}
|
|
return null // TODO: not found. should this throw?!
|
|
},
|
|
get: async function (data) {
|
|
const {
|
|
challenge: {
|
|
dnsZone: domain,
|
|
dnsAuthorization: txtRecord,
|
|
dnsPrefix
|
|
}
|
|
} = data
|
|
|
|
const zone = await getZone(domain)
|
|
if (zone.permissions.indexOf('#zone:read') === -1) {
|
|
throw new Error('Can not read zone ' + JSON.stringify(domain) + ' from this account')
|
|
}
|
|
|
|
const resp = await api('GET', `/zones/${zone.id}/dns_records?name=${encodeURI(dnsPrefix + '.' + domain)}`, undefined, 'zone')
|
|
if (resp.statusCode !== 200) {
|
|
formatError('Could not read record', resp)
|
|
}
|
|
|
|
let {result} = resp.body
|
|
|
|
let record = result.filter(record => (record.type === 'TXT' && record.content === txtRecord))[0]
|
|
|
|
if (record) {
|
|
return {dnsAuthorization: record.content}
|
|
} else {
|
|
return null // TODO: not found. should this throw?!
|
|
}
|
|
}
|
|
}
|
|
}
|