now passes tests for invalid account
This commit is contained in:
parent
d8c46652cf
commit
25f8b591db
15
index.js
15
index.js
|
@ -1,19 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
var leCore = require('letiny-core');
|
||||
var ACME = require('le-acme-core').ACME;
|
||||
|
||||
var LE = module.exports;
|
||||
LE.LE = LE;
|
||||
// in-process cache, shared between all instances
|
||||
var ipc = {};
|
||||
|
||||
LE.defaults = {
|
||||
productionServerUrl: leCore.productionServerUrl
|
||||
, stagingServerUrl: leCore.stagingServerUrl
|
||||
productionServerUrl: ACME.productionServerUrl
|
||||
, stagingServerUrl: ACME.stagingServerUrl
|
||||
|
||||
, rsaKeySize: leCore.rsaKeySize || 2048
|
||||
, challengeType: leCore.challengeType || 'http-01'
|
||||
, rsaKeySize: ACME.rsaKeySize || 2048
|
||||
, challengeType: ACME.challengeType || 'http-01'
|
||||
|
||||
, acmeChallengePrefix: leCore.acmeChallengePrefix
|
||||
, acmeChallengePrefix: ACME.acmeChallengePrefix
|
||||
};
|
||||
|
||||
// backwards compat
|
||||
|
@ -50,7 +51,7 @@ LE._undefine = function (le) {
|
|||
LE.create = function (le) {
|
||||
var PromiseA = require('bluebird');
|
||||
|
||||
le.acme = le.acme || leCore;
|
||||
le.acme = le.acme || ACME.create({ debug: le.debug });
|
||||
le.store = le.store || require('le-store-certbot').create({ debug: le.debug });
|
||||
le.challenger = le.challenger || require('le-store-certbot').create({ debug: le.debug });
|
||||
le.core = require('./lib/core');
|
||||
|
|
230
lib/core.js
230
lib/core.js
|
@ -36,77 +36,101 @@ module.exports.create = function (le) {
|
|||
//
|
||||
, accounts: {
|
||||
registerAsync: function (args) {
|
||||
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) {
|
||||
var err;
|
||||
|
||||
return le.acme.registerNewAccountAsync({
|
||||
email: args.email
|
||||
, newRegUrl: args._acmeUrls.newReg
|
||||
, agreeToTerms: function (tosUrl, agreeCb) {
|
||||
if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === le.agreeToTerms) {
|
||||
agreeCb(null, tosUrl);
|
||||
return;
|
||||
}
|
||||
if (!args.email || !args.agreeTos || (parseInt(args.rsaKeySize, 10) < 2048)) {
|
||||
err = new Error(
|
||||
"In order to register an account both 'email' and 'agreeTos' must be present"
|
||||
+ " and 'rsaKeySize' must be 2048 or greater."
|
||||
);
|
||||
err.code = 'E_ARGS';
|
||||
return PromiseA.reject(err);
|
||||
}
|
||||
|
||||
// args.email = email; // already there
|
||||
// args.domains = domains // already there
|
||||
args.tosUrl = tosUrl;
|
||||
le.agreeToTerms(args, agreeCb);
|
||||
}
|
||||
, accountKeypair: keypair
|
||||
return utils.testEmail(args.email).then(function () {
|
||||
|
||||
, debug: le.debug || args.debug
|
||||
}).then(function (body) {
|
||||
// TODO XXX use sha256 (the python client uses md5)
|
||||
// TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
|
||||
keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex');
|
||||
keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex');
|
||||
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) {
|
||||
// Note: the ACME urls are always fetched fresh on purpose
|
||||
// TODO is this the right place for this?
|
||||
return core.getAcmeUrlsAsync(args).then(function (urls) {
|
||||
args._acmeUrls = urls;
|
||||
|
||||
var accountId = keypair.publicKeyMd5;
|
||||
var regr = { body: body };
|
||||
var account = {};
|
||||
return le.acme.registerNewAccountAsync({
|
||||
email: args.email
|
||||
, newRegUrl: args._acmeUrls.newReg
|
||||
, agreeToTerms: function (tosUrl, agreeCb) {
|
||||
if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === le.agreeToTerms) {
|
||||
agreeCb(null, tosUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
args.accountId = accountId;
|
||||
// args.email = email; // already there
|
||||
// args.domains = domains // already there
|
||||
args.tosUrl = tosUrl;
|
||||
le.agreeToTerms(args, agreeCb);
|
||||
}
|
||||
, accountKeypair: keypair
|
||||
|
||||
account.keypair = keypair;
|
||||
account.regr = regr;
|
||||
account.accountId = accountId;
|
||||
account.id = accountId;
|
||||
, debug: le.debug || args.debug
|
||||
}).then(function (body) {
|
||||
// TODO XXX use sha256 (the python client uses md5)
|
||||
// TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
|
||||
keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex');
|
||||
keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex');
|
||||
|
||||
args.account = account;
|
||||
var accountId = keypair.publicKeyMd5;
|
||||
var regr = { body: body };
|
||||
var account = {};
|
||||
|
||||
return le.store.accounts.setAsync(args, account).then(function () {
|
||||
return account;
|
||||
args.accountId = accountId;
|
||||
|
||||
account.keypair = keypair;
|
||||
account.regr = regr;
|
||||
account.accountId = accountId;
|
||||
account.id = accountId;
|
||||
|
||||
args.account = account;
|
||||
|
||||
return le.store.accounts.setAsync(args, account).then(function () {
|
||||
return account;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// getOrCreateAcmeAccount
|
||||
|
||||
, getAsync: function (args) {
|
||||
return core.accounts.checkAsync(args).then(function (account) {
|
||||
if (account) {
|
||||
return le.store.accounts.checkAccount(args);
|
||||
} else {
|
||||
return core.accounts.registerAsync(args);
|
||||
}
|
||||
if (account) {
|
||||
return account;
|
||||
} else {
|
||||
return core.accounts.registerAsync(args);
|
||||
}
|
||||
});
|
||||
}
|
||||
, checkAsync: function (args) {
|
||||
return le.store.accounts.checkAccountId(args).then(function (accountId) {
|
||||
|
||||
if (!accountId) {
|
||||
, checkAsync: function (args) {
|
||||
var requiredArgs = ['accountId', 'email', 'domains', 'domain'];
|
||||
if (!requiredArgs.some(function (key) { return -1 !== Object.keys(args).indexOf(key) })) {
|
||||
return PromiseA.reject(new Error(
|
||||
"In order to register or retrieve an account one of '" + requiredArgs.join("', '") + "' must be present"
|
||||
));
|
||||
}
|
||||
|
||||
var copy = utils.merge(args, le);
|
||||
args = utils.tplCopy(copy);
|
||||
|
||||
return le.store.accounts.checkAsync(args).then(function (account) {
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
args.accountId = accountId;
|
||||
args.account = account;
|
||||
args.accountId = account.id;
|
||||
|
||||
// Note: the ACME urls are always fetched fresh on purpose
|
||||
return core.getAcmeUrlsAsync(args).then(function (urls) {
|
||||
args._acmeUrls = urls;
|
||||
|
||||
|
||||
// return le.store.accounts.checkAccountId(args).then(function (accountId) {
|
||||
return le.store.accounts.checkAsync(args);
|
||||
});
|
||||
return account;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -150,55 +174,61 @@ module.exports.create = function (le) {
|
|||
args.domainKeypair = domainKeypair;
|
||||
//args.registration = domainKey;
|
||||
|
||||
return le.acme.getCertificateAsync({
|
||||
debug: args.debug || le.debug
|
||||
// Note: the ACME urls are always fetched fresh on purpose
|
||||
// TODO is this the right place for this?
|
||||
return core.getAcmeUrlsAsync(args).then(function (urls) {
|
||||
args._acmeUrls = urls;
|
||||
|
||||
, newAuthzUrl: args._acmeUrls.newAuthz
|
||||
, newCertUrl: args._acmeUrls.newCert
|
||||
return le.acme.getCertificateAsync({
|
||||
debug: args.debug || le.debug
|
||||
|
||||
, accountKeypair: RSA.import(account.keypair)
|
||||
, domainKeypair: domainKeypair
|
||||
, domains: args.domains
|
||||
, challengeType: args.challengeType
|
||||
, newAuthzUrl: args._acmeUrls.newAuthz
|
||||
, newCertUrl: args._acmeUrls.newCert
|
||||
|
||||
//
|
||||
// IMPORTANT
|
||||
//
|
||||
// setChallenge and removeChallenge are handed defaults
|
||||
// instead of args because getChallenge does not have
|
||||
// access to args
|
||||
// (args is per-request, defaults is per instance)
|
||||
//
|
||||
, setChallenge: function (domain, key, value, done) {
|
||||
var copy = utils.merge({ domains: [domain] }, le);
|
||||
utils.tplCopy(copy);
|
||||
, accountKeypair: RSA.import(account.keypair)
|
||||
, domainKeypair: domainKeypair
|
||||
, domains: args.domains
|
||||
, challengeType: args.challengeType
|
||||
|
||||
//args.domains = [domain];
|
||||
args.domains = args.domains || [domain];
|
||||
//
|
||||
// IMPORTANT
|
||||
//
|
||||
// setChallenge and removeChallenge are handed defaults
|
||||
// instead of args because getChallenge does not have
|
||||
// access to args
|
||||
// (args is per-request, defaults is per instance)
|
||||
//
|
||||
, setChallenge: function (domain, key, value, done) {
|
||||
var copy = utils.merge({ domains: [domain] }, le);
|
||||
utils.tplCopy(copy);
|
||||
|
||||
if (5 !== le.challenger.set.length) {
|
||||
done(new Error("le.challenger.set receives the wrong number of arguments."
|
||||
+ " You must define setChallenge as function (opts, domain, key, val, cb) { }"));
|
||||
return;
|
||||
//args.domains = [domain];
|
||||
args.domains = args.domains || [domain];
|
||||
|
||||
if (5 !== le.challenger.set.length) {
|
||||
done(new Error("le.challenger.set receives the wrong number of arguments."
|
||||
+ " You must define setChallenge as function (opts, domain, key, val, cb) { }"));
|
||||
return;
|
||||
}
|
||||
|
||||
le.challenger.set(copy, domain, key, value, done);
|
||||
}
|
||||
, removeChallenge: function (domain, key, done) {
|
||||
var copy = utils.merge({ domains: [domain] }, le);
|
||||
utils.tplCopy(copy);
|
||||
|
||||
le.challenger.set(copy, domain, key, value, done);
|
||||
}
|
||||
, removeChallenge: function (domain, key, done) {
|
||||
var copy = utils.merge({ domains: [domain] }, le);
|
||||
utils.tplCopy(copy);
|
||||
if (4 !== le.challenger.remove.length) {
|
||||
done(new Error("le.challenger.remove receives the wrong number of arguments."
|
||||
+ " You must define removeChallenge as function (opts, domain, key, cb) { }"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (4 !== le.challenger.remove.length) {
|
||||
done(new Error("le.challenger.remove receives the wrong number of arguments."
|
||||
+ " You must define removeChallenge as function (opts, domain, key, cb) { }"));
|
||||
return;
|
||||
le.challenger.remove(copy, domain, key, done);
|
||||
}
|
||||
|
||||
le.challenger.remove(copy, domain, key, done);
|
||||
}
|
||||
}).then(utils.attachCertInfo);
|
||||
}).then(utils.attachCertInfo);
|
||||
});
|
||||
}).then(function (results) {
|
||||
// { cert, chain, fullchain, privkey }
|
||||
// { cert, chain, privkey }
|
||||
|
||||
args.pems = results;
|
||||
return le.store.certificates.setAsync(args).then(function () {
|
||||
|
@ -207,6 +237,10 @@ module.exports.create = function (le) {
|
|||
});
|
||||
});
|
||||
}
|
||||
, renewAsync: function (args) {
|
||||
// TODO fetch email address if not present
|
||||
return core.certificates.registerAsync(args);
|
||||
}
|
||||
, checkAsync: function (args) {
|
||||
var copy = utils.merge(args, le);
|
||||
utils.tplCopy(copy);
|
||||
|
@ -218,20 +252,20 @@ module.exports.create = function (le) {
|
|||
var copy = utils.merge(args, le);
|
||||
args = utils.tplCopy(copy);
|
||||
|
||||
if (args.duplicate) {
|
||||
// we're forcing a refresh via 'dupliate: true'
|
||||
return core.certificates.registerAsync(args);
|
||||
}
|
||||
|
||||
return core.certificates.checkAsync(args).then(function (certs) {
|
||||
if (!certs) {
|
||||
// There is no cert available
|
||||
return core.certificates.registerAsync(args);
|
||||
}
|
||||
|
||||
var renewableAt = certs.expiresAt - le.renewWithin;
|
||||
//var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
|
||||
//var renewable = (Date.now() - certs.issuedAt) > halfLife;
|
||||
|
||||
if (!certs || Date.now() >= renewableAt) {
|
||||
// There is no cert available
|
||||
// Or the cert is more than half-expired
|
||||
return core.certificates.registerAsync(args);
|
||||
if (args.duplicate || Date.now() >= renewableAt) {
|
||||
// The cert is more than half-expired
|
||||
// We're forcing a refresh via 'dupliate: true'
|
||||
return core.certificates.renewAsync(args);
|
||||
}
|
||||
|
||||
return PromiseA.reject(new Error(
|
||||
|
|
29
lib/utils.js
29
lib/utils.js
|
@ -4,6 +4,8 @@ var path = require('path');
|
|||
var homeRe = new RegExp("^~(\\/|\\\|\\" + path.sep + ")");
|
||||
var re = /^[a-zA-Z0-9\.\-]+$/;
|
||||
var punycode = require('punycode');
|
||||
var PromiseA = require('bluebird');
|
||||
var dns = PromiseA.promisifyAll(require('dns'));
|
||||
|
||||
module.exports.attachCertInfo = function (results) {
|
||||
var getCertInfo = require('./cert-info').getCertInfo;
|
||||
|
@ -33,7 +35,7 @@ module.exports.isValidDomain = function (domain) {
|
|||
|
||||
module.exports.merge = function (/*defaults, args*/) {
|
||||
var allDefaults = Array.prototype.slice.apply(arguments);
|
||||
var args = args.shift();
|
||||
var args = allDefaults.shift();
|
||||
var copy = {};
|
||||
|
||||
allDefaults.forEach(function (defaults) {
|
||||
|
@ -78,3 +80,28 @@ module.exports.tplCopy = function (copy) {
|
|||
|
||||
return copy;
|
||||
};
|
||||
|
||||
module.exports.testEmail = function (email) {
|
||||
var parts = (email||'').split('@');
|
||||
var err;
|
||||
|
||||
if (2 !== parts.length || !parts[0] || !parts[1]) {
|
||||
err = new Error("malformed email address '" + email + "'");
|
||||
err.code = 'E_EMAIL';
|
||||
return PromiseA.reject(err);
|
||||
}
|
||||
|
||||
return dns.resolveMxAsync(parts[1]).then(function (records) {
|
||||
// records only returns when there is data
|
||||
if (!records.length) {
|
||||
throw new Error("sanity check fail: success, but no MX records returned");
|
||||
}
|
||||
return email;
|
||||
}, function (err) {
|
||||
if ('ENODATA' === err.code) {
|
||||
err = new Error("no MX records found for '" + parts[1] + "'");
|
||||
err.code = 'E_EMAIL';
|
||||
return PromiseA.reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
'use strict';
|
||||
|
||||
var LE = require('../').LE;
|
||||
var le = LE.create({
|
||||
server: 'staging'
|
||||
, acme: require('le-acme-core').ACME.create()
|
||||
, store: require('le-store-certbot').create({
|
||||
configDir: '~/letsencrypt.test/etc/'
|
||||
})
|
||||
});
|
||||
|
||||
var testId = Math.round(Date.now() / 1000).toString();
|
||||
var fakeEmail = 'coolaj86+le.' + testId + '@example.com';
|
||||
var testEmail = 'coolaj86+le.' + testId + '@example.com';
|
||||
var testAccount;
|
||||
|
||||
var tests = [
|
||||
function () {
|
||||
return le.core.accounts.checkAsync({
|
||||
email: testEmail
|
||||
}).then(function (account) {
|
||||
if (account) {
|
||||
console.error(account);
|
||||
throw new Error("Test account should not exist.");
|
||||
}
|
||||
});
|
||||
}
|
||||
, function () {
|
||||
return le.core.accounts.registerAsync({
|
||||
email: testEmail
|
||||
, agreeTos: false
|
||||
, rsaKeySize: 2048
|
||||
}).then(function (/*account*/) {
|
||||
throw new Error("Should not register if 'agreeTos' is not truthy.");
|
||||
}, function (err) {
|
||||
if (err.code !== 'E_ARGS') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
, function () {
|
||||
return le.core.accounts.registerAsync({
|
||||
email: testEmail
|
||||
, agreeTos: true
|
||||
, rsaKeySize: 1024
|
||||
}).then(function (/*account*/) {
|
||||
throw new Error("Should not register if 'rsaKeySize' is less than 2048.");
|
||||
}, function (err) {
|
||||
if (err.code !== 'E_ARGS') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
, function () {
|
||||
return le.core.accounts.registerAsync({
|
||||
email: fakeEmail
|
||||
, agreeTos: true
|
||||
, rsaKeySize: 2048
|
||||
}).then(function (/*account*/) {
|
||||
// TODO test mx record
|
||||
throw new Error("Registration should NOT succeed with a bad email address.");
|
||||
}, function (err) {
|
||||
if (err.code !== 'E_EMAIL') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
, function () {
|
||||
throw new Error('NOT IMPLEMENTED');
|
||||
return le.core.accounts.registerAsync({
|
||||
email: 'coolaj86+le.' + testId + '@example.com'
|
||||
, agreeTos: true
|
||||
, rsaKeySize: 2048
|
||||
}).then(function (account) {
|
||||
testAccount = account;
|
||||
if (!account) {
|
||||
throw new Error("Registration should always return a new account.");
|
||||
}
|
||||
if (!account.email) {
|
||||
throw new Error("Registration should return the email.");
|
||||
}
|
||||
if (!account.id) {
|
||||
throw new Error("Registration should return the account id.");
|
||||
}
|
||||
});
|
||||
}
|
||||
, function () {
|
||||
return le.core.accounts.checkAsync({
|
||||
email: testAccount.email
|
||||
}).then(function (account) {
|
||||
if (!account) {
|
||||
throw new Error("Test account should exist when searched by email.");
|
||||
}
|
||||
});
|
||||
}
|
||||
, function () {
|
||||
return le.core.accounts.checkAsync({
|
||||
accountId: testAccount.id
|
||||
}).then(function (account) {
|
||||
if (!account) {
|
||||
throw new Error("Test account should exist when searched by account id.");
|
||||
}
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
function run() {
|
||||
var test = tests.shift();
|
||||
if (!test) {
|
||||
console.info('All tests passed');
|
||||
return;
|
||||
}
|
||||
|
||||
test().then(run);
|
||||
}
|
||||
|
||||
run();
|
Loading…
Reference in New Issue