diff --git a/README.md b/README.md
index bc1efdb..87564bc 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Handles ACME dns-01 challenges. Compatible with ACME.js and Greenlock.js. Passes
- Compatible
- Let’s Encrypt v2.1 / ACME draft 18 (2019)
- - Name.com API
+ - Name.com v4 API
- ACME.js, Greenlock.js, and others
- Quality
- node v6 compatible VanillaJS
@@ -21,10 +21,22 @@ Handles ACME dns-01 challenges. Compatible with ACME.js and Greenlock.js. Passes
npm install --save acme-dns-01-namedotcom
```
-Name.com Token:
+## Name.com API Token
-- Login to your account at: {{ Service URL }}
-- {{ Instructions to generate token }}
+- Login to your account at:
+- Generate a token at
+- **Important** Enable API Access, _again_ at
+
+The following error is what you may get if you have Two-Factor Auth and don't _Enable API Access_ the second time:
+
+```json
+{
+ "message": "Permission Denied",
+ "details": "Authentication Error - Account Has Namesafe Enabled"
+}
+```
+
+If you're using the Whitelist IPs feature, don't forget to add your test environment!
# Usage
@@ -32,7 +44,8 @@ First you create an instance with your credentials:
```js
var dns01 = require('acme-dns-01-namedotcom').create({
- baseUrl: '{{ api url }}', // default
+ baseUrl: 'http://api.name.com/v4/', // default
+ username: 'johndoe',
token: 'xxxx'
});
```
@@ -51,7 +64,8 @@ var greenlock = Greenlock.create({
});
```
-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.
+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
@@ -93,8 +107,8 @@ See acme-dns-01-test for more implementation details.
# Tests
```bash
-# node ./test.js domain-zone api-token
-node ./test.js example.com xxxxxx
+# node ./test.js domain-zone username api-token
+node ./test.js example.com me xxxxxx
```
# Authors
diff --git a/example.env b/example.env
index 48ceb39..4feaac3 100644
--- a/example.env
+++ b/example.env
@@ -1,2 +1,4 @@
ZONE=example.co.uk
-TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+# Used for HTTP Basic Auth
+USERNAME=johndoe
+TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/lib/index.js b/lib/index.js
index e25856a..8cb2b83 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,29 +1,137 @@
'use strict';
var request;
-var defaults = {};
+var defaults = {
+ baseUrl: 'https://api.name.com/v4/'
+};
module.exports.create = function(config) {
- return {
+ var baseUrl = (config.baseUrl || defaults.baseUrl).replace(/\/$/, '');
+ var token = config.token;
+ var username = config.username;
+
+ var plugin = {
init: function(opts) {
request = opts.request;
return null;
},
+
+ // We must list all zones (domains) in the account
zones: function(data) {
- //console.info('List Zones', data);
- throw Error('listing zones not implemented');
+ return api({
+ url: baseUrl + '/domains'
+ }).then(function(resp) {
+ return resp.body.domains.map(function(d) {
+ //#console.log("Domain Name:", d.domainName);
+ return d.domainName;
+ });
+ });
},
+
+ // We must set each record as required
set: function(data) {
- // console.info('Add TXT', data);
- throw Error('setting TXT not implemented');
- },
- remove: function(data) {
- // console.info('Remove TXT', data);
- throw Error('removing TXT not implemented');
+ // console.log('Add TXT', data);
+ var ch = data.challenge;
+ if (!ch.dnsZone) {
+ throw new Error(
+ '[Name.com Plugin] Unknown domain: ',
+ data.domain || data.dnsHost
+ );
+ }
+ return api({
+ method: 'POST',
+ url: baseUrl + '/domains/' + ch.dnsZone + '/records',
+ json: {
+ host: ch.dnsPrefix,
+ type: 'TXT',
+ answer: ch.dnsAuthorization,
+ ttl: 300 // minimum allowed value
+ }
+ }).then(function(resp) {
+ if (!resp.body.id) {
+ throw Error('[Name.com API] [set] ' + resp.body);
+ }
+ return null;
+ });
},
+
+ // We must be able to confirm that the appropriate records were set
get: function(data) {
- // console.info('List TXT', data);
- throw Error('listing TXTs not implemented');
+ // console.log('List TXT', data);
+ var ch = data.challenge;
+ if (!ch.dnsZone) {
+ throw new Error(
+ '[Name.com Plugin] Unknown domain: ',
+ data.domain || data.dnsHost
+ );
+ }
+ return api({
+ url: baseUrl + '/domains/' + ch.dnsZone + '/records'
+ }).then(function(resp) {
+ var value = resp.body.records.filter(function(r) {
+ return (
+ r.host === ch.dnsPrefix &&
+ 'TXT' === r.type &&
+ ch.dnsAuthorization === r.answer
+ );
+ })[0];
+ if (!value) {
+ return null;
+ }
+ // adding id to make re-usable for remove
+ return { id: value.id, dnsAuthorization: value.answer };
+ });
+ },
+
+ // We must delete junk records once we're done
+ remove: function(data) {
+ // console.log('Remove TXT', data);
+ var ch = data.challenge;
+ return plugin.get(data).then(function(r) {
+ if (!r.id) {
+ throw new Error(
+ '[Name.com Plugin] [del] Did not find TXT record for ' +
+ ch.dnsHost
+ );
+ }
+ return api({
+ method: 'DELETE',
+ url: baseUrl + '/domains/' + ch.dnsZone + '/records/' + r.id
+ }).then(function(resp) {
+ return null;
+ });
+ });
}
};
+
+ // Authentication and Error handling here
+ function api(opts) {
+ opts.auth = {
+ user: username,
+ pass: token,
+ sendImmediately: true
+ };
+ if (!opts.json) {
+ opts.json = true;
+ }
+ return request(opts).then(function(resp) {
+ if (!resp.body.message) {
+ return resp;
+ }
+
+ console.error(opts.method + ' ' + opts.url);
+ console.error(resp.headers);
+ console.error(resp.body);
+ throw new Error(
+ '[Name.com API] ' +
+ (opts.method || 'GET') +
+ ' ' +
+ opts.url +
+ ' : ' +
+ resp.body.message
+ );
+ });
+ }
+
+ return plugin;
};
diff --git a/package.json b/package.json
index d4cdd44..b101697 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "acme-dns-01-namedotcom",
- "version": "0.0.1",
+ "version": "3.0.0",
"description": "Name.com + Let's Encrypt for Node.js - ACME dns-01 challenges w/ ACME.js and Greenlock.js",
"main": "index.js",
"files": [
@@ -26,9 +26,9 @@
"author": "AJ ONeal (https://coolaj86.com/)",
"license": "MPL-2.0",
"devDependencies": {
+ "acme-challenge-test": "^3.3.2",
"dotenv": "^8.0.0"
},
"dependencies": {
- "acme-challenge-test": "^3.3.2"
}
}
diff --git a/test.js b/test.js
index d8b9412..04258f6 100755
--- a/test.js
+++ b/test.js
@@ -5,10 +5,12 @@
var tester = require('acme-challenge-test');
require('dotenv').config();
-// Usage: node ./test.js example.com xxxxxxxxx
+// Usage: node ./test.js example.com username xxxxxxxxx
var zone = process.argv[2] || process.env.ZONE;
var challenger = require('./index.js').create({
- token: process.argv[3] || process.env.TOKEN
+ //baseUrl: 'http://api.dev.name.com/v4/', // sandbox url
+ username: process.argv[3] || process.env.USERNAME,
+ token: process.argv[4] || process.env.TOKEN
});
// The dry-run tests can pass on, literally, 'example.com'
diff --git a/token-test.sh b/token-test.sh
new file mode 100644
index 0000000..dafe22d
--- /dev/null
+++ b/token-test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+
+# Before assuming there's something wrong with this plugin,
+# you should test that you can use your token with curl first.
+
+echo "USERNAME: '$USERNAME'"
+echo "TOKEN: '$TOKEN'"
+curl -f -u "$USERNAME:$TOKEN" https://api.name.com/v4/domains