mirror of
				https://github.com/therootcompany/greenlock.js.git
				synced 2025-10-30 19:42:47 +00:00 
			
		
		
		
	Merge branch 'v2.x' of github.com:Daplie/node-letsencrypt into v2.x
This commit is contained in:
		
						commit
						c246c196b9
					
				
							
								
								
									
										58
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								README.md
									
									
									
									
									
								
							| @ -7,23 +7,12 @@ | |||||||
| | [letsencrypt-hapi](https://github.com/Daplie/letsencrypt-hapi) | | [letsencrypt-hapi](https://github.com/Daplie/letsencrypt-hapi) | ||||||
| | | | | ||||||
| 
 | 
 | ||||||
| letsencrypt (v2) | letsencrypt | ||||||
| =========== | =========== | ||||||
| 
 | 
 | ||||||
| Automatic [Let's Encrypt](https://letsencrypt.org) HTTPS / TLS / SSL Certificates for node.js | Automatic [Let's Encrypt](https://letsencrypt.org) HTTPS / TLS / SSL Certificates for node.js | ||||||
| 
 | 
 | ||||||
|   * [Automatic HTTPS with ExpressJS](https://github.com/Daplie/letsencrypt-express) | Free SLL with [90-day](https://letsencrypt.org/2015/11/09/why-90-days.html) HTTPS / TLS Certificates | ||||||
|   * [Automatic live renewal](https://github.com/Daplie/letsencrypt-express#how-automatic) |  | ||||||
|   * On-the-fly HTTPS certificates for Dynamic DNS (in-process, no server restart) |  | ||||||
|   * Works with node cluster out of the box |  | ||||||
|   * usable [via commandline](https://github.com/Daplie/letsencrypt-cli) as well |  | ||||||
|   * Free SSL (HTTPS Certificates for TLS) |  | ||||||
|   * [90-day certificates](https://letsencrypt.org/2015/11/09/why-90-days.html) |  | ||||||
| 
 |  | ||||||
| **See Also** |  | ||||||
| 
 |  | ||||||
| * [Let's Encrypt in (exactly) 90 seconds with Caddy](https://daplie.com/articles/lets-encrypt-in-literally-90-seconds/) |  | ||||||
| * [lego](https://github.com/xenolf/lego): Let's Encrypt for golang |  | ||||||
| 
 | 
 | ||||||
| STOP | STOP | ||||||
| ==== | ==== | ||||||
| @ -71,7 +60,7 @@ It's very simple and easy to use, but also very complete and easy to extend and | |||||||
| 
 | 
 | ||||||
| ### Overly Simplified Example | ### Overly Simplified Example | ||||||
| 
 | 
 | ||||||
| Against my better judgement I'm providing a terribly oversimplified exmaple | Against my better judgement I'm providing a terribly oversimplified example | ||||||
| of how to use this library: | of how to use this library: | ||||||
| 
 | 
 | ||||||
| ```javascript | ```javascript | ||||||
| @ -148,37 +137,36 @@ le = LE.create({ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // Check in-memory cache of certificates for the named domain | // Check in-memory cache of certificates for the named domain | ||||||
| le.exists({ domain: 'example.com' }).then(function (results) { | le.check({ domain: 'example.com' }).then(function (results) { | ||||||
|   if (results) { |   if (results) { | ||||||
|     // we already have certificates |     // we already have certificates | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|   // Register Certificate manually |   // Register Certificate manually | ||||||
|   le.register( |   le.get({ | ||||||
| 
 | 
 | ||||||
|     { domains: ['example.com']                                // CHANGE TO YOUR DOMAIN (list for SANS) |     domains: ['example.com']                                // CHANGE TO YOUR DOMAIN (list for SANS) | ||||||
|   , email: 'user@email.com'                                 // CHANGE TO YOUR EMAIL |   , email: 'user@email.com'                                 // CHANGE TO YOUR EMAIL | ||||||
|     , agreeTos: ''                                            // set to tosUrl string to pre-approve (and skip agreeToTerms) |   , agreeTos: ''                                            // set to tosUrl string (or true) to pre-approve (and skip agreeToTerms) | ||||||
|     , rsaKeySize: 2048                                        // 1024 or 2048 |   , rsaKeySize: 2048                                        // 2048 or higher | ||||||
|   , challengeType: 'http-01'                                // http-01, tls-sni-01, or dns-01 |   , challengeType: 'http-01'                                // http-01, tls-sni-01, or dns-01 | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|   , function (err, results) { |   }).then(function (results) { | ||||||
|       if (err) { | 
 | ||||||
|  |     console.log('success'); | ||||||
|  | 
 | ||||||
|  |   }, function (err) { | ||||||
|  | 
 | ||||||
|     // Note: you must either use le.middleware() with express, |     // Note: you must either use le.middleware() with express, | ||||||
|     // manually use le.getChallenge(domain, key, val, done) |     // manually use le.getChallenge(domain, key, val, done) | ||||||
|     // or have a webserver running and responding |     // or have a webserver running and responding | ||||||
|     // to /.well-known/acme-challenge at `webrootPath` |     // to /.well-known/acme-challenge at `webrootPath` | ||||||
|     console.error('[Error]: node-letsencrypt/examples/standalone'); |     console.error('[Error]: node-letsencrypt/examples/standalone'); | ||||||
|     console.error(err.stack); |     console.error(err.stack); | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       console.log('success'); |   }); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|   ); |  | ||||||
| 
 | 
 | ||||||
| }); | }); | ||||||
| ``` | ``` | ||||||
| @ -200,6 +188,12 @@ API | |||||||
| 
 | 
 | ||||||
| The full end-user API is exposed in the example above and includes all relevant options. | The full end-user API is exposed in the example above and includes all relevant options. | ||||||
| 
 | 
 | ||||||
|  | ``` | ||||||
|  | le.register | ||||||
|  | le.get          // checkAndRegister | ||||||
|  | le.check | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ### Helper Functions | ### Helper Functions | ||||||
| 
 | 
 | ||||||
| We do expose a few helper functions: | We do expose a few helper functions: | ||||||
| @ -241,7 +235,7 @@ TODO double check and finish | |||||||
|   * accounts.get |   * accounts.get | ||||||
|   * accounts.exists |   * accounts.exists | ||||||
| * certs | * certs | ||||||
|   * certs.byDomain |   * certs.byAccount | ||||||
|   * certs.all |   * certs.all | ||||||
|   * certs.get |   * certs.get | ||||||
|   * certs.exists |   * certs.exists | ||||||
| @ -250,9 +244,9 @@ TODO double check and finish | |||||||
| 
 | 
 | ||||||
| TODO finish | TODO finish | ||||||
| 
 | 
 | ||||||
| * setChallenge(opts, domain, key, value, done);   // opts will be saved with domain/key | * `.set(opts, domain, key, value, done);`         // opts will be saved with domain/key | ||||||
| * getChallenge(domain, key, done);                // opts will be retrieved by domain/key | * `.get(opts, domain, key, done);`                // opts will be retrieved by domain/key | ||||||
| * removeChallenge(domain, key, done);             // opts will be retrieved by domain/key | * `.remove(opts, domain, key, done);`             // opts will be retrieved by domain/key | ||||||
| 
 | 
 | ||||||
| Change History | Change History | ||||||
| ============== | ============== | ||||||
|  | |||||||
							
								
								
									
										169
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								index.js
									
									
									
									
									
								
							| @ -1,21 +1,20 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| // TODO handle www and no-www together somehow?
 | var ACME = require('le-acme-core').ACME; | ||||||
| 
 |  | ||||||
| var PromiseA = require('bluebird'); |  | ||||||
| var leCore = require('letiny-core'); |  | ||||||
| 
 | 
 | ||||||
| var LE = module.exports; | var LE = module.exports; | ||||||
|  | LE.LE = LE; | ||||||
|  | // in-process cache, shared between all instances
 | ||||||
|  | var ipc = {}; | ||||||
| 
 | 
 | ||||||
| LE.defaults = { | LE.defaults = { | ||||||
|   server: leCore.productionServerUrl |   productionServerUrl: ACME.productionServerUrl | ||||||
| , stagingServer: leCore.stagingServerUrl | , stagingServerUrl: ACME.stagingServerUrl | ||||||
| , liveServer: leCore.productionServerUrl |  | ||||||
| 
 | 
 | ||||||
| , productionServerUrl: leCore.productionServerUrl | , rsaKeySize: ACME.rsaKeySize || 2048 | ||||||
| , stagingServerUrl: leCore.stagingServerUrl | , challengeType: ACME.challengeType || 'http-01' | ||||||
| 
 | 
 | ||||||
| , acmeChallengePrefix: leCore.acmeChallengePrefix | , acmeChallengePrefix: ACME.acmeChallengePrefix | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // backwards compat
 | // backwards compat
 | ||||||
| @ -23,58 +22,108 @@ Object.keys(LE.defaults).forEach(function (key) { | |||||||
|   LE[key] = LE.defaults[key]; |   LE[key] = LE.defaults[key]; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| LE.create = function (defaults, handlers, backend) { | // show all possible options
 | ||||||
|   var Core = require('./lib/core'); | var u; // undefined
 | ||||||
|   var core; | LE._undefined = { | ||||||
|   if (!backend) { backend = require('./lib/pycompat'); } |   acme: u | ||||||
|   if (!handlers) { handlers = {}; } | , store: u | ||||||
|   if (!handlers.renewWithin) { handlers.renewWithin = 3 * 24 * 60 * 60 * 1000; } | , challenger: u | ||||||
|   if (!handlers.memorizeFor) { handlers.memorizeFor = 1 * 24 * 60 * 60 * 1000; } | , register: u | ||||||
|   if (!handlers.sniRegisterCallback) { | , check: u | ||||||
|     handlers.sniRegisterCallback = function (args, cache, cb) { | , renewWithin: u | ||||||
|       // TODO when we have ECDSA, just do this automatically
 | , memorizeFor: u | ||||||
|       cb(null, null); | , acmeChallengePrefix: u | ||||||
|  | , rsaKeySize: u | ||||||
|  | , challengeType: u | ||||||
|  | , server: u | ||||||
|  | , agreeToTerms: u | ||||||
|  | , _ipc: u | ||||||
| }; | }; | ||||||
|  | LE._undefine = function (le) { | ||||||
|  |   Object.keys(LE._undefined).forEach(function (key) { | ||||||
|  |     if (!(key in le)) { | ||||||
|  |       le[key] = u; | ||||||
|     } |     } | ||||||
| 
 |   }); | ||||||
|   if (backend.create) { | 
 | ||||||
|     backend = backend.create(defaults); |   return le; | ||||||
|   } | }; | ||||||
|   backend = PromiseA.promisifyAll(backend); | LE.create = function (le) { | ||||||
|   core = Core.create(defaults, handlers, backend); |   var PromiseA = require('bluebird'); | ||||||
| 
 | 
 | ||||||
|   var le = { |   le.acme = le.acme || ACME.create({ debug: le.debug }); | ||||||
|     backend: backend |   le.store = le.store || require('le-store-certbot').create({ debug: le.debug }); | ||||||
|   , core: core |   le.challenger = le.challenger || require('le-store-certbot').create({ debug: le.debug }); | ||||||
|     // register
 |   le.core = require('./lib/core'); | ||||||
|   , create: function (args, cb) { | 
 | ||||||
|       return core.registerAsync(args).then(function (pems) { |   le = LE._undefine(le); | ||||||
|         cb(null, pems); |   le.acmeChallengePrefix = LE.acmeChallengePrefix; | ||||||
|       }, cb); |   le.rsaKeySize = le.rsaKeySize || LE.rsaKeySize; | ||||||
|     } |   le.challengeType = le.challengeType || LE.challengeType; | ||||||
|     // fetch
 |   le._ipc = ipc; | ||||||
|   , domain: function (args, cb) { | 
 | ||||||
|       // TODO must return email, domains, tos, pems
 |   if (!le.renewWithin) { le.renewWithin = 3 * 24 * 60 * 60 * 1000; } | ||||||
|       return core.fetchAsync(args).then(function (certInfo) { |   if (!le.memorizeFor) { le.memorizeFor = 1 * 24 * 60 * 60 * 1000; } | ||||||
|         cb(null, certInfo); | 
 | ||||||
|       }, cb); |   if (!le.server) { | ||||||
|     } |     throw new Error("opts.server must be set to 'staging' or a production url, such as LE.productionServerUrl'"); | ||||||
|   , domains: function (args, cb) { |   } | ||||||
|       // TODO show all domains or limit by account
 |   if ('staging' === le.server) { | ||||||
|       throw new Error('not implemented'); |     le.server = LE.stagingServerUrl; | ||||||
|     } |   } | ||||||
|   , accounts: function (args, cb) { |   else if ('production' === le.server) { | ||||||
|       // TODO show all accounts or limit by domain
 |     le.server = LE.productionServerUrl; | ||||||
|       throw new Error('not implemented'); |   } | ||||||
|     } | 
 | ||||||
|   , account: function (args, cb) { |   if (le.acme.create) { | ||||||
|       // TODO return one account
 |     le.acme = le.acme.create(le); | ||||||
|       throw new Error('not implemented'); |   } | ||||||
|     } |   le.acme = PromiseA.promisifyAll(le.acme); | ||||||
|   }; |   le._acmeOpts = le.acme.getOptions(); | ||||||
| 
 |   Object.keys(le._acmeOpts).forEach(function (key) { | ||||||
|   // exists
 |     if (!(key in le)) { | ||||||
|   // get
 |       le[key] = le._acmeOpts[key]; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (le.store.create) { | ||||||
|  |     le.store = le.store.create(le); | ||||||
|  |   } | ||||||
|  |   le.store = PromiseA.promisifyAll(le.store); | ||||||
|  |   le._storeOpts = le.store.getOptions(); | ||||||
|  |   Object.keys(le._storeOpts).forEach(function (key) { | ||||||
|  |     if (!(key in le)) { | ||||||
|  |       le[key] = le._storeOpts[key]; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (le.challenger.create) { | ||||||
|  |     le.challenger = le.challenger.create(le); | ||||||
|  |   } | ||||||
|  |   le.challenger = PromiseA.promisifyAll(le.challenger); | ||||||
|  |   le._challengerOpts = le.challenger.getOptions(); | ||||||
|  |   Object.keys(le._challengerOpts).forEach(function (key) { | ||||||
|  |     if (!(key in le)) { | ||||||
|  |       le[key] = le._challengerOpts[key]; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (le.core.create) { | ||||||
|  |     le.core = le.core.create(le); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   le.register = function (args) { | ||||||
|  |     return le.core.certificates.getAsync(args); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   le.check = function (args) { | ||||||
|  |     // TODO must return email, domains, tos, pems
 | ||||||
|  |     return le.core.certificates.checkAsync(args); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   le.middleware = function () { | ||||||
|  |     return require('./lib/middleware')(le); | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   return le; |   return le; | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										329
									
								
								lib/core.js
									
									
									
									
									
								
							
							
						
						
									
										329
									
								
								lib/core.js
									
									
									
									
									
								
							| @ -1,48 +1,77 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| var LE = require('../'); | module.exports.create = function (le) { | ||||||
| var ipc = {}; // in-process cache
 |  | ||||||
| 
 |  | ||||||
| module.exports.create = function (defaults, handlers, backend) { |  | ||||||
|   var backendDefaults = backend.getDefaults && backend.getDefaults || backend.defaults || {}; |  | ||||||
| 
 |  | ||||||
|   defaults.server = defaults.server || LE.liveServer; |  | ||||||
|   handlers.merge = require('./common').merge; |  | ||||||
|   handlers.tplCopy = require('./common').tplCopy; |  | ||||||
| 
 |  | ||||||
|   var PromiseA = require('bluebird'); |   var PromiseA = require('bluebird'); | ||||||
|  |   var utils = require('./utils'); // merge, tplCopy;
 | ||||||
|   var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA); |   var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA); | ||||||
|   var LeCore = PromiseA.promisifyAll(require('letiny-core')); |  | ||||||
|   var crypto = require('crypto'); |   var crypto = require('crypto'); | ||||||
| 
 | 
 | ||||||
|   function attachCertInfo(results) { |   var core = { | ||||||
|     var getCertInfo = require('./cert-info').getCertInfo; |     //
 | ||||||
|     // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 |     // Helpers
 | ||||||
|     var certInfo = getCertInfo(results.cert); |     //
 | ||||||
|  |     getAcmeUrlsAsync: function (args) { | ||||||
|  |       var now = Date.now(); | ||||||
| 
 | 
 | ||||||
|     //results.issuedAt = arr[3].mtime.valueOf()
 |       // TODO check response header on request for cache time
 | ||||||
|     results.issuedAt = Date(certInfo.notBefore.value).valueOf(); // Date.now()
 |       if ((now - le._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) { | ||||||
|     results.expiresAt = Date(certInfo.notAfter.value).valueOf(); |         return PromiseA.resolve(le._ipc.acmeUrls); | ||||||
| 
 |  | ||||||
|     return results; |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|   function createAccount(args, handlers) { |       return le.acme.getAcmeUrlsAsync(args.server).then(function (data) { | ||||||
|     args.rsaKeySize = args.rsaKeySize || 2048; |         le._ipc.acmeUrlsUpdatedAt = Date.now(); | ||||||
|  |         le._ipc.acmeUrls = data; | ||||||
|  | 
 | ||||||
|  |         return le._ipc.acmeUrls; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     //
 | ||||||
|  |     // The Main Enchilada
 | ||||||
|  |     //
 | ||||||
|  | 
 | ||||||
|  |     //
 | ||||||
|  |     // Accounts
 | ||||||
|  |     //
 | ||||||
|  |   , accounts: { | ||||||
|  |       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 LeCore.registerNewAccountAsync({ |               return le.acme.registerNewAccountAsync({ | ||||||
|                 email: args.email |                 email: args.email | ||||||
|               , newRegUrl: args._acmeUrls.newReg |               , newRegUrl: args._acmeUrls.newReg | ||||||
|       , agreeToTerms: function (tosUrl, agree) { |               , agreeToTerms: function (tosUrl, agreeCb) { | ||||||
|  |                   if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === le.agreeToTerms) { | ||||||
|  |                     agreeCb(null, tosUrl); | ||||||
|  |                     return; | ||||||
|  |                   } | ||||||
|  | 
 | ||||||
|                   // args.email = email;      // already there
 |                   // args.email = email;      // already there
 | ||||||
|  |                   // args.domains = domains   // already there
 | ||||||
|                   args.tosUrl = tosUrl; |                   args.tosUrl = tosUrl; | ||||||
|           handlers.agreeToTerms(args, agree); |                   le.agreeToTerms(args, agreeCb); | ||||||
|                 } |                 } | ||||||
|               , accountKeypair: keypair |               , accountKeypair: keypair | ||||||
| 
 | 
 | ||||||
|       , debug: defaults.debug || args.debug || handlers.debug |               , debug: le.debug || args.debug | ||||||
|               }).then(function (body) { |               }).then(function (body) { | ||||||
|                 // TODO XXX use sha256 (the python client uses md5)
 |                 // TODO XXX use sha256 (the python client uses md5)
 | ||||||
|                 // TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
 |                 // TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
 | ||||||
| @ -62,61 +91,96 @@ module.exports.create = function (defaults, handlers, backend) { | |||||||
| 
 | 
 | ||||||
|                 args.account = account; |                 args.account = account; | ||||||
| 
 | 
 | ||||||
|         return backend.setAccountAsync(args, account).then(function () { |                 return le.store.accounts.setAsync(args, account).then(function () { | ||||||
|                   return account; |                   return account; | ||||||
|                 }); |                 }); | ||||||
|               }); |               }); | ||||||
|             }); |             }); | ||||||
|   } |           }); | ||||||
| 
 |  | ||||||
|   function getAcmeUrls(args) { |  | ||||||
|     var now = Date.now(); |  | ||||||
| 
 |  | ||||||
|     // TODO check response header on request for cache time
 |  | ||||||
|     if ((now - ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) { |  | ||||||
|       return PromiseA.resolve(ipc.acmeUrls); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return LeCore.getAcmeUrlsAsync(args.server).then(function (data) { |  | ||||||
|       ipc.acmeUrlsUpdatedAt = Date.now(); |  | ||||||
|       ipc.acmeUrls = data; |  | ||||||
| 
 |  | ||||||
|       return ipc.acmeUrls; |  | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|   function getCertificateAsync(args, defaults, handlers) { |     , getAsync: function (args) { | ||||||
|     args.rsaKeySize = args.rsaKeySize || 2048; |         return core.accounts.checkAsync(args).then(function (account) { | ||||||
|     args.challengeType = args.challengeType || 'http-01'; |           if (account) { | ||||||
|  |             return account; | ||||||
|  |           } else { | ||||||
|  |             return core.accounts.registerAsync(args); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|     function log() { |     , checkAsync: function (args) { | ||||||
|       if (args.debug || defaults.debug) { |         var requiredArgs = ['accountId', 'email', 'domains', 'domain']; | ||||||
|         console.log.apply(console, arguments); |         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.account = account; | ||||||
|  |           args.accountId = account.id; | ||||||
|  | 
 | ||||||
|  |           return account; | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var account = args.account; |   , certificates: { | ||||||
|     var promise; |       registerAsync: function (args) { | ||||||
|  |         var err; | ||||||
|  |         var copy = utils.merge(args, le); | ||||||
|  |         args = utils.tplCopy(copy); | ||||||
|  | 
 | ||||||
|  |         if (!Array.isArray(args.domains)) { | ||||||
|  |           return PromiseA.reject(new Error('args.domains should be an array of domains')); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!(args.domains.length && args.domains.every(utils.isValidDomain))) { | ||||||
|  |           // NOTE: this library can't assume to handle the http loopback
 | ||||||
|  |           // (or dns-01 validation may be used)
 | ||||||
|  |           // so we do not check dns records or attempt a loopback here
 | ||||||
|  |           err = new Error("invalid domain name(s): '" + args.domains + "'"); | ||||||
|  |           err.code = "INVALID_DOMAIN"; | ||||||
|  |           return PromiseA.reject(err); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return core.accounts.getAsync(copy).then(function (account) { | ||||||
|  |           copy.account = account; | ||||||
|  | 
 | ||||||
|  |           //var account = args.account;
 | ||||||
|           var keypairOpts = { public: true, pem: true }; |           var keypairOpts = { public: true, pem: true }; | ||||||
| 
 | 
 | ||||||
|     promise = backend.getPrivatePem(args).then(function (pem) { |           var promise = le.store.certificates.checkKeypairAsync(args).then(function (keypair) { | ||||||
|       return RSA.import({ privateKeyPem: pem }); |             return RSA.import(keypair); | ||||||
|           }, function (/*err*/) { |           }, function (/*err*/) { | ||||||
|             return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) { |             return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) { | ||||||
|               keypair.privateKeyPem = RSA.exportPrivatePem(keypair); |               keypair.privateKeyPem = RSA.exportPrivatePem(keypair); | ||||||
|               keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair); |               keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair); | ||||||
|         return backend.setPrivatePem(args, keypair); |               return le.store.certificates.setKeypairAsync(args, keypair); | ||||||
|             }); |             }); | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           return promise.then(function (domainKeypair) { |           return promise.then(function (domainKeypair) { | ||||||
|       log("[le/core.js] get certificate"); |  | ||||||
| 
 |  | ||||||
|             args.domainKeypair = domainKeypair; |             args.domainKeypair = domainKeypair; | ||||||
|             //args.registration = domainKey;
 |             //args.registration = domainKey;
 | ||||||
| 
 | 
 | ||||||
|       return LeCore.getCertificateAsync({ |             // Note: the ACME urls are always fetched fresh on purpose
 | ||||||
|         debug: args.debug |             // TODO is this the right place for this?
 | ||||||
|  |             return core.getAcmeUrlsAsync(args).then(function (urls) { | ||||||
|  |               args._acmeUrls = urls; | ||||||
|  | 
 | ||||||
|  |               return le.acme.getCertificateAsync({ | ||||||
|  |                 debug: args.debug || le.debug | ||||||
| 
 | 
 | ||||||
|               , newAuthzUrl: args._acmeUrls.newAuthz |               , newAuthzUrl: args._acmeUrls.newAuthz | ||||||
|               , newCertUrl: args._acmeUrls.newCert |               , newCertUrl: args._acmeUrls.newCert | ||||||
| @ -135,146 +199,89 @@ module.exports.create = function (defaults, handlers, backend) { | |||||||
|                 // (args is per-request, defaults is per instance)
 |                 // (args is per-request, defaults is per instance)
 | ||||||
|                 //
 |                 //
 | ||||||
|               , setChallenge: function (domain, key, value, done) { |               , setChallenge: function (domain, key, value, done) { | ||||||
|           var copy = handlers.merge({ domains: [domain] }, defaults, backendDefaults); |                   var copy = utils.merge({ domains: [domain] }, le); | ||||||
|           handlers.tplCopy(copy); |                   utils.tplCopy(copy); | ||||||
| 
 | 
 | ||||||
|                   //args.domains = [domain];
 |                   //args.domains = [domain];
 | ||||||
|                   args.domains = args.domains || [domain]; |                   args.domains = args.domains || [domain]; | ||||||
| 
 | 
 | ||||||
|           if (5 !== handlers.setChallenge.length) { |                   if (5 !== le.challenger.set.length) { | ||||||
|             done(new Error("handlers.setChallenge receives the wrong number of arguments." |                     done(new Error("le.challenger.set receives the wrong number of arguments." | ||||||
|                       + " You must define setChallenge as function (opts, domain, key, val, cb) { }")); |                       + " You must define setChallenge as function (opts, domain, key, val, cb) { }")); | ||||||
|                     return; |                     return; | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
|           handlers.setChallenge(copy, domain, key, value, done); |                   le.challenger.set(copy, domain, key, value, done); | ||||||
|                 } |                 } | ||||||
|               , removeChallenge: function (domain, key, done) { |               , removeChallenge: function (domain, key, done) { | ||||||
|           var copy = handlers.merge({ domains: [domain] }, defaults, backendDefaults); |                   var copy = utils.merge({ domains: [domain] }, le); | ||||||
|           handlers.tplCopy(copy); |                   utils.tplCopy(copy); | ||||||
| 
 | 
 | ||||||
|           if (4 !== handlers.removeChallenge.length) { |                   if (4 !== le.challenger.remove.length) { | ||||||
|             done(new Error("handlers.removeChallenge receives the wrong number of arguments." |                     done(new Error("le.challenger.remove receives the wrong number of arguments." | ||||||
|                       + " You must define removeChallenge as function (opts, domain, key, cb) { }")); |                       + " You must define removeChallenge as function (opts, domain, key, cb) { }")); | ||||||
|                     return; |                     return; | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
|           handlers.removeChallenge(copy, domain, key, done); |                   le.challenger.remove(copy, domain, key, done); | ||||||
|                 } |                 } | ||||||
|       }).then(attachCertInfo); |               }).then(utils.attachCertInfo); | ||||||
|  |             }); | ||||||
|           }).then(function (results) { |           }).then(function (results) { | ||||||
|       // { cert, chain, fullchain, privkey }
 |             // { cert, chain, privkey }
 | ||||||
| 
 | 
 | ||||||
|             args.pems = results; |             args.pems = results; | ||||||
|       return backend.setRegistration(args, defaults, handlers); |             return le.store.certificates.setAsync(args).then(function () { | ||||||
|  |               return results; | ||||||
|  |             }); | ||||||
|  |           }); | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|  |     , 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); | ||||||
| 
 | 
 | ||||||
|   function getOrCreateDomainCertificate(args, defaults, handlers) { |         // returns pems
 | ||||||
|     if (args.duplicate) { |         return le.store.certificates.checkAsync(copy).then(utils.attachCertInfo); | ||||||
|       // we're forcing a refresh via 'dupliate: true'
 |       } | ||||||
|       return getCertificateAsync(args, defaults, handlers); |     , getAsync: function (args) { | ||||||
|  |         var copy = utils.merge(args, le); | ||||||
|  |         args = utils.tplCopy(copy); | ||||||
|  | 
 | ||||||
|  |         return core.certificates.checkAsync(args).then(function (certs) { | ||||||
|  |           if (!certs) { | ||||||
|  |             // There is no cert available
 | ||||||
|  |             return core.certificates.registerAsync(args); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|     return wrapped.fetchAsync(args).then(function (certs) { |           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;
 | ||||||
| 
 | 
 | ||||||
|       if (!certs || (Date.now() - certs.issuedAt) > halfLife) { |           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 getCertificateAsync(args, defaults, handlers); |             return core.certificates.renewAsync(args); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           return PromiseA.reject(new Error( |           return PromiseA.reject(new Error( | ||||||
|               "[ERROR] Certificate issued at '" |               "[ERROR] Certificate issued at '" | ||||||
|             + new Date(certs.issuedAt).toISOString() + "' and expires at '" |             + new Date(certs.issuedAt).toISOString() + "' and expires at '" | ||||||
|             + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '" |             + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '" | ||||||
|         + new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force." |             + new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force." | ||||||
|           )); |           )); | ||||||
|  |         }).then(function (results) { | ||||||
|  |           // returns pems
 | ||||||
|  |           return results; | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
| 
 |  | ||||||
|   // returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
 |  | ||||||
|   function getOrCreateAcmeAccount(args, defaults, handlers) { |  | ||||||
|     function log() { |  | ||||||
|       if (args.debug) { |  | ||||||
|         console.log.apply(console, arguments); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return backend.getAccountId(args).then(function (accountId) { |  | ||||||
| 
 |  | ||||||
|       // Note: the ACME urls are always fetched fresh on purpose
 |  | ||||||
|       return getAcmeUrls(args).then(function (urls) { |  | ||||||
|         args._acmeUrls = urls; |  | ||||||
| 
 |  | ||||||
|         if (accountId) { |  | ||||||
|           log('[le/core.js] use account'); |  | ||||||
| 
 |  | ||||||
|           args.accountId = accountId; |  | ||||||
|           return backend.getAccount(args, handlers); |  | ||||||
|         } else { |  | ||||||
|           log('[le/core.js] create account'); |  | ||||||
|           return createAccount(args, handlers); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   var wrapped = { |  | ||||||
|     registerAsync: function (args) { |  | ||||||
|       var utils = require('./lib/common'); |  | ||||||
|       var err; |  | ||||||
| 
 |  | ||||||
|       if (!Array.isArray(args.domains)) { |  | ||||||
|         return PromiseA.reject(new Error('args.domains should be an array of domains')); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (!(args.domains.length && args.domains.every(utils.isValidDomain))) { |  | ||||||
|         // NOTE: this library can't assume to handle the http loopback
 |  | ||||||
|         // (or dns-01 validation may be used)
 |  | ||||||
|         // so we do not check dns records or attempt a loopback here
 |  | ||||||
|         err = new Error("invalid domain name(s): '" + args.domains + "'"); |  | ||||||
|         err.code = "INVALID_DOMAIN"; |  | ||||||
|         return PromiseA.reject(err); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       var copy = handlers.merge(args, defaults, backendDefaults); |  | ||||||
|       handlers.tplCopy(copy); |  | ||||||
| 
 |  | ||||||
|       return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) { |  | ||||||
|         copy.account = account; |  | ||||||
| 
 |  | ||||||
|         return backend.getOrCreateRenewal(copy).then(function (pyobj) { |  | ||||||
| 
 |  | ||||||
|           copy.pyobj = pyobj; |  | ||||||
|           return getOrCreateDomainCertificate(copy, defaults, handlers); |  | ||||||
|         }); |  | ||||||
|       }).then(function (result) { |  | ||||||
|         return result; |  | ||||||
|       }, function (err) { |  | ||||||
|         return PromiseA.reject(err); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   , getOrCreateAccount: function (args) { |  | ||||||
|       return createAccount(args, handlers); |  | ||||||
|     } |  | ||||||
|   , configureAsync: function (hargs) { |  | ||||||
|       var copy = handlers.merge(hargs, defaults, backendDefaults); |  | ||||||
|       handlers.tplCopy(copy); |  | ||||||
| 
 |  | ||||||
|       return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) { |  | ||||||
|         copy.account = account; |  | ||||||
|         return backend.getOrCreateRenewal(copy); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   , fetchAsync: function (args) { |  | ||||||
|       var copy = handlers.merge(args, defaults); |  | ||||||
|       handlers.tplCopy(copy); |  | ||||||
| 
 |  | ||||||
|       return backend.fetchAsync(copy).then(attachCertInfo); |  | ||||||
|     } |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return wrapped; |   return core; | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								lib/middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | module.exports = function (le) { | ||||||
|  |   return function () { | ||||||
|  |     var prefix = le.acmeChallengePrefix; // /.well-known/acme-challenge/:token
 | ||||||
|  | 
 | ||||||
|  |     return function (req, res, next) { | ||||||
|  |       if (0 !== req.url.indexOf(prefix)) { | ||||||
|  |         next(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var key = req.url.slice(prefix.length); | ||||||
|  |       var hostname = req.hostname || (req.headers.host || '').toLowerCase().replace(/:*/, ''); | ||||||
|  | 
 | ||||||
|  |       // TODO tpl copy?
 | ||||||
|  |       le.challenger.getAsync(le, hostname, key).then(function (token) { | ||||||
|  |         if (!token) { | ||||||
|  |           res.status = 404; | ||||||
|  |           res.send("Error: These aren't the tokens you're looking for. Move along."); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         res.send(token); | ||||||
|  |       }, function (/*err*/) { | ||||||
|  |         res.status = 404; | ||||||
|  |         res.send("Error: These aren't the tokens you're looking for. Move along."); | ||||||
|  |       }); | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @ -4,6 +4,20 @@ 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) { | ||||||
|  |   var getCertInfo = require('./cert-info').getCertInfo; | ||||||
|  |   // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | ||||||
|  |   var certInfo = getCertInfo(results.cert); | ||||||
|  | 
 | ||||||
|  |   //results.issuedAt = arr[3].mtime.valueOf()
 | ||||||
|  |   results.issuedAt = Date(certInfo.notBefore.value).valueOf(); // Date.now()
 | ||||||
|  |   results.expiresAt = Date(certInfo.notAfter.value).valueOf(); | ||||||
|  | 
 | ||||||
|  |   return results; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| module.exports.isValidDomain = function (domain) { | module.exports.isValidDomain = function (domain) { | ||||||
|   if (re.test(domain)) { |   if (re.test(domain)) { | ||||||
| @ -21,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) { | ||||||
| @ -63,4 +77,31 @@ module.exports.tplCopy = function (copy) { | |||||||
|       copy[key] = copy[key].replace(':' + tplname, tpls[tplname]); |       copy[key] = copy[key].replace(':' + tplname, tpls[tplname]); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   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
									
								
							
							
						
						
									
										117
									
								
								tests/create-account.js
									
									
									
									
									
										Normal 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(); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user