From d28eeb4f93a8a3e0eed9b823b8e6e47f7d366f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81vila?= Date: Fri, 19 Jul 2019 15:38:27 +0100 Subject: [PATCH] feat(auth): implement api tokens Implement API authentication using Cloudflare's API Tokens, which use RFC6750 bearer authentication instead of custom headers. Signed-off-by: Terin Stock --- README.md | 16 ++++++++++++++-- index.js | 4 +++- lib/Client.js | 16 +++++++++++----- lib/method.js | 2 +- test/Client.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8f191f8..2601908 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,11 @@ Node.js v4 and greater are supported. ## Configuration +### API Keys Set your account email address and API key. The API key can be found on -the [My Account][my-account] page in the Cloudflare dashboard. +the [My Profile -> API Tokens][api-tokens] page in the Cloudflare dashboard. -[my-account]: https://www.cloudflare.com/a/account +[api-tokens]: https://dash.cloudflare.com/profile/api-tokens ```javascript var cf = require('cloudflare')({ @@ -45,6 +46,17 @@ var cf = require('cloudflare')({ }); ``` +### API Tokens (BETA) +Create your token on the [My Profile -> API Tokens][api-tokens] page in the Cloudflare dashboard. + +[api-tokens]: https://dash.cloudflare.com/profile/api-tokens + +```javascript +var cf = require('cloudflare')({ + token: 'your Cloudflare API token' +}); +``` + ## API Overview Every resource is accessed via your `cf` instance: diff --git a/index.js b/index.js index db5c2d9..93d2f77 100644 --- a/index.js +++ b/index.js @@ -60,7 +60,8 @@ const withEnvProxy = function withEnvProxy(opts) { * @class Cloudflare * @param {Object} auth - The API authentication for an account * @param {string} auth.email - The account email address - * @param {string} auth.key - The account API token key + * @param {string} auth.key - The account API key + * @param {string} auth.token - The account API token * * @property {DNSRecords} dnsRecords - DNS Records instance * @property {IPs} ips - IPs instance @@ -75,6 +76,7 @@ const Cloudflare = auto( const opts = { email: auth && auth.email, key: auth && auth.key, + token: auth && auth.token, }; withEnvProxy(opts); diff --git a/lib/Client.js b/lib/Client.js index 7b07daf..71855ea 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -38,10 +38,14 @@ module.exports = prototypal({ constructor: function constructor(options) { this.email = options.email; this.key = options.key; + this.token = options.token; this.getter = new Getter(options); }, request(requestMethod, requestPath, data, opts) { const uri = `https://api.cloudflare.com/client/v4/${requestPath}`; + const key = opts.auth.key || this.key; + const token = opts.auth.token || this.token; + const email = opts.auth.email || this.email; const options = { json: opts.json !== false, @@ -56,11 +60,13 @@ module.exports = prototypal({ }, }; - if (isUserServiceKey(opts.auth.key || this.key)) { - options.headers['X-Auth-User-Service-Key'] = opts.auth.key || this.key; - } else { - options.headers['X-Auth-Key'] = opts.auth.key || this.key; - options.headers['X-Auth-Email'] = opts.auth.email || this.email; + if (isUserServiceKey(key)) { + options.headers['X-Auth-User-Service-Key'] = key; + } else if (key) { + options.headers['X-Auth-Key'] = key; + options.headers['X-Auth-Email'] = email; + } else if (token) { + options.headers.Authorization = `Bearer ${token}`; } if (requestMethod === 'GET') { diff --git a/lib/method.js b/lib/method.js index b7e3b83..b49d3a8 100644 --- a/lib/method.js +++ b/lib/method.js @@ -9,7 +9,7 @@ const URLPattern = require('url-pattern'); -const options = ['key', 'email']; +const options = ['key', 'email', 'token']; const isPlainObject = function isPlainObject(x) { const prototype = Object.getPrototypeOf(x); diff --git a/test/Client.js b/test/Client.js index d150915..22dafce 100644 --- a/test/Client.js +++ b/test/Client.js @@ -179,6 +179,55 @@ describe('HTTP Client', () => { return res.then(resp => assert.deepEqual(resp, body)); }); + it('should support API Tokens', () => { + const getter = new FakeGetter(); + const token = 'vr29SNZEpswxdp'; + const body = { + hello: 'world', + }; + + const options = { + json: true, + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + query: { + name: 'world', + }, + }; + + td.when(getter.got(), {ignoreExtraArgs: true}).thenReject(); + td + .when( + getter.got( + 'https://api.cloudflare.com/client/v4/example/42', + td.matchers.contains(options) + ) + ) + .thenResolve({body}); + + const subject = new Client({ + token, + }); + + const res = subject.request( + 'GET', + 'example/42', + { + name: 'world', + }, + { + auth: {}, + headers: {}, + } + ); + + return res.then(resp => assert.deepEqual(resp, body)); + }); + it('should override authentication', () => { const getter = new FakeGetter(); const body = {