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;
|
||||||
|
|
128
bin/telebitd.js
128
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 || {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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…
Reference in New Issue