Compare commits
33 Commits
Author | SHA1 | Date |
---|---|---|
AJ ONeal | 2d7a117197 | |
AJ ONeal | 3c2778a67e | |
AJ ONeal | f4c1aa6906 | |
AJ ONeal | 974b3707c1 | |
AJ ONeal | 06c78a6a10 | |
CaptEmulation | 5aa65df228 | |
CaptEmulation | c58785f6e3 | |
AJ ONeal | 766eaa3eed | |
AJ ONeal | 2ac41d914a | |
AJ ONeal | 359755901d | |
AJ ONeal | 7f082d728b | |
AJ ONeal | f4ccef647b | |
AJ ONeal | 10764c4f56 | |
Jonathan Otto | 73f6cd5ab2 | |
AJ ONeal | a52e2aeb8e | |
AJ ONeal | 8e23160ab2 | |
AJ ONeal | fd3de76a9c | |
AJ ONeal | abc665fa81 | |
AJ ONeal | 434bde67dc | |
AJ ONeal | e937c74d76 | |
AJ ONeal | b5f44afec6 | |
AJ ONeal | c7d2806fe7 | |
AJ ONeal | b4210547a8 | |
AJ ONeal | d657afbac9 | |
AJ ONeal | 43c830482c | |
AJ ONeal | 3083130ccc | |
AJ ONeal | e120d2dcc2 | |
AJ ONeal | f8f7a75c99 | |
AJ ONeal | dfa5f79e0d | |
AJ ONeal | 5af14f1147 | |
AJ ONeal | bbf5168fb7 | |
AJ ONeal | 5a5848614b | |
AJ ONeal | ee294fce8c |
|
@ -0,0 +1,37 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
jspm_packages
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Daplie, Inc
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
101
README.md
101
README.md
|
@ -1,99 +1,8 @@
|
||||||
# le-store-SPEC
|
# See [`greenlock-store-test`](https://git.rootprojects.org/root/greenlock-store-test.js)
|
||||||
|
|
||||||
The reference implementation, specification, template, and tests for creating an le-store- strategy.
|
That's the test.
|
||||||
|
|
||||||
The reference implementation is completely in-memory.
|
## Reference implementations
|
||||||
|
|
||||||
See [Help Wanted: Database Plugins (for saving certs)](https://github.com/Daplie/node-letsencrypt/issues/39)
|
* [`greenlock-store-fs`](https://git.rootprojects.org/root/greenlock-store-fs.js)
|
||||||
|
* [`greenlock-store-sequelize`](https://git.rootprojects.org/root/greenlock-store-sequelize.js)
|
||||||
How to create a custom strategy
|
|
||||||
===============================
|
|
||||||
|
|
||||||
READ THIS README:
|
|
||||||
Believe it or not, most of your answers are either right here
|
|
||||||
or in the comments in the sample code in `index.js`.
|
|
||||||
|
|
||||||
Now, let's say there's some new database AwesomeDB that
|
|
||||||
we want to make a plugin for, here's how we'd start:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# First create you repo on github or wherever
|
|
||||||
# Then clone it
|
|
||||||
git clone git@github.com:AwesomeDB/le-store-awesome.git
|
|
||||||
|
|
||||||
pushd le-store-awesome
|
|
||||||
|
|
||||||
# IMPORTANT: we pull in the 'template' branch, which has the skeleton code
|
|
||||||
git pull https://github.com/Daplie/le-store-SPEC.git template
|
|
||||||
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, if you already have some code and just need to merge in the tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git pull https://github.com/Daplie/le-store-SPEC.git tests
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, Just run the tests
|
|
||||||
|
|
||||||
```
|
|
||||||
node tests/basic.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: you should not modify the tests that come from the tests branch,
|
|
||||||
but rather create separate files for your own tests.
|
|
||||||
|
|
||||||
API
|
|
||||||
===
|
|
||||||
|
|
||||||
```
|
|
||||||
* getOptions()
|
|
||||||
* accounts.
|
|
||||||
* checkKeypair(opts, cb)
|
|
||||||
* setKeypair(opts, keypair, cb)
|
|
||||||
* check(opts, cb)
|
|
||||||
* set(opts, reg, cb)
|
|
||||||
* certificates.
|
|
||||||
* checkKeypair(opts, cb)
|
|
||||||
* setKeypair(opts, keypair, cb)
|
|
||||||
* check(opts, cb)
|
|
||||||
* set(opts, certs, cb)
|
|
||||||
```
|
|
||||||
|
|
||||||
Keypairs
|
|
||||||
--------
|
|
||||||
|
|
||||||
For convenience, the keypair object will always contain **both** PEM and JWK
|
|
||||||
versions of the private and/or public keys when being passed to the `*Keypair` functions.
|
|
||||||
|
|
||||||
**set**
|
|
||||||
|
|
||||||
`setKeypair` will always be called with `email` and **all three** forms of the keypair:
|
|
||||||
`privateKeyPem`, `publicKeyPem`, and `privateKeyJwk`. It's easy to generate `publicKeyJwk`
|
|
||||||
from `privateKeyJwk` because it is just a copy of the public fields `e` and `n`.
|
|
||||||
|
|
||||||
```
|
|
||||||
// keypair looks like this
|
|
||||||
{ privateKeyPem: '...'
|
|
||||||
, publicKeyPem: '...'
|
|
||||||
, privateKeyJwk: { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**check**
|
|
||||||
|
|
||||||
`checkKeypair` may be called with any of `email`, `accountId`, and `keypair` - which will
|
|
||||||
contain only `publicKeyPem` and `publicKeyJwk`.
|
|
||||||
|
|
||||||
```
|
|
||||||
// opts looks like this
|
|
||||||
{
|
|
||||||
email: '...@...'
|
|
||||||
, accountId: '...'
|
|
||||||
, keypair: {
|
|
||||||
publicKeyPem: '...'
|
|
||||||
, publicKeyJwk: { ... }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports.create = function (options) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var defaults = {};
|
||||||
|
var memDb = {
|
||||||
|
accountKeypairs: {}
|
||||||
|
, certificateKeypairs: {}
|
||||||
|
, accountIndices: {}
|
||||||
|
, certIndices: {}
|
||||||
|
, certificates: {}
|
||||||
|
, accounts: {}
|
||||||
|
, accountCerts: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var accounts = {
|
||||||
|
// Accounts
|
||||||
|
setKeypair: function (opts, keypair, cb) {
|
||||||
|
// opts.email // non-optional
|
||||||
|
// opts.keypair // non-optional
|
||||||
|
|
||||||
|
if (!opts.email) {
|
||||||
|
cb(new Error("MUST use email when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keypair.privateKeyJwk) {
|
||||||
|
cb(new Error("MUST use privateKeyJwk when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!keypair.privateKeyPem) {
|
||||||
|
cb(new Error("MUST use privateKeyPem when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!keypair.publicKeyPem) {
|
||||||
|
cb(new Error("MUST use publicKeyPem when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountId = crypto.createHash('sha256').update(keypair.publicKeyPem).digest('hex');
|
||||||
|
|
||||||
|
memDb.accountIndices[accountId] = accountId;
|
||||||
|
memDb.accountIndices[opts.email] = accountId;
|
||||||
|
memDb.accountKeypairs[accountId] = keypair;
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
id: accountId
|
||||||
|
// TODO nix accountId
|
||||||
|
, accountId: accountId
|
||||||
|
, email: opts.email
|
||||||
|
, keypair: keypair
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
cb(null, memDb.accountKeypairs[accountId]);
|
||||||
|
}
|
||||||
|
// Accounts
|
||||||
|
, checkKeypair: function (opts, cb) {
|
||||||
|
// opts.email // optional
|
||||||
|
// opts.accountId // optional
|
||||||
|
|
||||||
|
var keypair = opts.keypair || {};
|
||||||
|
var index;
|
||||||
|
|
||||||
|
if (keypair.publicKeyPem) {
|
||||||
|
index = crypto.createHash('sha256').update(keypair.publicKeyPem).digest('hex');
|
||||||
|
index = memDb.accountIndices[index];
|
||||||
|
}
|
||||||
|
else if (keypair.publicKeyJwk) {
|
||||||
|
// TODO RSA.exportPublicPem(keypair);
|
||||||
|
cb(new Error("id from publicKeyJwk not yet implemented"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (opts.email) {
|
||||||
|
index = memDb.accountIndices[opts.email];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cb(new Error("MUST supply email or keypair.publicKeyPem or keypair.publicKeyJwk"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, memDb.accountKeypairs[index] || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Accounts
|
||||||
|
, set: function (opts, reg, cb) {
|
||||||
|
// opts.email
|
||||||
|
// reg.keypair
|
||||||
|
// reg.receipt // response from acme server
|
||||||
|
|
||||||
|
var keypair = reg.keypair || opts.keypair || {};
|
||||||
|
var accountId;
|
||||||
|
var index;
|
||||||
|
|
||||||
|
if (keypair.publicKeyPem) {
|
||||||
|
index = crypto.createHash('sha256').update(keypair.publicKeyPem).digest('hex');
|
||||||
|
index = memDb.accountIndices[index];
|
||||||
|
}
|
||||||
|
else if (keypair.publicKeyJwk) {
|
||||||
|
// TODO RSA.exportPublicPem(keypair);
|
||||||
|
cb(new Error("id from publicKeyJwk not yet implemented"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (opts.email) {
|
||||||
|
index = memDb.accountIndices[opts.email];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cb(new Error("MUST supply email or keypair.publicKeyPem or keypair.publicKeyJwk"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountId = memDb.accountIndices[index];
|
||||||
|
if (!accountId) {
|
||||||
|
cb(new Error("keypair was not previously set with email and keypair.publicKeyPem"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memDb.accounts[accountId] = {
|
||||||
|
id: accountId
|
||||||
|
// TODO nix accountId
|
||||||
|
, accountId: accountId
|
||||||
|
, email: opts.email
|
||||||
|
, keypair: keypair
|
||||||
|
, agreeTos: opts.agreeTos || reg.agreeTos
|
||||||
|
//, receipt: reg.receipt || opts.receipt
|
||||||
|
};
|
||||||
|
Object.keys(reg).forEach(function (key) {
|
||||||
|
memDb.accounts[accountId][key] = reg[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cb(null, memDb.accounts[accountId]);
|
||||||
|
}
|
||||||
|
// Accounts
|
||||||
|
, check: function (opts, cb) {
|
||||||
|
// opts.email // optional
|
||||||
|
// opts.accountId // optional
|
||||||
|
// opts.domains // optional
|
||||||
|
|
||||||
|
var keypair = opts.keypair || {};
|
||||||
|
var index;
|
||||||
|
var accountId;
|
||||||
|
var account;
|
||||||
|
|
||||||
|
if (opts.accountId) {
|
||||||
|
index = memDb.accountIndices[opts.accountId];
|
||||||
|
}
|
||||||
|
else if (keypair.publicKeyPem) {
|
||||||
|
index = crypto.createHash('sha256').update(keypair.publicKeyPem).digest('hex');
|
||||||
|
index = memDb.accountIndices[index];
|
||||||
|
}
|
||||||
|
else if (keypair.publicKeyJwk) {
|
||||||
|
// TODO RSA.exportPublicPem(keypair);
|
||||||
|
cb(new Error("id from publicKeyJwk not yet implemented"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (opts.email) {
|
||||||
|
index = memDb.accountIndices[opts.email];
|
||||||
|
}
|
||||||
|
else if (opts.domains && opts.domains[0]) {
|
||||||
|
index = memDb.accountIndices[opts.domains[0]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(opts);
|
||||||
|
cb(new Error("MUST supply email or keypair.publicKeyPem or keypair.publicKeyJwk"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountId = memDb.accountIndices[index];
|
||||||
|
if (!accountId) {
|
||||||
|
cb(null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
account = JSON.parse(JSON.stringify(memDb.accounts[accountId] || null));
|
||||||
|
account.keypair = memDb.accountKeypairs[accountId] || null;
|
||||||
|
|
||||||
|
cb(null, account);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var certificates = {
|
||||||
|
// Certificates
|
||||||
|
setKeypair: function (opts, keypair, cb) {
|
||||||
|
// opts.domains
|
||||||
|
|
||||||
|
if (!opts.domains || !opts.domains.length) {
|
||||||
|
cb(new Error("MUST use domains when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!opts.email) {
|
||||||
|
cb(new Error("MUST use email when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!opts.accountId) {
|
||||||
|
cb(new Error("MUST use accountId when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!keypair.privateKeyJwk) {
|
||||||
|
cb(new Error("MUST use privateKeyJwk when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!keypair.privateKeyPem) {
|
||||||
|
cb(new Error("MUST use privateKeyPem when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!keypair.publicKeyPem) {
|
||||||
|
cb(new Error("MUST use publicKeyPem when setting Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var subject = opts.domains[0];
|
||||||
|
|
||||||
|
opts.domains.forEach(function (domain) {
|
||||||
|
memDb.certIndices[domain] = subject;
|
||||||
|
});
|
||||||
|
|
||||||
|
memDb.certKeypairs[subject] = keypair;
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
subject: subject
|
||||||
|
, keypair: keypair
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
cb(null, memDb.certKeypairs[subject]);
|
||||||
|
}
|
||||||
|
// Certificates
|
||||||
|
, checkKeypair: function (opts, cb) {
|
||||||
|
// opts.domains
|
||||||
|
if (!opts.domains || !opts.domains.length) {
|
||||||
|
cb(new Error("MUST use domains when checking Keypair"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var domain = opts.domains[0];
|
||||||
|
var subject = memDb.certIndices[domain];
|
||||||
|
|
||||||
|
cb(null, memDb.certKeypairs[subject]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Certificates
|
||||||
|
, set: function (opts, cb) {
|
||||||
|
// opts.domains
|
||||||
|
// opts.email // optional
|
||||||
|
// opts.accountId // optional
|
||||||
|
|
||||||
|
// opts.certs.privkey
|
||||||
|
// opts.certs.cert
|
||||||
|
// opts.certs.chain
|
||||||
|
|
||||||
|
var index;
|
||||||
|
var accountId;
|
||||||
|
var account;
|
||||||
|
var certs = opts.certs;
|
||||||
|
var subject = certs.subject || opts.domains[0];
|
||||||
|
var altnames = certs.altnames || opts.domains;
|
||||||
|
var accountCerts;
|
||||||
|
|
||||||
|
if (opts.accountId) {
|
||||||
|
index = opts.accountId;
|
||||||
|
}
|
||||||
|
else if (opts.email) {
|
||||||
|
index = opts.email;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cb(new Error("MUST supply email or accountId"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountId = memDb.accountIndices[index];
|
||||||
|
account = memDb.accounts[accountId];
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
cb(new Error("account must exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
accountId = memDb.accountIndices[index];
|
||||||
|
if (!accountId) {
|
||||||
|
cb(new Error("keypair was not previously set with email and keypair.publicKeyPem"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memDb.certIndices[subject] = subject;
|
||||||
|
altnames.forEach(function (altname) {
|
||||||
|
memDb.certIndices[altname] = subject;
|
||||||
|
});
|
||||||
|
|
||||||
|
accountCerts = memDb.accountCerts[accountId] || {};
|
||||||
|
accountCerts[subject] = subject;
|
||||||
|
memDb.accountCerts[accountId] = accountCerts;
|
||||||
|
|
||||||
|
memDb.certificates[subject] = certs;
|
||||||
|
|
||||||
|
// SAVE to the database, index the email address, the accountId, and alias the domains
|
||||||
|
cb(null, certs);
|
||||||
|
}
|
||||||
|
// Certificates
|
||||||
|
, check: function (opts, cb) {
|
||||||
|
// You will be provided one of these (which should be tried in this order)
|
||||||
|
// opts.domains
|
||||||
|
// opts.email // optional
|
||||||
|
// opts.accountId // optional
|
||||||
|
var subject;
|
||||||
|
var subjects;
|
||||||
|
var accountId;
|
||||||
|
|
||||||
|
if (opts.domains) {
|
||||||
|
subject = memDb.certIndices[opts.domains[0]];
|
||||||
|
cb(null, memDb.certificates[subject]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.accountId) {
|
||||||
|
accountId = memDb.accountIndices[opts.accountId];
|
||||||
|
}
|
||||||
|
else if (opts.email) {
|
||||||
|
accountId = memDb.accountIndices[opts.email];
|
||||||
|
}
|
||||||
|
|
||||||
|
subjects = memDb.accountCerts[accountId] || [];
|
||||||
|
cb(null, subjects.map(function (subject) {
|
||||||
|
subject = memDb.certIndices[subject];
|
||||||
|
return memDb.certificates[subject] || null ;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
getOptions: function () {
|
||||||
|
Object.keys(defaults).forEach(function (key) {
|
||||||
|
if ('undefined' === typeof options[key]) {
|
||||||
|
options[key] = defaults[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// merge options with default settings and then return them
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
, accounts: accounts
|
||||||
|
, certificates: certificates
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,347 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird');
|
||||||
|
var leStore = PromiseA.promisifyAll(require('../').create({
|
||||||
|
debug: true
|
||||||
|
}));
|
||||||
|
leStore.accounts = PromiseA.promisifyAll(leStore.accounts);
|
||||||
|
leStore.certificates = PromiseA.promisifyAll(leStore.certificates);
|
||||||
|
|
||||||
|
// fixtures
|
||||||
|
var doesntExist = {
|
||||||
|
email: 'e@gmail.co'
|
||||||
|
, accountId: 'eee'
|
||||||
|
};
|
||||||
|
var goodGuy = {
|
||||||
|
email: 'goodguy@gmail.com'
|
||||||
|
, keypair: {
|
||||||
|
privateKeyPem: 'PRIVKEY.PEM', privateKeyJwk: { e: 'EXPO', n: 'MODULO' }
|
||||||
|
, publicKeyPem: 'PUBKEY.PEM'/*, publicKeyJwk: would be reduntdant */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var tests = [
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// SANITY CHECKS
|
||||||
|
//
|
||||||
|
|
||||||
|
// SANITY test that an unregistered email returns no results
|
||||||
|
function () {
|
||||||
|
return leStore.accounts.checkKeypairAsync({
|
||||||
|
email: doesntExist.email
|
||||||
|
}).then(function (keypair) {
|
||||||
|
if (null !== keypair) {
|
||||||
|
throw new Error("Should return `null` when keypair does not exist by `email`.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// SANITY test that an unregistered account id returns no results
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.checkAsync({
|
||||||
|
accountId: doesntExist.accountId
|
||||||
|
}).then(function (account) {
|
||||||
|
if (null !== account) {
|
||||||
|
throw new Error("Should return `null` when account does not exist by `accountId`.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Creating Account Keypairs
|
||||||
|
//
|
||||||
|
|
||||||
|
// Register a private key to an email
|
||||||
|
// and make sure agreeTos remains falsey
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.setKeypairAsync(goodGuy, goodGuy.keypair);
|
||||||
|
}
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.checkKeypairAsync({
|
||||||
|
email: goodGuy.email
|
||||||
|
}).then(function (keypair) {
|
||||||
|
|
||||||
|
if (!keypair) {
|
||||||
|
throw new Error("should return saved keypair");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goodGuy.keypair.privateKeyPem !== keypair.privateKeyPem) {
|
||||||
|
if (keypair.privateKeyJwk) {
|
||||||
|
throw new Error("Error in test itself (not your fault). TODO: implement checking privateKeyJwk.");
|
||||||
|
}
|
||||||
|
throw new Error("agreeTos should return false or null because it was not set.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Creating Accounts
|
||||||
|
//
|
||||||
|
|
||||||
|
// create a new account
|
||||||
|
, function () {
|
||||||
|
var account = {
|
||||||
|
agreeTos: true
|
||||||
|
, keypair: goodGuy.keypair
|
||||||
|
, receipt: { foo: 'bar' }
|
||||||
|
};
|
||||||
|
return leStore.accounts.setAsync(goodGuy, account).then(function (account) {
|
||||||
|
if (!account || !account.id || !account.email) {
|
||||||
|
throw new Error('accounts.set should return the object with its new `id` attached');
|
||||||
|
}
|
||||||
|
|
||||||
|
goodGuy.accountId = account.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get by account id
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.checkAsync({
|
||||||
|
accountId: goodGuy.accountId
|
||||||
|
}).then(function (account) {
|
||||||
|
if (!account) {
|
||||||
|
throw new Error("Did not find account.");
|
||||||
|
}
|
||||||
|
else if (!account.keypair) {
|
||||||
|
throw new Error("Account did not have a keypair.");
|
||||||
|
}
|
||||||
|
else if (goodGuy.keypair.privateKeyPem !== account.keypair.privateKeyPem) {
|
||||||
|
if (account.keypair.privateKeyJwk) {
|
||||||
|
throw new Error("Error in test itself (not your fault). TODO: implement checking privateKeyJwk.");
|
||||||
|
}
|
||||||
|
throw new Error("agreeTos should return false or null because it was not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.email) {
|
||||||
|
throw new Error("should have returned email");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.agreeTos) {
|
||||||
|
throw new Error("should have returned agreeTos");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.receipt) {
|
||||||
|
throw new Error("should have returned receipt");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// get by email
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.checkAsync({
|
||||||
|
email: goodGuy.email
|
||||||
|
}).then(function (account) {
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new Error("should have returned account for " + goodGuy.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.keypair) {
|
||||||
|
throw new Error("should have returned account.keypair for " + goodGuy.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goodGuy.keypair.privateKeyPem !== account.keypair.privateKeyPem) {
|
||||||
|
if (account.keypair.privateKeyJwk) {
|
||||||
|
throw new Error("Error in test itself (not your fault). TODO: implement checking privateKeyJwk.");
|
||||||
|
}
|
||||||
|
throw new Error("agreeTos should return false or null because it was not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.email) {
|
||||||
|
throw new Error("should have returned email");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.agreeTos) {
|
||||||
|
throw new Error("should have returned agreeTos");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.receipt) {
|
||||||
|
throw new Error("should have returned receipt");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that id and accountId are ignored
|
||||||
|
// and that arbitrary keys are stored
|
||||||
|
, function () {
|
||||||
|
var rnd = require('crypto').randomBytes(8).toString('hex');
|
||||||
|
var opts = {
|
||||||
|
accountId: '_account_id'
|
||||||
|
, id: '__account_id'
|
||||||
|
, email: 'john.doe@gmail.com'
|
||||||
|
, agreeTos: 'TOS_URL'
|
||||||
|
};
|
||||||
|
var account = {
|
||||||
|
keypair: { privateKeyJwk: {}, privateKeyPem: 'PEM2', publicKeyPem: 'PUBPEM2' }
|
||||||
|
, receipt: {}
|
||||||
|
};
|
||||||
|
account[rnd] = rnd;
|
||||||
|
return leStore.accounts.setKeypairAsync(opts, account.keypair).then(function () {
|
||||||
|
return leStore.accounts.setAsync(opts, account).then(function (account) {
|
||||||
|
if ('_account_id' === account.id || '__account_id' === account.id) {
|
||||||
|
throw new Error("Should create `id` deterministically from email or public key, not the given `accountId` or `id`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('john.doe@gmail.com' !== account.email) {
|
||||||
|
throw new Error("Should return the same email that was stored.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('TOS_URL' !== account.agreeTos) {
|
||||||
|
throw new Error("Should return the same string for the tosUrl in agreeTos as was stored.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('PEM2' !== account.keypair.privateKeyPem) {
|
||||||
|
throw new Error("Should return the same privateKey that was stored.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rnd !== account[rnd]) {
|
||||||
|
throw new Error("Should save and restore arbitrary keys.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// test lots of stuff
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.checkAsync({
|
||||||
|
accountId: goodGuy.accountId
|
||||||
|
}).then(function (account) {
|
||||||
|
if (!account
|
||||||
|
|| !account.agreeTos
|
||||||
|
|| account.email !== goodGuy.email
|
||||||
|
|| goodGuy.keypair.privateKeyPem !== account.keypair.privateKeyPem
|
||||||
|
) {
|
||||||
|
throw new Error("Should return the same account that was saved when retrieved using `accountId`.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, function () {
|
||||||
|
return leStore.accounts.checkAsync({
|
||||||
|
email: goodGuy.email
|
||||||
|
}).then(function (account) {
|
||||||
|
if (!account
|
||||||
|
|| !account.agreeTos
|
||||||
|
|| account.email !== goodGuy.email
|
||||||
|
|| goodGuy.keypair.privateKeyPem !== account.keypair.privateKeyPem
|
||||||
|
) {
|
||||||
|
throw new Error("Should return the same account that was saved when retrieved using `accountId`.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Save a cert
|
||||||
|
//
|
||||||
|
, function () {
|
||||||
|
var certOpts = {
|
||||||
|
domains: [ 'example.com', 'www.example.com', 'foo.net', 'bar.foo.net' ]
|
||||||
|
, email: goodGuy.email
|
||||||
|
, certs: {
|
||||||
|
cert: 'CERT_A.PEM'
|
||||||
|
, privkey: 'PRIVKEY_A.PEM'
|
||||||
|
, chain: 'CHAIN_A.PEM'
|
||||||
|
// TODO issuedAt, expiresAt?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return leStore.certificates.setAsync(certOpts);
|
||||||
|
}
|
||||||
|
// and another
|
||||||
|
, function () {
|
||||||
|
var certOpts = {
|
||||||
|
domains: [ 'foo.com', 'www.foo.com', 'baz.net', 'bar.baz.net' ]
|
||||||
|
, accountId: goodGuy.accountId
|
||||||
|
, certs: {
|
||||||
|
cert: 'CERT_B.PEM'
|
||||||
|
, privkey: 'PRIVKEY_B.PEM'
|
||||||
|
, chain: 'CHAIN_B.PEM'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return leStore.certificates.setAsync(certOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic test (set by email)
|
||||||
|
, function () {
|
||||||
|
var certOpts = {
|
||||||
|
domains: [ 'example.com' ]
|
||||||
|
};
|
||||||
|
return leStore.certificates.checkAsync(certOpts).then(function (certs) {
|
||||||
|
if (!certs || certs.privkey !== 'PRIVKEY_A.PEM') {
|
||||||
|
throw new Error("should have correct certs for example.com (set by email)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// basic test (set by accountId)
|
||||||
|
, function () {
|
||||||
|
var certOpts = {
|
||||||
|
domains: [ 'example.com' ]
|
||||||
|
};
|
||||||
|
return leStore.certificates.checkAsync(certOpts).then(function (certs) {
|
||||||
|
if (!certs || certs.privkey !== 'PRIVKEY_A.PEM') {
|
||||||
|
throw new Error("should have correct certs for example.com (set by email)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// altnames test
|
||||||
|
, function () {
|
||||||
|
var certOpts = {
|
||||||
|
domains: [ 'bar.foo.net' ]
|
||||||
|
};
|
||||||
|
return leStore.certificates.checkAsync(certOpts).then(function (certs) {
|
||||||
|
if (!certs || certs.privkey !== 'PRIVKEY_A.PEM') {
|
||||||
|
throw new Error("should have correct certs for bar.foo.net (one of the example.com altnames)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// altnames test
|
||||||
|
, function () {
|
||||||
|
var certOpts = {
|
||||||
|
domains: [ 'baz.net' ]
|
||||||
|
};
|
||||||
|
return leStore.certificates.checkAsync(certOpts).then(function (certs) {
|
||||||
|
if (!certs || certs.privkey !== 'PRIVKEY_B.PEM') {
|
||||||
|
throw new Error("should have correct certs for baz.net (one of the foo.com altnames)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var arr = tests.slice(0);
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var test = tests.shift();
|
||||||
|
if (!test) {
|
||||||
|
console.info('All tests passed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
test().then(run, function (err) {
|
||||||
|
var index = arr.length - tests.length - 1;
|
||||||
|
console.error('');
|
||||||
|
console.error(arr[index].toString());
|
||||||
|
console.error('');
|
||||||
|
console.error(err.stack);
|
||||||
|
console.error('');
|
||||||
|
console.error('Failed Test #' + index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
|
@ -0,0 +1,3 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
throw new Error("Tests not implemented");
|
Loading…
Reference in New Issue