now passes tests for invalid account

This commit is contained in:
AJ ONeal 2016-08-08 15:17:09 -04:00
parent d8c46652cf
commit 25f8b591db
4 changed files with 285 additions and 106 deletions

View File

@ -1,19 +1,20 @@
'use strict'; 'use strict';
var leCore = require('letiny-core'); var ACME = require('le-acme-core').ACME;
var LE = module.exports; var LE = module.exports;
LE.LE = LE;
// in-process cache, shared between all instances // in-process cache, shared between all instances
var ipc = {}; var ipc = {};
LE.defaults = { LE.defaults = {
productionServerUrl: leCore.productionServerUrl productionServerUrl: ACME.productionServerUrl
, stagingServerUrl: leCore.stagingServerUrl , stagingServerUrl: ACME.stagingServerUrl
, rsaKeySize: leCore.rsaKeySize || 2048 , rsaKeySize: ACME.rsaKeySize || 2048
, challengeType: leCore.challengeType || 'http-01' , challengeType: ACME.challengeType || 'http-01'
, acmeChallengePrefix: leCore.acmeChallengePrefix , acmeChallengePrefix: ACME.acmeChallengePrefix
}; };
// backwards compat // backwards compat
@ -50,7 +51,7 @@ LE._undefine = function (le) {
LE.create = function (le) { LE.create = function (le) {
var PromiseA = require('bluebird'); 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.store = le.store || require('le-store-certbot').create({ debug: le.debug });
le.challenger = le.challenger || 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'); le.core = require('./lib/core');

View File

@ -36,7 +36,24 @@ module.exports.create = function (le) {
// //
, accounts: { , accounts: {
registerAsync: function (args) { registerAsync: function (args) {
var err;
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);
}
return utils.testEmail(args.email).then(function () {
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) { 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;
return le.acme.registerNewAccountAsync({ return le.acme.registerNewAccountAsync({
email: args.email email: args.email
@ -79,34 +96,41 @@ module.exports.create = function (le) {
}); });
}); });
}); });
});
});
} }
// getOrCreateAcmeAccount
, getAsync: function (args) { , getAsync: function (args) {
return core.accounts.checkAsync(args).then(function (account) { return core.accounts.checkAsync(args).then(function (account) {
if (account) { if (account) {
return le.store.accounts.checkAccount(args); return account;
} else { } else {
return core.accounts.registerAsync(args); 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; return null;
} }
args.accountId = accountId; args.account = account;
args.accountId = account.id;
// Note: the ACME urls are always fetched fresh on purpose return account;
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);
});
}); });
} }
} }
@ -150,6 +174,11 @@ module.exports.create = function (le) {
args.domainKeypair = domainKeypair; args.domainKeypair = domainKeypair;
//args.registration = domainKey; //args.registration = domainKey;
// 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;
return le.acme.getCertificateAsync({ return le.acme.getCertificateAsync({
debug: args.debug || le.debug debug: args.debug || le.debug
@ -197,8 +226,9 @@ module.exports.create = function (le) {
le.challenger.remove(copy, domain, key, done); le.challenger.remove(copy, domain, key, done);
} }
}).then(utils.attachCertInfo); }).then(utils.attachCertInfo);
});
}).then(function (results) { }).then(function (results) {
// { cert, chain, fullchain, privkey } // { cert, chain, privkey }
args.pems = results; args.pems = results;
return le.store.certificates.setAsync(args).then(function () { 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) { , checkAsync: function (args) {
var copy = utils.merge(args, le); var copy = utils.merge(args, le);
utils.tplCopy(copy); utils.tplCopy(copy);
@ -218,20 +252,20 @@ module.exports.create = function (le) {
var copy = utils.merge(args, le); var copy = utils.merge(args, le);
args = utils.tplCopy(copy); args = utils.tplCopy(copy);
if (args.duplicate) { return core.certificates.checkAsync(args).then(function (certs) {
// we're forcing a refresh via 'dupliate: true' if (!certs) {
// There is no cert available
return core.certificates.registerAsync(args); return core.certificates.registerAsync(args);
} }
return core.certificates.checkAsync(args).then(function (certs) {
var renewableAt = certs.expiresAt - le.renewWithin; var renewableAt = certs.expiresAt - le.renewWithin;
//var halfLife = (certs.expiresAt - certs.issuedAt) / 2; //var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
//var renewable = (Date.now() - certs.issuedAt) > halfLife; //var renewable = (Date.now() - certs.issuedAt) > halfLife;
if (!certs || Date.now() >= renewableAt) { if (args.duplicate || Date.now() >= renewableAt) {
// There is no cert available // The cert is more than half-expired
// Or the cert is more than half-expired // We're forcing a refresh via 'dupliate: true'
return core.certificates.registerAsync(args); return core.certificates.renewAsync(args);
} }
return PromiseA.reject(new Error( return PromiseA.reject(new Error(

View File

@ -4,6 +4,8 @@ var path = require('path');
var homeRe = new RegExp("^~(\\/|\\\|\\" + path.sep + ")"); var homeRe = new RegExp("^~(\\/|\\\|\\" + path.sep + ")");
var re = /^[a-zA-Z0-9\.\-]+$/; var re = /^[a-zA-Z0-9\.\-]+$/;
var punycode = require('punycode'); var punycode = require('punycode');
var PromiseA = require('bluebird');
var dns = PromiseA.promisifyAll(require('dns'));
module.exports.attachCertInfo = function (results) { module.exports.attachCertInfo = function (results) {
var getCertInfo = require('./cert-info').getCertInfo; var getCertInfo = require('./cert-info').getCertInfo;
@ -33,7 +35,7 @@ module.exports.isValidDomain = function (domain) {
module.exports.merge = function (/*defaults, args*/) { module.exports.merge = function (/*defaults, args*/) {
var allDefaults = Array.prototype.slice.apply(arguments); var allDefaults = Array.prototype.slice.apply(arguments);
var args = args.shift(); var args = allDefaults.shift();
var copy = {}; var copy = {};
allDefaults.forEach(function (defaults) { allDefaults.forEach(function (defaults) {
@ -78,3 +80,28 @@ module.exports.tplCopy = function (copy) {
return 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);
}
});
};

117
tests/create-account.js Normal file
View File

@ -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();