commit c33c42899f14f44681d7782ef6c13e9a2cba5db4 Author: nyaundi brian Date: Sun Jul 28 14:28:13 2019 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bb61b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +*secret* +node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..420e082 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "bracketSpacing": true, + "printWidth": 80, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": true +} diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0d29c56 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +AJ ONeal (https://coolaj86.com/) +Nyaundi Brian (https://danleyb2.online/) diff --git a/README.md b/README.md new file mode 100644 index 0000000..5dbe457 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# [acme-dns-01-godaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js) | a [Root](https://rootprojects.org) project + +Godaddy DNS + Let's Encrypt for Node.js + +This handles ACME dns-01 challenges, compatible with ACME.js and Greenlock.js. +Passes [acme-dns-01-test](https://git.rootprojects.org/root/acme-dns-01-test.js). + +# Install + +```bash +npm install --save acme-dns-01-godaddy@3.x +``` + +# Usage + +First you create an instance with your API token: + +```js +var dns01 = require('acme-dns-01-godaddy').create({ + baseUrl: 'https://api.godaddy.com', // default + key: 'xxxx', + secret: 'xxxx' +}); +``` + +Then you can use it with any compatible ACME module, +such as Greenlock.js or ACME.js. + +### Greenlock.js + +```js +var Greenlock = require('greenlock-express'); +var greenlock = Greenlock.create({ + challenges: { + 'dns-01': dns01 + // ... + } +}); +``` + +See [Greenlockā„¢ Express](https://git.rootprojects.org/root/greenlock-express.js) +and/or [Greenlock.js](https://git.rootprojects.org/root/greenlock.js) documentation for more details. + +### ACME.js + +```js +// TODO +``` + +See the [ACME.js](https://git.rootprojects.org/root/acme-v2.js) for more details. + +### Build your own + +```js +dns01 + .set({ + identifier: { value: 'foo.example.com' }, + wildcard: false, + dnsHost: '_acme-challenge.foo.example.com', + dnsAuthorization: 'xxx_secret_xxx' + }) + .then(function() { + console.log('TXT record set'); + }) + .catch(function() { + console.log('Failed to set TXT record'); + }); +``` + +See [acme-dns-01-test](https://git.rootprojects.org/root/acme-dns-01-test.js) +for more implementation details. + +# Tests + +```bash +# node ./test.js domain-zone api-key api-secret +node ./test.js example.com xxxxxx xxxxxx +``` diff --git a/example.env b/example.env new file mode 100644 index 0000000..c9ece7c --- /dev/null +++ b/example.env @@ -0,0 +1,9 @@ +ZONE=example.co.uk +KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +# production +# BASE_URL=https://api.godaddy.com/v1/ + +# test +BASE_URL=https://api.ote-godaddy.com/v1/ diff --git a/index.js b/index.js new file mode 100644 index 0000000..647221a --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/index.js'); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..ed24b34 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,149 @@ +'use strict'; + +var request; // = require('@root/request'); +// request = require('util').promisify(request); + +var OTE_ENVIRONMENT = 'https://api.ote-godaddy.com/v1/'; +var PRODUCTION_ENVIRONMENT = 'https://api.godaddy.com/v1/'; + +var defaults = { + baseUrl: PRODUCTION_ENVIRONMENT +}; + +module.exports.create = function(config) { + var baseUrl = (config.baseUrl || defaults.baseUrl); + var apiKey = config.key || config.apiKey; + var apiSecret = config.secret || config.apiSecret; + + var auth = 'sso-key '+apiKey+':'+apiSecret; + + function api(method, path, form) { + return request({ + method: method, + url: baseUrl + path, + headers: { + 'Authorization': auth + }, + json: form || true + }); + } + + return { + init: function(deps) { + request = deps.request; + return null; + }, + + zones: function(data) { + return api('GET', 'domains?statuses=ACTIVE').then(function(resp) { + if (200 !== resp.statusCode) { + console.error(resp.statusCode); + console.error(resp.body); + throw new Error('Could not get list of zones. Check api key, etc'); + } + + return resp.body.map(function(x) { + return x.domain; + }); + + }); + }, + set: function(data) { + var ch = data.challenge; + var txt = ch.dnsAuthorization; + // If the domain to be verified is + + + // optional params commented + var records = [ + { + "data": txt, + "name": ch.dnsPrefix, + // "port": 0, + // "priority": 0, + // "protocol": "string", + // "service": "string", + "ttl": 600, + "type": "TXT", + // "weight": 0 + } + ]; + + return api('PATCH', 'domains/'+ch.dnsZone+'/records', records).then(function(resp) { + if (200 !== resp.statusCode) { + console.error(resp.statusCode); + console.error(resp.body); + throw new Error('record did not set. check subdomain, api key, etc'); + } + + return true; + }); + }, + remove: function(data) { + var ch = data.challenge; + + // get all domain records of type or name + return api('GET', 'domains/'+ch.dnsZone+'/records/TXT/'+ch.dnsPrefix) + .then(function(resp) { + if (200 !== resp.statusCode) { + console.error(resp.statusCode); + console.error(resp.body); + throw new Error('Could not get list of zones. Check api key, etc'); + } + + var newEntries = []; + // filter all TXT records without record to remove + for (let i = 0; i < resp.body.length; i++) { + if (resp.body[i]['data'] !== ch.dnsAuthorization) { + newEntries.push(resp.body[i]) + } + } + return newEntries; + }).then(function(newRecords) { + // godaddy doesn't provide an endpoint for a single record removal + // but provides this endpoint to replace all records of a given type + // https://developer.godaddy.com/doc/endpoint/domains#/v1/recordReplaceType + // however, calling the endpoint with no records does no changes + // hence when only a single record of type exists and is the one to remove + // we call the endpoint with this dummy record + + if(!newRecords.length) { + newRecords.push({ + "data": 'free_to_delete', + "name": 'remove_placeholder', + "ttl": 600 + + }); + } + + // update - overwrite all type and name records under domain + return api('PUT', 'domains/'+ch.dnsZone+'/records/TXT', newRecords).then(function(resp) { + if (200 !== resp.statusCode) { + console.error(resp.statusCode); + console.error(resp.body); + throw new Error('record did not remove. check subdomain, api key, etc'); + } + return true; + }); + }); + + + }, + get: function(data) { + var ch = data.challenge; + return api('GET', 'domains/'+ch.dnsZone+'/records/TXT/'+ch.dnsPrefix).then(function(resp) { + resp = resp.body; + + var entry = null; + + if(resp.length) { + entry = resp.filter(function(x) { + return x.data === ch.dnsAuthorization; + })[0]; + } + + return entry? { dnsAuthorization: entry.data }:null; + }); + } + }; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a128ad4 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "acme-dns-01-godaddy", + "version": "3.0.0", + "description": "Godaddy DNS for Let's Encrypt / ACME dns-01 challenges with ACME.js and Greenlock.js", + "main": "index.js", + "scripts": { + "test": "node ./test.js" + }, + "repository": { + "type": "git", + "url": "https://git.rootprojects.org/root/acme-dns-01-godaddy.git" + }, + "keywords": [ + "godaddy", + "dns", + "dns-01", + "letsencrypt", + "acme", + "greenlock" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "MPL-2.0", + "dependencies": { + "@root/request": "^1.3.11" + }, + "devDependencies": { + "acme-dns-01-test": "^3.2.1", + "dotenv": "^8.0.0" + } +} diff --git a/test.js b/test.js new file mode 100755 index 0000000..a3e108b --- /dev/null +++ b/test.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +'use strict'; + +// See https://git.coolaj86.com/coolaj86/acme-challenge-test.js +var tester = require('acme-challenge-test'); +require('dotenv').config(); + +// Usage: node ./test.js example.com xxxxxxxxx +var zone = process.argv[2] || process.env.ZONE; +var challenger = require('./index.js').create({ + key: process.argv[3] || process.env.KEY, + secret: process.argv[4] || process.env.SECRET, + baseUrl: process.argv[5] || process.env.BASE_URL +}); + +// The dry-run tests can pass on, literally, 'example.com' +// but the integration tests require that you have control over the domain +tester + .testZone('dns-01', zone, challenger) + .then(function() { + console.info('PASS', zone); + }) + .catch(function(e) { + console.error(e.message); + console.error(e.stack); + });