handle accounts
This commit is contained in:
parent
c045e4c712
commit
4e459ea617
|
@ -0,0 +1,90 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Telebit Account</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="js-auth-form">
|
||||||
|
<input class="js-auth-subject" type="email"/>
|
||||||
|
<button class="js-auth-submit" type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
var OAUTH3 = window.OAUTH3;
|
||||||
|
var oauth3 = OAUTH3.create({
|
||||||
|
host: window.location.host
|
||||||
|
, pathname: window.location.pathname.replace(/\/[^\/]*$/, '/')
|
||||||
|
});
|
||||||
|
var $ = function () { return document.querySelector.apply(document, arguments); }
|
||||||
|
|
||||||
|
function onChangeProvider(providerUri) {
|
||||||
|
// example https://oauth3.org
|
||||||
|
return oauth3.setIdentityProvider(providerUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This opens up the login window for the specified provider
|
||||||
|
//
|
||||||
|
function onClickLogin(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
// TODO check subject for provider viability
|
||||||
|
return oauth3.authenticate({
|
||||||
|
subject: $('.js-auth-subject').value
|
||||||
|
}).then(function (session) {
|
||||||
|
|
||||||
|
console.info('Authentication was Successful:');
|
||||||
|
console.log(session);
|
||||||
|
|
||||||
|
// You can use the PPID (or preferably a hash of it) as the login for your app
|
||||||
|
// (it securely functions as both username and password which is known only by your app)
|
||||||
|
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
||||||
|
//
|
||||||
|
console.info('Secure PPID (aka subject):', session.token.sub);
|
||||||
|
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json'
|
||||||
|
.replace(/:sub/g, session.token.sub)
|
||||||
|
.replace(/:kid/g, session.token.iss)
|
||||||
|
, session: session
|
||||||
|
}).then(function (resp) {
|
||||||
|
console.info("Public Key:");
|
||||||
|
console.log(resp.data);
|
||||||
|
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.oauth3.org/api/issuer@oauth3.org/acl/profile'
|
||||||
|
, session: session
|
||||||
|
}).then(function (resp) {
|
||||||
|
|
||||||
|
console.info("Inspect Token:");
|
||||||
|
console.log(resp.data);
|
||||||
|
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.telebit.cloud/api/telebit.cloud/account'
|
||||||
|
, session: session
|
||||||
|
}).then(function (resp) {
|
||||||
|
|
||||||
|
console.info("Telebit Account:");
|
||||||
|
console.log(resp.data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}, function (err) {
|
||||||
|
console.error('Authentication Failed:');
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('body form.js-auth-form').addEventListener('submit', onClickLogin);
|
||||||
|
onChangeProvider('oauth3.org');
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -29,6 +29,9 @@
|
||||||
<p>Friends enable friends to share anything, access anywhere, connect anytime.</p>
|
<p>Friends enable friends to share anything, access anywhere, connect anytime.</p>
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
|
<a href="account.html#/login">Login</a>
|
||||||
|
<a href="account.html#/create_account">Create Account</a>
|
||||||
|
|
||||||
<div style="width: 800px; margin: auto;">
|
<div style="width: 800px; margin: auto;">
|
||||||
<div>
|
<div>
|
||||||
<h2>Share and Test over HTTPS</h2>
|
<h2>Share and Test over HTTPS</h2>
|
||||||
|
@ -139,74 +142,6 @@ TCP
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="js-auth-form">
|
|
||||||
<input class="js-auth-subject" type="email"/>
|
|
||||||
<button class="js-auth-submit" type="submit">Login</button>
|
|
||||||
</form>
|
|
||||||
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
var OAUTH3 = window.OAUTH3;
|
|
||||||
var oauth3 = OAUTH3.create(window.location);
|
|
||||||
var $ = function () { return document.querySelector.apply(document, arguments); }
|
|
||||||
|
|
||||||
function onChangeProvider(providerUri) {
|
|
||||||
// example https://oauth3.org
|
|
||||||
return oauth3.setIdentityProvider(providerUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This opens up the login window for the specified provider
|
|
||||||
//
|
|
||||||
function onClickLogin(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
// TODO check subject for provider viability
|
|
||||||
return oauth3.authenticate({
|
|
||||||
subject: $('.js-auth-subject').value
|
|
||||||
}).then(function (session) {
|
|
||||||
|
|
||||||
console.info('Authentication was Successful:');
|
|
||||||
console.log(session);
|
|
||||||
|
|
||||||
// You can use the PPID (or preferably a hash of it) as the login for your app
|
|
||||||
// (it securely functions as both username and password which is known only by your app)
|
|
||||||
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
|
||||||
//
|
|
||||||
console.info('Secure PPID (aka subject):', session.token.sub);
|
|
||||||
|
|
||||||
return oauth3.request({
|
|
||||||
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json'
|
|
||||||
.replace(/:sub/g, session.token.sub)
|
|
||||||
.replace(/:kid/g, session.token.iss)
|
|
||||||
, session: session
|
|
||||||
}).then(function (resp) {
|
|
||||||
console.info("Public Key:");
|
|
||||||
console.log(resp.data);
|
|
||||||
|
|
||||||
return oauth3.request({
|
|
||||||
url: 'https://api.oauth3.org/api/issuer@oauth3.org/acl/profile'
|
|
||||||
, session: session
|
|
||||||
}).then(function (resp) {
|
|
||||||
|
|
||||||
console.info("Inspect Token:");
|
|
||||||
console.log(resp.data);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function (err) {
|
|
||||||
console.error('Authentication Failed:');
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
$('body form.js-auth-form').addEventListener('submit', onClickLogin);
|
|
||||||
onChangeProvider('oauth3.org');
|
|
||||||
}());
|
|
||||||
</script>
|
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -6,7 +6,9 @@ var util = require('util');
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var escapeHtml = require('escape-html');
|
var escapeHtml = require('escape-html');
|
||||||
var jwt = require('jsonwebtoken');
|
var jwt = require('jsonwebtoken');
|
||||||
var requestAsync = util.promisify(require('request'));
|
var requestAsync = util.promisify(require('@coolaj86/urequest'));
|
||||||
|
var readFileAsync = util.promisify(fs.readFile);
|
||||||
|
var mkdirpAsync = util.promisify(require('mkdirp'));
|
||||||
|
|
||||||
var _auths = module.exports._auths = {};
|
var _auths = module.exports._auths = {};
|
||||||
var Auths = {};
|
var Auths = {};
|
||||||
|
@ -77,6 +79,72 @@ Auths._clean = function () {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var sfs = require('safe-replace');
|
||||||
|
var Accounts = {};
|
||||||
|
Accounts._getTokenId = function (auth) {
|
||||||
|
return auth.data.sub + '@' + (auth.data.iss||'').replace(/\/|\\/g, '-');
|
||||||
|
};
|
||||||
|
Accounts._accPath = function (req, accId) {
|
||||||
|
return path.join(req._state.config.accountsDir, 'self', accId);
|
||||||
|
};
|
||||||
|
Accounts._subPath = function (req, id) {
|
||||||
|
return path.join(req._state.config.accountsDir, 'oauth3', id);
|
||||||
|
};
|
||||||
|
Accounts._setSub = function (req, id, subData) {
|
||||||
|
var subpath = Accounts._subPath(req, id);
|
||||||
|
return mkdirpAsync(subpath).then(function () {
|
||||||
|
return sfs.writeFileAsync(path.join(subpath, 'index.json'), JSON.stringify(subData));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Accounts._setAcc = function (req, accId, acc) {
|
||||||
|
var accpath = Accounts._accPath(req, accId);
|
||||||
|
return mkdirpAsync(accpath).then(function () {
|
||||||
|
return sfs.writeFileAsync(path.join(accpath, 'index.json'), JSON.stringify(acc));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Accounts.create = function (req) {
|
||||||
|
var id = Accounts._getTokenId(req.auth);
|
||||||
|
var acc = {
|
||||||
|
sub: crypto.randomBytes(16).toString('hex')
|
||||||
|
// TODO use something from the request to know which of the domains to use
|
||||||
|
, iss: req._state.config.webminDomain
|
||||||
|
, contacts: []
|
||||||
|
};
|
||||||
|
var accId = Accounts._getTokenId(acc);
|
||||||
|
acc.id = accId;
|
||||||
|
|
||||||
|
// TODO notify any non-authorized accounts that they've been added?
|
||||||
|
return Accounts.getBySub(req).then(function (subData) {
|
||||||
|
subData.accounts.push({ type: 'self', id: accId });
|
||||||
|
acc.contacts.push({ type: 'oauth3', id: subData.id, sub: subData.sub, iss: subData.iss });
|
||||||
|
return Accounts._setSub(req, id, subData).then(function () {
|
||||||
|
return Accounts._setAcc(req, accId, acc).then(function () {
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
// TODO an owner of an asset can give permission to another entity
|
||||||
|
// but that does not mean that that owner has access to that entity's things
|
||||||
|
// Example:
|
||||||
|
// A 3rd party login's email verification cannot be trusted for auth
|
||||||
|
// Only 1st party verifications can be trusted for authorization
|
||||||
|
Accounts.link = function (req) {
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
Accounts.getBySub = function (req) {
|
||||||
|
var id = Accounts._getTokenId(req.auth);
|
||||||
|
var subpath = Accounts._subPath(req, id);
|
||||||
|
return readFileAsync(path.join(subpath, 'index.json'), 'utf8').then(function (text) {
|
||||||
|
return JSON.parse(text);
|
||||||
|
}, function (/*err*/) {
|
||||||
|
return null;
|
||||||
|
}).then(function (links) {
|
||||||
|
return links || { id: id, sub: req.auth.sub, iss: req.auth.iss, accounts: [] };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function sendMail(state, auth) {
|
function sendMail(state, auth) {
|
||||||
console.log('[DEBUG] ext auth', auth);
|
console.log('[DEBUG] ext auth', auth);
|
||||||
/*
|
/*
|
||||||
|
@ -137,6 +205,179 @@ function sendMail(state, auth) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO replace with OAuth3 function
|
||||||
|
function oauth3Auth(req, res, next) {
|
||||||
|
var jwt = require('jsonwebtoken');
|
||||||
|
var verifyJwt = util.promisify(jwt.verify);
|
||||||
|
var token = (req.headers.authorization||'').replace(/^bearer /i, '');
|
||||||
|
var auth;
|
||||||
|
var authData;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_NOAUTH"
|
||||||
|
, message: "no authorization header"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
authData = jwt.decode(token, { complete: true });
|
||||||
|
} catch(e) {
|
||||||
|
authData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authData) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_PARSEAUTH"
|
||||||
|
, message: "could not parse authorization header as JWT"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = authData.payload;
|
||||||
|
if (!auth.sub && ('*' === auth.aud || '*' === auth.azp)) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_NOIMPL"
|
||||||
|
, message: "missing 'sub' and a wildcard 'azp' or 'aud' indicates that this is an exchange token,"
|
||||||
|
+ " however, this app has not yet implemented opaque token exchange"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([ 'sub', 'iss' ].some(function (key) {
|
||||||
|
if ('string' !== typeof auth[key]) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_PARSEAUTH"
|
||||||
|
, message: "could not read property '" + key + "' of authorization token"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) { return; }
|
||||||
|
if ([ 'kid' ].some(function (key) {
|
||||||
|
if (/\/|\\/.test(authData.header[key])) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_PARSESUBJECT"
|
||||||
|
, message: "'" + key + "' `" + JSON.stringify(authData.header[key]) + "' contains invalid characters"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) { return; }
|
||||||
|
if ([ 'sub', 'kid' ].some(function (key) {
|
||||||
|
if (/\/|\\/.test(auth[key])) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_PARSESUBJECT"
|
||||||
|
, message: "'" + key + "' `" + JSON.stringify(auth[key]) + "' contains invalid characters"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) { return; }
|
||||||
|
|
||||||
|
// TODO needs to work with app:// and custom://
|
||||||
|
function prefixHttps(str) {
|
||||||
|
return (str||'').replace(/^(https?:\/\/)?/i, 'https://');
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = require('url');
|
||||||
|
var discoveryUrl = url.resolve(prefixHttps(auth.iss), '_apis/oauth3.org/index.json');
|
||||||
|
console.log('discoveryUrl: ', discoveryUrl, auth.iss);
|
||||||
|
return requestAsync({
|
||||||
|
url: discoveryUrl
|
||||||
|
, json: true
|
||||||
|
}).then(function (resp) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// it may be necessary to exchange the token,
|
||||||
|
|
||||||
|
if (200 !== resp.statusCode || 'object' !== typeof resp.body || !resp.body.retrieve_jwk
|
||||||
|
|| 'string' !== typeof resp.body.retrieve_jwk.url || 'string' !== typeof resp.body.api) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_NOTFOUND"
|
||||||
|
, message: resp.statusCode + ": issuer `" + JSON.stringify(auth.iss)
|
||||||
|
+ "' does not declare 'api' & 'retrieve_key' and hence the token you provided cannot be verified."
|
||||||
|
, _status: resp.statusCode
|
||||||
|
, _url: discoveryUrl
|
||||||
|
, _body: resp.body
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var keyUrl = url.resolve(
|
||||||
|
prefixHttps(resp.body.api).replace(/:hostname/g, auth.iss)
|
||||||
|
, resp.body.retrieve_jwk.url
|
||||||
|
.replace(/:hostname/g, auth.iss)
|
||||||
|
.replace(/:sub/g, auth.sub)
|
||||||
|
// TODO
|
||||||
|
.replace(/:kid/g, authData.header.kid || auth.iss)
|
||||||
|
);
|
||||||
|
console.log('keyUrl: ', keyUrl);
|
||||||
|
return requestAsync({
|
||||||
|
url: keyUrl
|
||||||
|
, json: true
|
||||||
|
}).then(function (resp) {
|
||||||
|
var jwk = resp.body;
|
||||||
|
if (200 !== resp.statusCode || 'object' !== typeof resp.body) {
|
||||||
|
//headers.authorization
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_NOTFOUND"
|
||||||
|
, message: resp.statusCode + ": did not retrieve public key from `" + JSON.stringify(auth.iss)
|
||||||
|
+ "' for token validation and hence the token you provided cannot be verified."
|
||||||
|
, _status: resp.statusCode
|
||||||
|
, _url: keyUrl
|
||||||
|
, _body: resp.body
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubpem;
|
||||||
|
try {
|
||||||
|
pubpem = require('jwk-to-pem')(jwk, { private: false });
|
||||||
|
} catch(e) {
|
||||||
|
pubpem = null;
|
||||||
|
}
|
||||||
|
return verifyJwt(token, pubpem, {
|
||||||
|
algorithms: [ 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512' ]
|
||||||
|
}).then(function (decoded) {
|
||||||
|
if (!decoded) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: "E_UNVERIFIED"
|
||||||
|
, message: "retrieved jwk does not verify provided token."
|
||||||
|
, _jwk: jwk
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
req.auth = {};
|
||||||
|
req.auth.jwt = token;
|
||||||
|
req.auth.data = auth;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: err.code || "E_GENERIC"
|
||||||
|
, message: err.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.pairRequest = function (opts) {
|
module.exports.pairRequest = function (opts) {
|
||||||
console.log("It's auth'n time!");
|
console.log("It's auth'n time!");
|
||||||
var state = opts.state;
|
var state = opts.state;
|
||||||
|
@ -357,9 +598,36 @@ var urls = {
|
||||||
pairState: '/api/telebit.cloud/pair_state/:id'
|
pairState: '/api/telebit.cloud/pair_state/:id'
|
||||||
};
|
};
|
||||||
staticApp.use('/', express.static(path.join(__dirname, 'admin')));
|
staticApp.use('/', express.static(path.join(__dirname, 'admin')));
|
||||||
app.use('/api', CORS({}));
|
app.use('/api', CORS({
|
||||||
|
credentials: true
|
||||||
|
, headers: [ 'Authorization', 'X-Requested-With', 'X-HTTP-Method-Override', 'Content-Type', 'Accept' ]
|
||||||
|
}));
|
||||||
app.use('/api', bodyParser.json());
|
app.use('/api', bodyParser.json());
|
||||||
|
|
||||||
|
app.use('/api/telebit.cloud/account', oauth3Auth);
|
||||||
|
app.get('/api/telebit.cloud/account', function (req, res) {
|
||||||
|
Accounts.getBySub(req).then(function (subData) {
|
||||||
|
res.send(subData);
|
||||||
|
}, function (err) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
code: err.code || "E_GENERIC"
|
||||||
|
, message: err.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.post('/api/telebit.cloud/account', function (req, res) {
|
||||||
|
return Accounts.create(req).then(function (acc) {
|
||||||
|
res.send({
|
||||||
|
success: true
|
||||||
|
, id: acc.id
|
||||||
|
, sub: acc.sub
|
||||||
|
, iss: acc.iss
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// From Device (which knows id, but not secret)
|
// From Device (which knows id, but not secret)
|
||||||
app.post('/api/telebit.cloud/pair_request', function (req, res) {
|
app.post('/api/telebit.cloud/pair_request', function (req, res) {
|
||||||
var auth = req.body;
|
var auth = req.body;
|
||||||
|
@ -383,7 +651,7 @@ app.post('/api/telebit.cloud/pair_request', function (req, res) {
|
||||||
app.get('/api/telebit.cloud/pair_request/:secret', function (req, res) {
|
app.get('/api/telebit.cloud/pair_request/:secret', function (req, res) {
|
||||||
var secret = req.params.secret;
|
var secret = req.params.secret;
|
||||||
var auth = Auths.getBySecret(secret);
|
var auth = Auths.getBySecret(secret);
|
||||||
var crypto = require('crypto');
|
//var crypto = require('crypto');
|
||||||
var response = {};
|
var response = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
11
package.json
11
package.json
|
@ -37,8 +37,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js",
|
"homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coolaj86/urequest": "^1.1.1",
|
"@coolaj86/urequest": "^1.2.1",
|
||||||
"bluebird": "^3.5.1",
|
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"cluster-store": "^2.0.8",
|
"cluster-store": "^2.0.8",
|
||||||
"connect-cors": "^0.5.6",
|
"connect-cors": "^0.5.6",
|
||||||
|
@ -48,16 +47,22 @@
|
||||||
"greenlock": "^2.2.4",
|
"greenlock": "^2.2.4",
|
||||||
"human-readable-ids": "^1.0.4",
|
"human-readable-ids": "^1.0.4",
|
||||||
"js-yaml": "^3.11.0",
|
"js-yaml": "^3.11.0",
|
||||||
"jsonwebtoken": "^8.2.1",
|
"jsonwebtoken": "^8.3.0",
|
||||||
|
"jwk-to-pem": "^2.0.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
"nowww": "^1.2.1",
|
"nowww": "^1.2.1",
|
||||||
"proxy-packer": "^1.4.3",
|
"proxy-packer": "^1.4.3",
|
||||||
"recase": "^1.0.4",
|
"recase": "^1.0.4",
|
||||||
"redirect-https": "^1.1.5",
|
"redirect-https": "^1.1.5",
|
||||||
"request": "^2.87.0",
|
"request": "^2.87.0",
|
||||||
|
"safe-replace": "^1.0.3",
|
||||||
"serve-static": "^1.13.2",
|
"serve-static": "^1.13.2",
|
||||||
"sni": "^1.0.0",
|
"sni": "^1.0.0",
|
||||||
"ws": "^5.1.1"
|
"ws": "^5.1.1"
|
||||||
},
|
},
|
||||||
|
"trulyOptionalDependencies": {
|
||||||
|
"bluebird": "^3.5.1"
|
||||||
|
},
|
||||||
"engineStrict": true,
|
"engineStrict": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.2.1"
|
"node": "10.2.1"
|
||||||
|
|
Loading…
Reference in New Issue