diff --git a/index.js b/index.js index 3f2af93..be059fa 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,7 @@ var PromiseA = require('bluebird'); var leCore = require('letiny-core'); var merge = require('./lib/common').merge; -var tplHostname = require('./lib/common').tplHostname; +var tplCopy = require('./lib/common').tplCopy; var LE = module.exports; LE.productionServerUrl = leCore.productionServerUrl; @@ -58,7 +58,7 @@ LE.create = function (defaults, handlers, backend) { var getChallenge = require('./lib/default-handlers').getChallenge; var copy = merge(defaults, { domains: [hostname] }); - tplHostname(hostname, copy); + tplCopy(copy); defaultos.domains = [hostname]; if (3 === getChallenge.length) { @@ -158,9 +158,13 @@ LE.create = function (defaults, handlers, backend) { return; } - //console.log("[NLE]: begin registration"); + if (args.debug) { + console.log("[NLE]: begin registration"); + } return backend.registerAsync(copy).then(function (pems) { - //console.log("[NLE]: end registration"); + if (args.debug) { + console.log("[NLE]: end registration"); + } cb(null, pems); //return le.fetch(args, cb); }, cb); diff --git a/lib/accounts.js b/lib/accounts.js index 7942a34..83983c3 100644 --- a/lib/accounts.js +++ b/lib/accounts.js @@ -13,7 +13,7 @@ function createAccount(args, handlers) { // TODO support ECDSA // arg.rsaBitLength args.rsaExponent - return leCrypto.generateRsaKeypairAsync(args.rsaBitLength, args.rsaExponent).then(function (pems) { + return leCrypto.generateRsaKeypairAsync(args.rsaKeySize, 65537).then(function (pems) { /* pems = { privateKeyPem, privateKeyJwk, publicKeyPem, publicKeyMd5, publicKeySha256 } */ return LeCore.registerNewAccountAsync({ @@ -117,15 +117,69 @@ function getAccount(accountId, args, handlers) { }); } -function getAccountByEmail(/*args*/) { +function getAccountIdByEmail(args, handlers) { // If we read 10,000 account directories looking for // just one email address, that could get crazy. // We should have a folder per email and list // each account as a file in the folder // TODO - return PromiseA.resolve(null); + var email = args.email; + if ('string' !== typeof email) { + if (args.debug) { + console.log("[LE] No email given"); + } + return PromiseA.resolve(null); + } + return fs.readdirAsync(args.accountsDir).then(function (nodes) { + if (args.debug) { + console.log("[LE] arg.accountsDir success"); + } + + return PromiseA.all(nodes.map(function (node) { + return fs.readFileAsync(path.join(args.accountsDir, node, 'regr.json'), 'utf8').then(function (text) { + var regr = JSON.parse(text); + regr.__accountId = node; + + return regr; + }); + })).then(function (regrs) { + var accountId; + + /* + if (args.debug) { + console.log('read many regrs'); + console.log('regrs', regrs); + } + */ + + regrs.some(function (regr) { + return regr.body.contact.some(function (contact) { + var match = contact.toLowerCase() === 'mailto:' + email.toLowerCase(); + if (match) { + accountId = regr.__accountId; + return true; + } + }); + }); + + if (!accountId) { + return null; + } + + return accountId; + }); + }).then(function (accountId) { + return accountId; + }, function (err) { + if ('ENOENT' == err.code) { + // ignore error + return null; + } + + return PromiseA.reject(err); + }); } -module.exports.getAccountByEmail = getAccountByEmail; +module.exports.getAccountIdByEmail = getAccountIdByEmail; module.exports.getAccount = getAccount; module.exports.createAccount = createAccount; diff --git a/lib/common.js b/lib/common.js index 6615e83..e1f4a09 100644 --- a/lib/common.js +++ b/lib/common.js @@ -45,12 +45,23 @@ module.exports.merge = function merge(defaults, args) { return copy; }; -module.exports.tplHostname = function merge(hostname, copy) { +module.exports.tplCopy = function merge(copy) { var homedir = require('homedir')(); + var tpls = { + hostname: (copy.domains || [])[0] + , server: (copy.server || '').replace('https://', '').replace(/(\/)$/, '') + , conf: copy.configDir + , config: copy.configDir + }; + Object.keys(copy).forEach(function (key) { if ('string' === typeof copy[key]) { - copy[key] = copy[key].replace(':hostname', hostname).replace(':host', hostname); - copy[key] = copy[key].replace(homeRe, homedir + path.sep); + Object.keys(tpls).sort(function (a, b) { + return b.length - a.length; + }).forEach(function (tplname) { + copy[key] = copy[key].replace(':' + tplname, tpls[tplname]); + copy[key] = copy[key].replace(homeRe, homedir + path.sep); + }); } }); diff --git a/lib/core.js b/lib/core.js index b3e7248..9d47f40 100644 --- a/lib/core.js +++ b/lib/core.js @@ -10,7 +10,7 @@ var leCrypto = PromiseA.promisifyAll(LeCore.leCrypto); var Accounts = require('./accounts'); var merge = require('./common').merge; -var tplHostname = require('./common').tplHostname; +var tplCopy = require('./common').tplCopy; var fetchFromConfigLiveDir = require('./common').fetchFromDisk; var ipc = {}; // in-process cache @@ -34,7 +34,7 @@ function getAcmeUrls(args) { function getCertificateAsync(account, args, defaults, handlers) { - return leCrypto.generateRsaKeypairAsync(args.rsaKeySize, args.rsaExponent).then(function (domainKey) { + return leCrypto.generateRsaKeypairAsync(args.rsaKeySize, 65537).then(function (domainKey) { if (args.debug) { console.log("get certificate"); } @@ -58,7 +58,7 @@ function getCertificateAsync(account, args, defaults, handlers) { // , setChallenge: function (domain, key, value, done) { var copy = merge(defaults, { domains: [domain] }); - tplHostname(domain, copy); + tplCopy(copy); args.domains = [domain]; args.webrootPath = args.webrootPath; @@ -74,7 +74,7 @@ function getCertificateAsync(account, args, defaults, handlers) { } , removeChallenge: function (domain, key, done) { var copy = merge(defaults, { domains: [domain] }); - tplHostname(domain, copy); + tplCopy(copy); if (3 === handlers.removeChallenge.length) { handlers.removeChallenge(copy, key, done); @@ -204,7 +204,35 @@ function getCertificateAsync(account, args, defaults, handlers) { }); } -function registerWithAcme(args, defaults, handlers) { +function getOrCreateDomainCertificate(account, args, defaults, hanlers) { + return fetchFromConfigLiveDir(args).then(function (certs) { + // if nothing, register and save + // if something, check date (don't register unless 30+ days) + // if good, don't bother registering + // (but if we get to the point that we're actually calling + // this function, that shouldn't be the case, right?) + //console.log(certs); + if (!certs) { + // no certs, seems like a good time to get some + return getCertificateAsync(account, args, defaults, handlers); + } + else if (certs.issuedAt > (27 * 24 * 60 * 60 * 1000)) { + // cert is at least 27 days old we can renew that + return getCertificateAsync(account, args, defaults, handlers); + } + else if (args.duplicate) { + // YOLO! I be gettin' fresh certs 'erday! Yo! + return getCertificateAsync(account, args, defaults, handlers); + } + else { + console.warn('[WARN] Ignoring renewal attempt for certificate less than 27 days old. Use args.duplicate to force.'); + // We're happy with what we have + return certs; + } + }); +}; + +function getOrCreateAcmeAccount(args, defaults, handlers) { var pyconf = PromiseA.promisifyAll(require('pyconf')); var server = args.server; var acmeHostname = require('url').parse(server).hostname; @@ -220,18 +248,28 @@ function registerWithAcme(args, defaults, handlers) { return accountId; }, function (err) { if ("ENOENT" === err.code) { - return Accounts.getAccountByEmail(args, handlers); + if (args.debug) { + console.log("[LE] try email"); + } + return Accounts.getAccountIdByEmail(args, handlers); } return PromiseA.reject(err); }).then(function (accountId) { + // Note: the ACME urls are always fetched fresh on purpose return getAcmeUrls(args).then(function (urls) { args._acmeUrls = urls; if (accountId) { + if (args.debug) { + console.log('[LE] use account'); + } return Accounts.getAccount(accountId, args, handlers); } else { + if (args.debug) { + console.log('[LE] create account'); + } return Accounts.createAccount(args, handlers); } }); @@ -242,33 +280,6 @@ function registerWithAcme(args, defaults, handlers) { return; } */ - - //console.log(account); - return fetchFromConfigLiveDir(args).then(function (certs) { - // if nothing, register and save - // if something, check date (don't register unless 30+ days) - // if good, don't bother registering - // (but if we get to the point that we're actually calling - // this function, that shouldn't be the case, right?) - //console.log(certs); - if (!certs) { - // no certs, seems like a good time to get some - return getCertificateAsync(account, args, defaults, handlers); - } - else if (certs.issuedAt > (27 * 24 * 60 * 60 * 1000)) { - // cert is at least 27 days old we can renew that - return getCertificateAsync(account, args, defaults, handlers); - } - else if (args.duplicate) { - // YOLO! I be gettin' fresh certs 'erday! Yo! - return getCertificateAsync(account, args, defaults, handlers); - } - else { - console.warn('[WARN] Ignoring renewal attempt for certificate less than 27 days old. Use args.duplicate to force.'); - // We're happy with what we have - return certs; - } - }); }); /* return fs.readdirAsync(accountsDir, function (nodes) { @@ -293,16 +304,18 @@ module.exports.create = function (defaults, handlers) { // So :config/accounts/:server/directory is *incorrect*, but the following *is* correct: args.accountsDir = args.accountsDir || ':config/accounts/:server'; copy = merge(args, defaults); - tplHostname(args.domains[0], copy); + tplCopy(copy); if (args.debug) { console.log('[LE DEBUG] reg domains', args.domains); } - return registerWithAcme(copy, defaults, handlers); + return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) { + return getOrCreateDomainCertificate(account, copy, defaults, handlers); + }); } , fetchAsync: function (args) { var copy = merge(args, defaults); - tplHostname(args.domains[0], copy); + tplCopy(copy); if (args.debug) { console.log('[LE DEBUG] fetch domains', copy);