MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -763,11 +763,31 @@ var parsers = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var keystore = require('../lib/keystore.js').create(state); | var keystore = require('../lib/keystore.js').create(state); | ||||||
| var keyname = 'telebit-remote'; |  | ||||||
| state.keystore = keystore; | state.keystore = keystore; | ||||||
| state.keystoreSecure = !keystore.insecure; | state.keystoreSecure = !keystore.insecure; | ||||||
| keystore.get(keyname).then(function (key) { | keystore.all().then(function (list) { | ||||||
|   if (key && key.kty && key.kid) { |   var keyext = '.key.jwk.json'; | ||||||
|  |   var key; | ||||||
|  |   var convert; | ||||||
|  |   // TODO create map by account and index into that map to get the master key
 | ||||||
|  |   // and sort keys in the process
 | ||||||
|  |   list.some(function (el) { | ||||||
|  |     if (keyext === el.account.slice(-keyext.length) | ||||||
|  |       && el.password.kty && el.password.kid) { | ||||||
|  |       key = el.password; | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   if (!key) { | ||||||
|  |     list.some(function (el) { | ||||||
|  |       if (el.password.kty) { | ||||||
|  |         convert = el.password; | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (key) { | ||||||
|     state.key = key; |     state.key = key; | ||||||
|     state.pub = keypairs.neuter({ jwk: key }); |     state.pub = keypairs.neuter({ jwk: key }); | ||||||
|     fs.readFile(confpath, 'utf8', parseConfig); |     fs.readFile(confpath, 'utf8', parseConfig); | ||||||
| @ -776,9 +796,10 @@ keystore.get(keyname).then(function (key) { | |||||||
| 
 | 
 | ||||||
|   return keypairs.generate().then(function (pair) { |   return keypairs.generate().then(function (pair) { | ||||||
|     var jwk = pair.private; |     var jwk = pair.private; | ||||||
|     return keypairs.thumbprint({ jwk: pair.public }).then(function (kid) { |     if (convert) { jwk = convert; } | ||||||
|  |     return keypairs.thumbprint({ jwk: jwk }).then(function (kid) { | ||||||
|       jwk.kid = kid; |       jwk.kid = kid; | ||||||
|       return keystore.set(keyname, jwk).then(function () { |       return keystore.set(kid + keyext, jwk).then(function () { | ||||||
|         var size = (jwk.crv || Buffer.from(jwk.n, 'base64').byteLength * 8); |         var size = (jwk.crv || Buffer.from(jwk.n, 'base64').byteLength * 8); | ||||||
|         console.info("Generated new %s %s private key with thumbprint %s", jwk.kty, size, kid); |         console.info("Generated new %s %s private key with thumbprint %s", jwk.kty, size, kid); | ||||||
|         state.key = jwk; |         state.key = jwk; | ||||||
|  | |||||||
							
								
								
									
										130
									
								
								bin/telebitd.js
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								bin/telebitd.js
									
									
									
									
									
								
							| @ -73,14 +73,11 @@ if (!confpath || /^--/.test(confpath)) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| state._confpath = confpath; | state._confpath = confpath; | ||||||
| var tokenpath = path.join(path.dirname(state._confpath), 'access_token.txt'); | var keystore = require('../lib/keystore.js').create({ | ||||||
| var token; |   name: "Telebit Daemon" | ||||||
| try { | , configDir: path.basename(confpath) | ||||||
|   token = fs.readFileSync(tokenpath, 'ascii').trim(); | }); | ||||||
|   //console.log('[DEBUG] access_token', typeof token, token);
 | 
 | ||||||
| } catch(e) { |  | ||||||
|   // ignore
 |  | ||||||
| } |  | ||||||
| var controlServer; | var controlServer; | ||||||
| var myRemote; | var myRemote; | ||||||
| 
 | 
 | ||||||
| @ -442,14 +439,12 @@ function jwtEggspress(req, res, next) { | |||||||
| 
 | 
 | ||||||
| function verifyJws(jwk, jws) { | function verifyJws(jwk, jws) { | ||||||
|   return require('keypairs').export({ jwk: jwk }).then(function (pem) { |   return require('keypairs').export({ jwk: jwk }).then(function (pem) { | ||||||
|     var alg = 'RSA-SHA' + jws.header.alg.replace(/[^\d]+/i, ''); |     var alg = 'SHA' + jws.header.alg.replace(/[^\d]+/i, ''); | ||||||
|     // XXX
 |     var sig = ecdsaAsn1SigToJwtSig(jws.header.alg, jws.signature); | ||||||
|     // TODO check for public key in keytar
 |  | ||||||
|     // XXX
 |  | ||||||
|     return require('crypto') |     return require('crypto') | ||||||
|       .createVerify(alg) |       .createVerify(alg) | ||||||
|       .update(jws.protected + '.' + jws.payload) |       .update(jws.protected + '.' + jws.payload) | ||||||
|       .verify(pem, jws.signature, 'base64'); |       .verify(pem, sig, 'base64'); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -465,16 +460,31 @@ function jwsEggspress(req, res, next) { | |||||||
|   if ('{'.charCodeAt(0) === req.body[0] || '['.charCodeAt(0) === req.body[0]) { |   if ('{'.charCodeAt(0) === req.body[0] || '['.charCodeAt(0) === req.body[0]) { | ||||||
|     req.body = JSON.parse(req.body); |     req.body = JSON.parse(req.body); | ||||||
|   } |   } | ||||||
|   if (req.jws.header.jwk) { | 
 | ||||||
|     verifyJws(req.jws.header.jwk, req.jws).then(function (verified) { |   var vjwk; | ||||||
|       req.jws.selfVerified = verified; |   jwks.some(function (jwk) { | ||||||
|       next(); |     if (jwk.kid === req.jws.header.kid) { | ||||||
|  |       vjwk = jwk; | ||||||
|  |     } | ||||||
|   }); |   }); | ||||||
|     return; |   if ((0 === jwks.length && req.jws.header.jwk)) { | ||||||
|  |     vjwk = req.jws.header.jwk; | ||||||
|  |     if (!vjwk.kid) { throw Error("Impossible: no key id"); } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // TODO verify if possible
 |   return verifyJws(vjwk, req.jws).then(function (verified) { | ||||||
|  |     if (true !== verified) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     req.jws.verified = verified; | ||||||
|  | 
 | ||||||
|  |     if (0 !== jwks.length) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     return keystore.set(vjwk.kid + '.pub.jwk.json', vjwk); | ||||||
|  |   }).then(function () { | ||||||
|     next(); |     next(); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function handleApi() { | function handleApi() { | ||||||
| @ -883,7 +893,9 @@ function serveControlsHelper() { | |||||||
|         // nada
 |         // nada
 | ||||||
|       } |       } | ||||||
|       setTimeout(function () { |       setTimeout(function () { | ||||||
|         console.log("trying again"); |         console.log("Could not start control server (%s), trying again...", err.code); | ||||||
|  |         console.log(portFile); | ||||||
|  |         console.log(serverOpts); | ||||||
|         serveControlsHelper(); |         serveControlsHelper(); | ||||||
|       }, 1000); |       }, 1000); | ||||||
|       return; |       return; | ||||||
| @ -1313,15 +1325,19 @@ state.handlers = { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     state.token = opts.jwt || opts.access_token; |     state.token = opts.jwt || opts.access_token; | ||||||
|  |     // TODO don't put token in config
 | ||||||
|     state.config.token = opts.jwt || opts.access_token; |     state.config.token = opts.jwt || opts.access_token; | ||||||
|     console.info("Updating '" + tokenpath + "' with new token:"); |     console.info("Placing new token in keystore."); | ||||||
|     try { |     try { | ||||||
|       fs.writeFileSync(tokenpath, opts.jwt); |  | ||||||
|       fs.writeFileSync(confpath, YAML.safeDump(snakeCopy(state.config))); |       fs.writeFileSync(confpath, YAML.safeDump(snakeCopy(state.config))); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       console.error("Token not saved:"); |       console.error("Token not saved:"); | ||||||
|       console.error(e); |       console.error(e); | ||||||
|     } |     } | ||||||
|  |     return keystore.set("access_token.jwt", opts.jwt || opts.access_token).catch(function (e) { | ||||||
|  |       console.error("Token not saved:"); | ||||||
|  |       console.error(e); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -1358,6 +1374,72 @@ state.net = state.net || { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| fs.readFile(confpath, 'utf8', parseConfig); | var token; | ||||||
| 
 | var tokenname = "access_token.jwt"; | ||||||
|  | // backwards-compatibility shim
 | ||||||
|  | try { | ||||||
|  |   var tokenpath = path.join(path.dirname(state._confpath), 'access_token.txt'); | ||||||
|  |   token = fs.readFileSync(tokenpath, 'ascii').trim(); | ||||||
|  |   keystore.set(tokenname, token).then(onKeystore).catch(function (err) { | ||||||
|  |     console.error('keystore failure:'); | ||||||
|  |     console.error(err); | ||||||
|  |   }); | ||||||
|  | } catch(e) { | ||||||
|  |   onKeystore(); | ||||||
|  | } | ||||||
|  | var jwks = []; | ||||||
|  | function onKeystore() { | ||||||
|  |   return keystore.all().then(function (list) { | ||||||
|  |     list.forEach(function (el) { | ||||||
|  |       if (tokenname === el.account) { | ||||||
|  |         token = el.password; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       // these are secret because just adding the
 | ||||||
|  |       // willy-nilly to the fs can allow arbitrary tokens
 | ||||||
|  |       if (/\.pub\.jwk\.json$/.test(el.account)) { | ||||||
|  |         // pre-parsed
 | ||||||
|  |         jwks.push(el.password); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     fs.readFile(confpath, 'utf8', parseConfig); | ||||||
|  |   }); | ||||||
|  | } | ||||||
| }()); | }()); | ||||||
|  | 
 | ||||||
|  | function ecdsaAsn1SigToJwtSig(alg, b64sig) { | ||||||
|  |   // ECDSA JWT signatures differ from "normal" ECDSA signatures
 | ||||||
|  |   // https://tools.ietf.org/html/rfc7518#section-3.4
 | ||||||
|  |   if (!/^ES/i.test(alg)) { return b64sig; } | ||||||
|  | 
 | ||||||
|  |   var bufsig = Buffer.from(b64sig, 'base64'); | ||||||
|  |   var hlen = bufsig.byteLength / 2; // should be even
 | ||||||
|  |   var r = bufsig.slice(0, hlen); | ||||||
|  |   var s = bufsig.slice(hlen); | ||||||
|  |   // unpad positive ints less than 32 bytes wide
 | ||||||
|  |   while (!r[0]) { r = r.slice(1); } | ||||||
|  |   while (!s[0]) { s = s.slice(1); } | ||||||
|  |   // pad (or re-pad) ambiguously non-negative BigInts to 33 bytes wide
 | ||||||
|  |   if (0x80 & r[0]) { r = Buffer.concat([Buffer.from([0]), r]); } | ||||||
|  |   if (0x80 & s[0]) { s = Buffer.concat([Buffer.from([0]), s]); } | ||||||
|  | 
 | ||||||
|  |   var len = 2 + r.byteLength + 2 + s.byteLength; | ||||||
|  |   var head = [0x30]; | ||||||
|  |   // hard code 0x80 + 1 because it won't be longer than
 | ||||||
|  |   // two SHA512 plus two pad bytes (130 bytes <= 256)
 | ||||||
|  |   if (len >= 0x80) { head.push(0x81); } | ||||||
|  |   head.push(len); | ||||||
|  | 
 | ||||||
|  |   var buf = Buffer.concat([ | ||||||
|  |     Buffer.from(head) | ||||||
|  |   , Buffer.from([0x02, r.byteLength]), r | ||||||
|  |   , Buffer.from([0x02, s.byteLength]), s | ||||||
|  |   ]); | ||||||
|  | 
 | ||||||
|  |   return buf.toString('base64') | ||||||
|  |     .replace(/-/g, '+') | ||||||
|  |     .replace(/_/g, '/') | ||||||
|  |     .replace(/=/g, '') | ||||||
|  |   ; | ||||||
|  | } | ||||||
|  | |||||||
| @ -34,14 +34,21 @@ module.exports = function eggspress() { | |||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       try { |       function fail(e) { | ||||||
|         //console.log("[eggspress] matched pattern", todo[0], req.url);
 |  | ||||||
|         todo[1](req, res, next); |  | ||||||
|       } catch(e) { |  | ||||||
|         console.error("[eggspress] error", todo[2], todo[0], req.url); |         console.error("[eggspress] error", todo[2], todo[0], req.url); | ||||||
|         console.error(e); |         console.error(e); | ||||||
|         // TODO make a nice error message
 |         // TODO make a nice error message
 | ||||||
|         res.end(e.message); |         res.end(e.message); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       try { | ||||||
|  |         console.log("[eggspress] matched pattern", todo[0], req.url); | ||||||
|  |         var p = todo[1](req, res, next); | ||||||
|  |         if (p && p.catch) { | ||||||
|  |           p.catch(fail); | ||||||
|  |         } | ||||||
|  |       } catch(e) { | ||||||
|  |         fail(e); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,12 +1,14 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  | /*global Promise*/ | ||||||
| 
 | 
 | ||||||
| var fs = require('fs').promises; | var fs = require('fs').promises; | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (opts) { | module.exports.create = function (opts) { | ||||||
|  |   var keyext = '.key'; | ||||||
|   return { |   return { | ||||||
|     getPassword: function (service, name) { |     getPassword: function (service, name) { | ||||||
|       var f = path.join(opts.configDir, name + '.key'); |       var f = path.join(opts.configDir, name + keyext); | ||||||
|       return fs.readFile(f, 'utf8').catch(function (err) { |       return fs.readFile(f, 'utf8').catch(function (err) { | ||||||
|         if ('ENOEXIST' === err.code) { |         if ('ENOEXIST' === err.code) { | ||||||
|           return; |           return; | ||||||
| @ -14,13 +16,22 @@ module.exports.create = function (opts) { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , setPassword: function (service, name, key) { |   , setPassword: function (service, name, key) { | ||||||
|       var f = path.join(opts.configDir, name + '.key'); |       var f = path.join(opts.configDir, name + keyext); | ||||||
|       return fs.writeFile(f, key, 'utf8'); |       return fs.writeFile(f, key, 'utf8'); | ||||||
|     } |     } | ||||||
|   , deletePassword: function (service, name) { |   , deletePassword: function (service, name) { | ||||||
|       var f = path.join(opts.configDir, name + '.key'); |       var f = path.join(opts.configDir, name + keyext); | ||||||
|       return fs.unlink(f); |       return fs.unlink(f); | ||||||
|     } |     } | ||||||
|  |   , findCredentials: function (/*service*/) { | ||||||
|  |       return fs.readDir(opts.configDir).then(function (nodes) { | ||||||
|  |         return Promise.all(nodes.filter(function (node) { | ||||||
|  |           return keyext === node.slice(-4); | ||||||
|  |         }).map(function (node) { | ||||||
|  |           return fs.readFile(path.join(opts.configDir, node + keyext)); | ||||||
|  |         })); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|   , insecure: true |   , insecure: true | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (opts) { | module.exports.create = function (opts) { | ||||||
|   var service = "Telebit"; |   var service = opts.name || "Telebit"; | ||||||
|   var keytar; |   var keytar; | ||||||
|   try { |   try { | ||||||
|     keytar = require('keytar'); |     keytar = require('keytar'); | ||||||
|  |     // TODO test that long "passwords" (JWTs and JWKs) can be stored in all OSes
 | ||||||
|   } catch(e) { |   } catch(e) { | ||||||
|     console.warn("Could not load native key management. Keys will be stored in plain text."); |     console.warn("Could not load native key management. Keys will be stored in plain text."); | ||||||
|     keytar = require('./keystore-fallback.js').create(opts); |     keytar = require('./keystore-fallback.js').create(opts); | ||||||
| @ -21,6 +22,14 @@ module.exports.create = function (opts) { | |||||||
|   , delete: function (name) { |   , delete: function (name) { | ||||||
|       return keytar.deletePassword(service, name); |       return keytar.deletePassword(service, name); | ||||||
|     } |     } | ||||||
|  |   , all: function () { | ||||||
|  |       return keytar.findCredentials(service).then(function (list) { | ||||||
|  |         return list.map(function (el) { | ||||||
|  |           el.password = maybeParse(el.password); | ||||||
|  |           return el; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|   , insecure: keytar.insecure |   , insecure: keytar.insecure | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -38,8 +38,7 @@ | |||||||
|     "ansi-regex": { |     "ansi-regex": { | ||||||
|       "version": "2.1.1", |       "version": "2.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", |       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", | ||||||
|       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", |       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" | ||||||
|       "optional": true |  | ||||||
|     }, |     }, | ||||||
|     "aproba": { |     "aproba": { | ||||||
|       "version": "1.2.0", |       "version": "1.2.0", | ||||||
| @ -137,14 +136,12 @@ | |||||||
|     "code-point-at": { |     "code-point-at": { | ||||||
|       "version": "1.1.0", |       "version": "1.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", |       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", | ||||||
|       "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", |       "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" | ||||||
|       "optional": true |  | ||||||
|     }, |     }, | ||||||
|     "console-control-strings": { |     "console-control-strings": { | ||||||
|       "version": "1.1.0", |       "version": "1.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", |       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", | ||||||
|       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", |       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" | ||||||
|       "optional": true |  | ||||||
|     }, |     }, | ||||||
|     "core-util-is": { |     "core-util-is": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
| @ -236,7 +233,6 @@ | |||||||
|       "version": "1.4.1", |       "version": "1.4.1", | ||||||
|       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", |       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", | ||||||
|       "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", |       "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "once": "^1.4.0" |         "once": "^1.4.0" | ||||||
|       } |       } | ||||||
| @ -400,7 +396,6 @@ | |||||||
|       "version": "1.0.0", |       "version": "1.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", |       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", | ||||||
|       "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", |       "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "number-is-nan": "^1.0.0" |         "number-is-nan": "^1.0.0" | ||||||
|       } |       } | ||||||
| @ -429,10 +424,20 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", |       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", | ||||||
|       "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" |       "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" | ||||||
|     }, |     }, | ||||||
|  |     "keyfetch": { | ||||||
|  |       "version": "1.1.8", | ||||||
|  |       "resolved": "https://registry.npmjs.org/keyfetch/-/keyfetch-1.1.8.tgz", | ||||||
|  |       "integrity": "sha512-a8E1E25mHiv2zZnrBM6WNfQi4hG43TgVg1JG/D61WiTBAM07OJzSuy3j00H2pWPF6MCofBmA+KTzSu145nZWuA==", | ||||||
|  |       "requires": { | ||||||
|  |         "@coolaj86/urequest": "^1.3.6", | ||||||
|  |         "eckles": "^1.4.0", | ||||||
|  |         "rasha": "^1.2.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "keypairs": { |     "keypairs": { | ||||||
|       "version": "1.2.6", |       "version": "1.2.12", | ||||||
|       "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.6.tgz", |       "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.12.tgz", | ||||||
|       "integrity": "sha512-sJDaZvJqHWUawJjrOGKJvKGLfPh0eo2WV7td4RSL88w3BjPYCYI9PkqBn0hLqc6uw0HFSqZMikhGn/jgPpcWnQ==", |       "integrity": "sha512-zYjYdDvo7G4AIkkZVM3WEJBTRUIrFzYswYNqCxcCPHUsgbBBdewSHAH1CiaQ+VA6Yb7BLEPIv8gFrRz5wJrgsw==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "eckles": "^1.4.1", |         "eckles": "^1.4.1", | ||||||
|         "rasha": "^1.2.4" |         "rasha": "^1.2.4" | ||||||
| @ -597,8 +602,7 @@ | |||||||
|     "number-is-nan": { |     "number-is-nan": { | ||||||
|       "version": "1.0.1", |       "version": "1.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", |       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", | ||||||
|       "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", |       "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" | ||||||
|       "optional": true |  | ||||||
|     }, |     }, | ||||||
|     "object-assign": { |     "object-assign": { | ||||||
|       "version": "4.1.1", |       "version": "4.1.1", | ||||||
| @ -617,7 +621,6 @@ | |||||||
|       "version": "1.4.0", |       "version": "1.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", |       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | ||||||
|       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", |       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "wrappy": "1" |         "wrappy": "1" | ||||||
|       } |       } | ||||||
| @ -951,7 +954,6 @@ | |||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", | ||||||
|       "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", |       "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "code-point-at": "^1.0.0", |         "code-point-at": "^1.0.0", | ||||||
|         "is-fullwidth-code-point": "^1.0.0", |         "is-fullwidth-code-point": "^1.0.0", | ||||||
| @ -970,7 +972,6 @@ | |||||||
|       "version": "3.0.1", |       "version": "3.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", |       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", | ||||||
|       "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", |       "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "ansi-regex": "^2.0.0" |         "ansi-regex": "^2.0.0" | ||||||
|       } |       } | ||||||
| @ -1104,8 +1105,7 @@ | |||||||
|     "wrappy": { |     "wrappy": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||||||
|       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", |       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" | ||||||
|       "optional": true |  | ||||||
|     }, |     }, | ||||||
|     "ws": { |     "ws": { | ||||||
|       "version": "6.0.0", |       "version": "6.0.0", | ||||||
|  | |||||||
| @ -57,7 +57,8 @@ | |||||||
|     "finalhandler": "^1.1.1", |     "finalhandler": "^1.1.1", | ||||||
|     "greenlock": "^2.6.7", |     "greenlock": "^2.6.7", | ||||||
|     "js-yaml": "^3.11.0", |     "js-yaml": "^3.11.0", | ||||||
|     "keypairs": "^1.2.6", |     "keyfetch": "^1.1.8", | ||||||
|  |     "keypairs": "^1.2.12", | ||||||
|     "mkdirp": "^0.5.1", |     "mkdirp": "^0.5.1", | ||||||
|     "proxy-packer": "^2.0.2", |     "proxy-packer": "^2.0.2", | ||||||
|     "ps-list": "^5.0.0", |     "ps-list": "^5.0.0", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user