Added support for authentication tokens + fix for #1 #2
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @ -13,7 +13,7 @@ npm install --save acme-dns-01-cloudflare@3.x | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| First you create an instance with your credentials: | ||||
| First you create an instance with your account credentials: | ||||
| 
 | ||||
| ```js | ||||
| var dns01 = require('acme-dns-01-cloudflare').create({ | ||||
| @ -22,6 +22,18 @@ var dns01 = require('acme-dns-01-cloudflare').create({ | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| or token credentials: | ||||
| 
 | ||||
| ```js | ||||
| var dns01 = require('acme-dns-01-cloudflare').create({ | ||||
| 	bearerTokens: { | ||||
| 		list: '123yourListToken', // This token needs to be able to list all of your zones | ||||
| 		zone: '456yourZoneToken' // This token needs to have full control over the targeted DNS zone(s) | ||||
| 	}, | ||||
| 	authEmail: 'you@example.com' | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| Then you can use it with any compatible ACME module, | ||||
| such as Greenlock.js or ACME.js. | ||||
| 
 | ||||
| @ -72,6 +84,8 @@ for more implementation details. | ||||
| # Tests | ||||
| 
 | ||||
| ```bash | ||||
| # node ./test.js domain-zone auth-key auth-email | ||||
| node ./test.js example.com xxxxxx you@example.com | ||||
| # node ./test.js domain-zone auth-email auth-type auth-credential (aux-credential?) | ||||
| node ./test.js example.com you@example.com key YourApiKey | ||||
| node ./test.js example.com you@example.com token YourApiTokenWithFullRights | ||||
| node ./test.js example.com you@example.com token YourApiTokenWithListRights YourApiTokenWithEditRightsForTheZone | ||||
| ``` | ||||
|  | ||||
							
								
								
									
										56
									
								
								lib/index.js
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								lib/index.js
									
									
									
									
									
								
							| @ -6,10 +6,16 @@ request = require('util').promisify(request) | ||||
| const Joi = require('@hapi/joi') | ||||
| 
 | ||||
| const schema = Joi.object().keys({ | ||||
|   authKey: Joi.string().alphanum().required(), | ||||
|   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') | ||||
| }).with('username', 'birthyear').without('password', 'access_token').nand( | ||||
|   'authKey', 'bearerTokens' | ||||
| ) | ||||
| 
 | ||||
| function formatError (msg, resp) { | ||||
|   const e = new Error(`${resp.statusCode}: ${msg}! (Check the Credentials)`) | ||||
| @ -27,25 +33,35 @@ var defaults = { | ||||
| } | ||||
| 
 | ||||
| module.exports.create = function (config) { | ||||
|   const baseUrl = (config.baseUrl || defaults.baseUrl).replace(/\/$/, '') | ||||
|   Joi.validate(config, schema) | ||||
|   const baseUrl = (config.baseUrl || defaults.baseUrl).replace(/\/$/, '') | ||||
| 
 | ||||
|   function api (method, path, body) { | ||||
|   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({ | ||||
|       method: method, | ||||
|       url: baseUrl + path, | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|         'X-Auth-Key': config.authKey, | ||||
|         'X-Auth-Email': config.authEmail | ||||
|       }, | ||||
|       json: true, | ||||
|       method, | ||||
|       headers, | ||||
|       body | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   async function zones (domain) { | ||||
|     const resp = await api('GET', '/zones?per_page=1000' + (domain ? '&name=' + domain : '')) // TODO: use proper pagination?!
 | ||||
|     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) | ||||
|     } | ||||
| @ -70,14 +86,13 @@ module.exports.create = function (config) { | ||||
|           dnsPrefix | ||||
|         } | ||||
|       } = data | ||||
|       console.log(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}) | ||||
|       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) | ||||
|       } | ||||
| @ -88,6 +103,7 @@ module.exports.create = function (config) { | ||||
|       const { | ||||
|         challenge: { | ||||
|           dnsZone: domain, | ||||
|           dnsAuthorization: txtRecord, | ||||
|           dnsPrefix | ||||
|         } | ||||
|       } = data | ||||
| @ -97,28 +113,28 @@ module.exports.create = function (config) { | ||||
|         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)}`) | ||||
|       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'))[0] | ||||
|       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}`) | ||||
|         const resp = await api('DELETE', `/zones/${zone.id}/dns_records/${record.id}`, undefined, 'zone') | ||||
|         if (resp.statusCode !== 200) { | ||||
|           formatError('Could not delete record', resp) | ||||
|         } | ||||
|       } else { | ||||
|         return null // TODO: not found. should this throw?!
 | ||||
|       } | ||||
|       return null // TODO: not found. should this throw?!
 | ||||
|     }, | ||||
|     get: async function (data) { | ||||
|       const { | ||||
|         challenge: { | ||||
|           dnsZone: domain, | ||||
|           dnsAuthorization: txtRecord, | ||||
|           dnsPrefix | ||||
|         } | ||||
|       } = data | ||||
| @ -128,14 +144,14 @@ module.exports.create = function (config) { | ||||
|         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)}`) | ||||
|       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'))[0] | ||||
|       let record = result.filter(record => (record.type === 'TXT' && record.content === txtRecord))[0] | ||||
| 
 | ||||
|       if (record) { | ||||
|         return {dnsAuthorization: record.content} | ||||
|  | ||||
							
								
								
									
										18
									
								
								test.js
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								test.js
									
									
									
									
									
								
							| @ -2,11 +2,21 @@ | ||||
| 'use strict' | ||||
| 
 | ||||
| // https://git.rootprojects.org/root/acme-dns-01-test.js
 | ||||
| var tester = require('acme-dns-01-test') | ||||
| const tester = require('acme-dns-01-test') | ||||
| 
 | ||||
| // Usage: node ./test.js example.com xxxxxxxxx
 | ||||
| let [zone, authKey, authEmail] = process.argv.slice(2) | ||||
| var challenger = require('./index.js').create({ authKey, authEmail }) | ||||
| // Usage: node ./test.js example.com you@example.com key xxxxxxxxx
 | ||||
| // Usage: node ./test.js example.com you@example.com token xxxxxxxxx
 | ||||
| // Usage: node ./test.js example.com you@example.com token xxxxxxxxx yyyyyyy
 | ||||
| const [zone, authEmail, authType, credential, zoneToken] = process.argv.slice(2) | ||||
| const config = { authEmail } | ||||
| switch (authType) { | ||||
|   case 'token': | ||||
|     config.bearerTokens = {list: credential, zone: zoneToken || credential} | ||||
|     break | ||||
|   default: | ||||
|     config.authKey = credential | ||||
| } | ||||
| const challenger = require('./index.js').create(config) | ||||
| 
 | ||||
| // The dry-run tests can pass on, literally, 'example.com'
 | ||||
| // but the integration tests require that you have control over the domain
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user