diff --git a/.gitignore b/.gitignore index bf23d29..d9bed02 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules +.idea +.DS_Store diff --git a/README.md b/README.md index b0ff070..38a4080 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,9 @@ For **testing** and **development**, you can also inject the dependencies you wa 'use strict'; var ACME = require('le-acme-core').ACME.create({ - request: require('request') , RSA: require('rsa-compat').RSA }); -// now uses node `request` (could also use jQuery or Angular in the browser) ACME.getAcmeUrls(discoveryUrl, function (err, urls) { console.log(urls); }); diff --git a/lib/acme-client.js b/lib/acme-client.js index 5568742..0bdafae 100644 --- a/lib/acme-client.js +++ b/lib/acme-client.js @@ -11,7 +11,7 @@ module.exports.create = function (deps) { var NOOP = function () { }; var log = NOOP; - var request = deps.request || require('request'); + var acmeRequest = deps.acmeRequest; var RSA = deps.RSA; var generateSignature = RSA.signJws; @@ -30,7 +30,7 @@ module.exports.create = function (deps) { Acme.prototype.getNonce=function(url, cb) { var self=this; - request.head({ + acmeRequest.create().head({ url:url, }, function(err, res/*, body*/) { if (err) { @@ -73,7 +73,7 @@ module.exports.create = function (deps) { //process.exit(1); //return; - return request.post({ + return acmeRequest.create().post({ url: url , body: signed , encoding: null diff --git a/lib/get-acme-urls.js b/lib/get-acme-urls.js index ece0680..acb4276 100644 --- a/lib/get-acme-urls.js +++ b/lib/get-acme-urls.js @@ -6,7 +6,7 @@ 'use strict'; module.exports.create = function (deps) { - var request = deps.request; + var acmeRequest = deps.acmeRequest; var knownUrls = deps.LeCore.knownEndpoints; function getAcmeUrls(acmeDiscoveryUrl, cb) { @@ -15,7 +15,7 @@ module.exports.create = function (deps) { } // TODO check response header on request for cache time - return request({ + return acmeRequest.create()({ url: acmeDiscoveryUrl , encoding: 'utf8' }, function (err, resp) { diff --git a/lib/get-certificate.js b/lib/get-certificate.js index 0a80ad8..a78bd97 100644 --- a/lib/get-certificate.js +++ b/lib/get-certificate.js @@ -24,7 +24,7 @@ function certBufferToPem(cert) { } module.exports.create = function (deps) { - var request = deps.request; + var acmeRequest = deps.acmeRequest; var Acme = deps.Acme; var RSA = deps.RSA; @@ -193,7 +193,7 @@ module.exports.create = function (deps) { if (authz.status==='pending') { setTimeout(function() { - request({ + acmeRequest.create()({ method: 'GET' , url: state.authorizationUrl }, function(err, res, body) { @@ -278,7 +278,7 @@ module.exports.create = function (deps) { state.certificate=body; certUrl=res.headers.location; - request({ + acmeRequest.create()({ method: 'GET' , url: certUrl , encoding: null @@ -310,7 +310,7 @@ module.exports.create = function (deps) { function downloadIssuerCert(links) { log('Requesting issuer certificate...'); - request({ + acmeRequest.create()({ method: 'GET' , url: links.up , encoding: null diff --git a/lib/le-acme-request.js b/lib/le-acme-request.js new file mode 100644 index 0000000..d3c630c --- /dev/null +++ b/lib/le-acme-request.js @@ -0,0 +1,72 @@ +/*! + * le-acme-core + * Author: Kelly Johnson + * Copyright 2017 + * Apache-2.0 OR MIT (and hence also MPL 2.0) + */ +'use strict'; + +const request = require('request'); +const pkgJSON = require('../package.json'); +const version = pkgJSON.version; +const os = require('os'); + +const uaDefaults = { + pkg: `Daplie Greenlock/${version}` + , os: ` (${os.type()}; ${process.arch} ${os.platform()} ${os.release()})` + , node: ` Node.js/${process.version}` + , user: '' +} + +let currentUAProps; + +function getUaString() { + let userAgent = ''; + for (let key in currentUAProps) { + userAgent += currentUAProps[key]; + } + return userAgent.trim(); +} + +function getRequest() { + return request.defaults({ + headers: { + 'User-Agent': getUaString() + } + }); +} + +function resetUa() { + currentUAProps = {}; + for (let key in uaDefaults) { + currentUAProps[key] = uaDefaults[key]; + } +} + +function addUaString(string) { + currentUAProps.user += ` ${string}`; +} + +function omitUaProperties(opts) { + if (opts.all) { + currentUAProps = {}; + } else { + for (let key in opts) { + currentUAProps[key] = ''; + } + } +} + +// Set our UA to begin with +resetUa(); + +module.exports = { + create: function create() { + // get deps and modify here if need be + return getRequest(); + } + , addUaString: addUaString + , omitUaProperties: omitUaProperties + , resetUa: resetUa + , getUaString: getUaString +}; diff --git a/lib/register-new-account.js b/lib/register-new-account.js index 29b32ca..c8e15ba 100644 --- a/lib/register-new-account.js +++ b/lib/register-new-account.js @@ -8,7 +8,7 @@ 'use strict'; module.exports.create = function (deps) { var NOOP=function () {}, log=NOOP; - var request=deps.request; + var acmeRequest = deps.acmeRequest; var RSA = deps.RSA; var Acme = deps.Acme; @@ -55,7 +55,7 @@ module.exports.create = function (deps) { state.agreeTerms = agree; state.termsUrl=links['terms-of-service']; log(state.termsUrl); - request.get(state.termsUrl, getAgreement); + acmeRequest.create().get(state.termsUrl, getAgreement); }); } else { cb(null, null); diff --git a/node.js b/node.js index fe0fb70..36c09fd 100644 --- a/node.js +++ b/node.js @@ -24,10 +24,11 @@ function create(deps) { }); deps.RSA = deps.RSA || require('rsa-compat').RSA; - deps.request = deps.request || require('request'); + deps.acmeRequest = require('./lib/le-acme-request'); deps.Acme = require('./lib/acme-client').create(deps); deps.LeCore.Acme = deps.Acme; + deps.LeCore.acmeRequest = deps.acmeRequest; deps.LeCore.getAcmeUrls = require('./lib/get-acme-urls').create(deps); deps.LeCore.registerNewAccount = require('./lib/register-new-account').create(deps); deps.LeCore.getCertificate = require('./lib/get-certificate').create(deps); diff --git a/package.json b/package.json index 3bfc34f..7be1b84 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,11 @@ "dependencies": { "request": "^2.74.0", "rsa-compat": "^1.2.7" + }, + "devDependencies": { + "better-assert": "^1.0.2", + "chai": "^3.5.0", + "chai-string": "^1.3.0", + "request-debug": "^0.2.0" } } diff --git a/test/test-request.js b/test/test-request.js new file mode 100644 index 0000000..cb258ad --- /dev/null +++ b/test/test-request.js @@ -0,0 +1,74 @@ +/*! + * le-acme-core + * Author: Kelly Johnson + * Copyright 2017 + * Apache-2.0 OR MIT (and hence also MPL 2.0) + */ +'use strict'; + +const acmeRequest = require('../lib/le-acme-request'); +const debugRequest = require('request-debug'); +const chai = require('chai'); +chai.use(require('chai-string')); +const expect = chai.expect; + +const productId = 'Daplie Greenlock'; +const UA = 'User-Agent'; + +function checkRequest(req, done, tester) { + debugRequest(req, function dbg(type, data, r) { + if (type !== 'request') return; // Only interested in the request + expect(data.headers).to.have.property(UA); + let uaString = data.headers[UA]; + tester(uaString); + req.stopDebugging(); + done(); + }); + req('http://www.google.com', function (error, response, body) { + }); +} + +describe('le-acme-request', function () { + + beforeEach(function () { + acmeRequest.resetUa(); + }); + + it('should build User-Agent string', function () { + let uaString = acmeRequest.getUaString(); + expect(uaString).to.startsWith(productId); + }); + + it('should have proper User-Agent in request', function (done) { + let request = acmeRequest.create(); + checkRequest(request, done, function (uaString) { + expect(uaString).to.startsWith(productId); + }); + }); + + it('should add custom string to User Agent', function (done) { + let testStr = 'check it'; + acmeRequest.addUaString(testStr); + let request = acmeRequest.create(); + checkRequest(request, done, function (uaString) { + // Added space to ensure str was properly appended + expect(uaString).to.endsWith(` ${testStr}`); + }); + }); + + it('should remove all items from User Agent', function (done) { + acmeRequest.omitUaProperties({all: true}); + let request = acmeRequest.create(); + checkRequest(request, done, function (uaString) { + expect(uaString).to.be.empty; + }); + }); + + it('should remove one item from User Agent', function (done) { + acmeRequest.omitUaProperties({pkg: true}); + const request = acmeRequest.create(); + checkRequest(request, done, function (uaString) { + expect(uaString).to.not.have.string(productId); + }); + }); +}); diff --git a/test/test.js b/test/test.js index 3be443a..210f557 100644 --- a/test/test.js +++ b/test/test.js @@ -1,5 +1,5 @@ var forge=require('node-forge'), assert=require('better-assert'), fs=require('fs'), - letiny=require('../lib/client'), config=require('./config.json'), + letiny=require('../'), config=require('./config.json'), res, newReg='https://acme-staging.api.letsencrypt.org/acme/new-reg'; config.newReg=config.newReg || newReg;