Merge branch 'commercial' of https://git.ppl.family/ppl/commercial.telebit-relay.js into commercial
This commit is contained in:
commit
de9aab8195
|
@ -1,21 +1,24 @@
|
||||||
email: 'jon@example.com' # must be valid (for certificate recovery and security alerts)
|
email: coolaj86@gmail.com
|
||||||
agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
|
agree_tos: true
|
||||||
community_member: true # receive infrequent relevant updates
|
community_member: true
|
||||||
telemetry: true # contribute to project telemetric data
|
telemetry: true
|
||||||
webmin_domain: example.com
|
webmin_domain: example.com
|
||||||
shared_domain: xm.pl
|
api_domain: example.com
|
||||||
servernames: # hostnames that direct to the Telebit Relay admin console
|
shared_domain: example.com
|
||||||
- telebit.example.com
|
servernames:
|
||||||
- telebit.example.net
|
- www.example.com
|
||||||
vhost: /srv/www/:hostname # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public)
|
- example.com
|
||||||
|
- api.example.com
|
||||||
|
vhost: /srv/www/:hostname
|
||||||
greenlock:
|
greenlock:
|
||||||
version: 'draft-11'
|
version: 'draft-11'
|
||||||
server: 'https://acme-v02.api.letsencrypt.org/directory'
|
server: 'https://acme-v02.api.letsencrypt.org/directory'
|
||||||
store:
|
store:
|
||||||
strategy: le-store-certbot # certificate storage plugin
|
strategy: le-store-certbot
|
||||||
config_dir: /etc/acme # directory for ssl certificates
|
config_dir: /opt/telebit-relay/etc/acme
|
||||||
secret: '' # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))"
|
secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
mailer:
|
mailer:
|
||||||
url: 'https://api.mailgun.net/v3/EXAMPLE.COM/messages'
|
url: 'https://api.mailgun.net/v3/EXAMPLE.COM/messages'
|
||||||
api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
from: 'Example Mailer <MALIER@EXAMPLE.COM>'
|
from: 'Example Mailer <MALIER@EXAMPLE.COM>'
|
||||||
|
debug: true
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
servernames: [ 'telebit.cloud' ]
|
||||||
|
email: 'coolaj86@gmail.com'
|
||||||
|
agree_tos: true
|
||||||
|
community_member: false
|
||||||
|
vhost: /srv/www/:hostname
|
|
@ -179,6 +179,9 @@ if [ ! -f "$TELEBIT_RELAY_PATH/etc/$my_app.yml" ]; then
|
||||||
sudo bash -c "echo 'email: $my_email' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "echo 'email: $my_email' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
sudo bash -c "echo 'secret: $my_secret' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "echo 'secret: $my_secret' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
sudo bash -c "echo 'servernames: [ $my_servername ]' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "echo 'servernames: [ $my_servername ]' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
|
sudo bash -c "echo 'webmin_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
|
sudo bash -c "echo 'api_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
|
sudo bash -c "echo 'shared_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
sudo bash -c "cat $TELEBIT_RELAY_PATH/examples/$my_app.yml.tpl >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "cat $TELEBIT_RELAY_PATH/examples/$my_app.yml.tpl >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,24 @@
|
||||||
|
|
||||||
var Devices = module.exports;
|
var Devices = module.exports;
|
||||||
Devices.add = function (store, servername, newDevice) {
|
Devices.add = function (store, servername, newDevice) {
|
||||||
var devices = store[servername] || [];
|
if (!store[servername]) {
|
||||||
|
store[servername] = [];
|
||||||
|
}
|
||||||
|
var devices = store[servername];
|
||||||
devices.push(newDevice);
|
devices.push(newDevice);
|
||||||
store[servername] = devices;
|
};
|
||||||
|
Devices.alias = function (store, servername, alias) {
|
||||||
|
if (!store[servername]) {
|
||||||
|
store[servername] = [];
|
||||||
|
}
|
||||||
|
if (!store[servername]._primary) {
|
||||||
|
store[servername]._primary = servername;
|
||||||
|
}
|
||||||
|
if (!store[servername].aliases) {
|
||||||
|
store[servername].aliases = {};
|
||||||
|
}
|
||||||
|
store[alias] = store[servername];
|
||||||
|
store[servername].aliases[alias] = true;
|
||||||
};
|
};
|
||||||
Devices.remove = function (store, servername, device) {
|
Devices.remove = function (store, servername, device) {
|
||||||
var devices = store[servername] || [];
|
var devices = store[servername] || [];
|
||||||
|
@ -17,9 +32,11 @@ Devices.remove = function (store, servername, device) {
|
||||||
return devices.splice(index, 1)[0];
|
return devices.splice(index, 1)[0];
|
||||||
};
|
};
|
||||||
Devices.list = function (store, servername) {
|
Devices.list = function (store, servername) {
|
||||||
|
// efficient lookup first
|
||||||
if (store[servername] && store[servername].length) {
|
if (store[servername] && store[servername].length) {
|
||||||
return store[servername];
|
return store[servername]._primary && store[store[servername]._primary] || store[servername];
|
||||||
}
|
}
|
||||||
|
|
||||||
// There wasn't an exact match so check any of the wildcard domains, sorted longest
|
// There wasn't an exact match so check any of the wildcard domains, sorted longest
|
||||||
// first so the one with the biggest natural match with be found first.
|
// first so the one with the biggest natural match with be found first.
|
||||||
var deviceList = [];
|
var deviceList = [];
|
||||||
|
@ -28,10 +45,19 @@ Devices.list = function (store, servername) {
|
||||||
}).sort(function (a, b) {
|
}).sort(function (a, b) {
|
||||||
return b.length - a.length;
|
return b.length - a.length;
|
||||||
}).some(function (pattern) {
|
}).some(function (pattern) {
|
||||||
|
// '.example.com' = '*.example.com'.split(1)
|
||||||
var subPiece = pattern.slice(1);
|
var subPiece = pattern.slice(1);
|
||||||
|
// '.com' = 'sub.example.com'.slice(-4)
|
||||||
|
// '.example.com' = 'sub.example.com'.slice(-12)
|
||||||
if (subPiece === servername.slice(-subPiece.length)) {
|
if (subPiece === servername.slice(-subPiece.length)) {
|
||||||
console.log('"'+servername+'" matches "'+pattern+'"');
|
console.log('[Devices.list] "'+servername+'" matches "'+pattern+'"');
|
||||||
deviceList = store[pattern];
|
deviceList = store[pattern];
|
||||||
|
|
||||||
|
// Devices.alias(store, '*.example.com', 'sub.example.com'
|
||||||
|
// '*.example.com' retrieves a reference to 'example.com'
|
||||||
|
// and this reference then also referenced by 'sub.example.com'
|
||||||
|
// Hence this O(n) check is replaced with the O(1) check above
|
||||||
|
Devices.alias(store, pattern, servername);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
_apis
|
|
@ -0,0 +1 @@
|
||||||
|
oauth3.org
|
|
@ -0,0 +1 @@
|
||||||
|
../assets/oauth3.org/_apis/oauth3.org
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8e2e09f5823ae919c615c9c3b21114e01096b1ee
|
|
@ -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>
|
||||||
|
|
|
@ -19,7 +19,7 @@ function checkStatus() {
|
||||||
}
|
}
|
||||||
if ('complete' === data.status) {
|
if ('complete' === data.status) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.document.body.innerHTML += ('<img src="https://' + domainname + '/_apis/telebit.cloud/clear.gif">');
|
//window.document.body.innerHTML += ('<img src="https://' + domainname + '/_apis/telebit.cloud/clear.gif">');
|
||||||
// TODO once this is loaded (even error) Let's Encrypt is done,
|
// TODO once this is loaded (even error) Let's Encrypt is done,
|
||||||
// then it's time to redirect to the domain. Yay!
|
// then it's time to redirect to the domain. Yay!
|
||||||
}, 1 * 1000);
|
}, 1 * 1000);
|
||||||
|
|
|
@ -6,7 +6,16 @@ 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 PromiseA;
|
||||||
|
try {
|
||||||
|
PromiseA = require('bluebird');
|
||||||
|
} catch(e) {
|
||||||
|
PromiseA = global.Promise;
|
||||||
|
}
|
||||||
|
|
||||||
var _auths = module.exports._auths = {};
|
var _auths = module.exports._auths = {};
|
||||||
var Auths = {};
|
var Auths = {};
|
||||||
|
@ -77,6 +86,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);
|
||||||
/*
|
/*
|
||||||
|
@ -132,8 +207,188 @@ function sendMail(state, auth) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log("[DEBUG] email was sent, or so they say");
|
// anything in the 200 range
|
||||||
console.log(resp.body);
|
if (2 === Math.floor(resp.statusCode / 100)) {
|
||||||
|
console.log("[DEBUG] email was sent, or so they say");
|
||||||
|
} else {
|
||||||
|
console.error("[Error] email failed to send, or so they say:");
|
||||||
|
console.error(resp.headers);
|
||||||
|
console.error(resp.statusCode, resp.body);
|
||||||
|
return PromiseA.reject(new Error("Error sending email: " + resp.statusCode + " " + resp.body));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,20 +411,24 @@ module.exports.pairRequest = function (opts) {
|
||||||
, aud: state.config.webminDomain
|
, aud: state.config.webminDomain
|
||||||
, iat: Math.round(now / 1000)
|
, iat: Math.round(now / 1000)
|
||||||
, id: authReq.id
|
, id: authReq.id
|
||||||
|
, sub: authReq.subject
|
||||||
, pin: pin
|
, pin: pin
|
||||||
, hostname: authReq.hostname
|
, hostname: authReq.hostname
|
||||||
};
|
};
|
||||||
auth = {
|
auth = {
|
||||||
id: authReq.id
|
id: authReq.id
|
||||||
, secret: authReq.secret
|
, secret: authReq.secret
|
||||||
|
, subject: authReq.subject
|
||||||
, pin: pin
|
, pin: pin
|
||||||
, dt: now
|
, dt: now
|
||||||
, exp: now + (2 * 60 * 60 * 1000)
|
, exp: now + (2 * 60 * 60 * 1000)
|
||||||
, authnData: authnData
|
|
||||||
, authn: jwt.sign(authnData, state.secret)
|
|
||||||
, request: authReq
|
, request: authReq
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Setting extra authnData
|
||||||
|
auth.authn = jwt.sign(authnData, state.secret);
|
||||||
authnData.jwt = auth.authn;
|
authnData.jwt = auth.authn;
|
||||||
|
auth.authnData = authnData;
|
||||||
Auths.set(auth, authReq.id, authReq.secret);
|
Auths.set(auth, authReq.id, authReq.secret);
|
||||||
return authnData;
|
return authnData;
|
||||||
});
|
});
|
||||||
|
@ -179,16 +438,23 @@ module.exports.pairPin = function (opts) {
|
||||||
return state.Promise.resolve().then(function () {
|
return state.Promise.resolve().then(function () {
|
||||||
var pin = opts.pin;
|
var pin = opts.pin;
|
||||||
var secret = opts.secret;
|
var secret = opts.secret;
|
||||||
var auth = Auths.getBySecretAndPin(secret, pin);
|
var auth = Auths.getBySecret(secret);
|
||||||
|
|
||||||
|
console.log('[pairPin] validating secret and pin');
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
throw new Error("I can't even right now - bad magic link or pairing code");
|
throw new Error("Invalid magic link token '" + secret + "'");
|
||||||
|
}
|
||||||
|
auth = Auths.getBySecretAndPin(secret, pin);
|
||||||
|
if (!auth) {
|
||||||
|
throw new Error("Invalid pairing code '" + pin + "' for magic link token '" + secret + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth._offered) {
|
if (auth._offered) {
|
||||||
|
console.log('[pairPin] already has offer to return');
|
||||||
return auth._offered;
|
return auth._offered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[pairPin] generating offer');
|
||||||
var hri = require('human-readable-ids').hri;
|
var hri = require('human-readable-ids').hri;
|
||||||
var hrname = hri.random() + '.' + state.config.sharedDomain;
|
var hrname = hri.random() + '.' + state.config.sharedDomain;
|
||||||
// TODO check used / unused names and ports
|
// TODO check used / unused names and ports
|
||||||
|
@ -202,9 +468,14 @@ module.exports.pairPin = function (opts) {
|
||||||
};
|
};
|
||||||
var pathname = path.join(__dirname, 'emails', auth.subject + '.' + hrname + '.data');
|
var pathname = path.join(__dirname, 'emails', auth.subject + '.' + hrname + '.data');
|
||||||
auth.authz = jwt.sign(authzData, state.secret);
|
auth.authz = jwt.sign(authzData, state.secret);
|
||||||
|
auth.authzData = authzData;
|
||||||
authzData.jwt = auth.authz;
|
authzData.jwt = auth.authz;
|
||||||
|
auth._offered = authzData;
|
||||||
if (auth.resolve) {
|
if (auth.resolve) {
|
||||||
|
console.log('[pairPin] resolving');
|
||||||
auth.resolve(auth);
|
auth.resolve(auth);
|
||||||
|
} else {
|
||||||
|
console.log('[pairPin] not resolvable');
|
||||||
}
|
}
|
||||||
fs.writeFile(pathname, JSON.stringify(authzData), function (err) {
|
fs.writeFile(pathname, JSON.stringify(authzData), function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -212,16 +483,32 @@ module.exports.pairPin = function (opts) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
auth._offered = authzData;
|
|
||||||
return authzData;
|
return authzData;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// From a WS connection
|
// From a WS connection
|
||||||
|
module.exports.authHelper = function (meta) {
|
||||||
|
console.log('[authHelper] 1');
|
||||||
|
var state = meta.state;
|
||||||
|
console.log('[authHelper] 2');
|
||||||
|
return state.Promise.resolve().then(function () {
|
||||||
|
console.log('[authHelper] 3');
|
||||||
|
var auth = meta.session;
|
||||||
|
console.log('[authHelper] 4', auth);
|
||||||
|
if (!auth || 'string' !== typeof auth.authz || 'object' !== typeof auth.authzData) {
|
||||||
|
console.log('[authHelper] 5');
|
||||||
|
console.error("[SANITY FAIL] should not complete auth without authz data and access_token");
|
||||||
|
console.error(auth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("[authHelper] passing authzData right along", auth.authzData);
|
||||||
|
return auth.authzData;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// opts = { state: state, auth: auth_request OR access_token }
|
||||||
module.exports.authenticate = function (opts) {
|
module.exports.authenticate = function (opts) {
|
||||||
var jwt = require('jsonwebtoken');
|
var jwt = require('jsonwebtoken');
|
||||||
var jwtoken = opts.auth;
|
|
||||||
var authReq = opts.auth;
|
|
||||||
var state = opts.state;
|
var state = opts.state;
|
||||||
var auth;
|
var auth;
|
||||||
var decoded;
|
var decoded;
|
||||||
|
@ -241,7 +528,6 @@ module.exports.authenticate = function (opts) {
|
||||||
// this will cause the websocket to disconnect
|
// this will cause the websocket to disconnect
|
||||||
|
|
||||||
auth.resolve = function (auth) {
|
auth.resolve = function (auth) {
|
||||||
opts.auth = auth.authz;
|
|
||||||
auth.resolve = null;
|
auth.resolve = null;
|
||||||
auth.reject = null;
|
auth.reject = null;
|
||||||
// NOTE XXX: This is premature in the sense that we can't be 100% sure
|
// NOTE XXX: This is premature in the sense that we can't be 100% sure
|
||||||
|
@ -249,7 +535,12 @@ module.exports.authenticate = function (opts) {
|
||||||
// sort of check that the client actually received the token
|
// sort of check that the client actually received the token
|
||||||
// (i.e. when the grant event gets an ack)
|
// (i.e. when the grant event gets an ack)
|
||||||
auth._claimed = true;
|
auth._claimed = true;
|
||||||
return state.defaults.authenticate(opts.auth).then(resolve);
|
// this is probably not necessary anymore
|
||||||
|
opts.auth = auth.authz;
|
||||||
|
return module.exports.authHelper({
|
||||||
|
state: state
|
||||||
|
, session: auth
|
||||||
|
}).then(resolve);
|
||||||
};
|
};
|
||||||
auth.reject = function (err) {
|
auth.reject = function (err) {
|
||||||
auth.resolve = null;
|
auth.resolve = null;
|
||||||
|
@ -261,41 +552,43 @@ module.exports.authenticate = function (opts) {
|
||||||
return auth.promise;
|
return auth.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('object' === typeof authReq && /^.+@.+\..+$/.test(authReq.subject)) {
|
// Promise Authz on Auth Creds
|
||||||
console.log("[ext token] Looks Like Auth Object");
|
// TODO: remove
|
||||||
|
if ('object' === typeof opts.auth && /^.+@.+\..+$/.test(opts.auth.subject)) {
|
||||||
|
console.log("[wss.ext.authenticate] [1] Request Pair for Credentials");
|
||||||
return module.exports.pairRequest(opts).then(function (authnData) {
|
return module.exports.pairRequest(opts).then(function (authnData) {
|
||||||
console.log("[ext token] Promises Like Auth Object");
|
console.log("[wss.ext.authenticate] [2] Promise Authz on Pair Complete");
|
||||||
var auth = Auths.get(authnData.id);
|
var auth = Auths.get(authnData.id);
|
||||||
return getPromise(auth);
|
return getPromise(auth);
|
||||||
|
//getPromise(auth);
|
||||||
|
//return state.defaults.authenticate(authnData.jwt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[ext token] Trying Token Parse");
|
|
||||||
try {
|
try {
|
||||||
decoded = jwt.decode(jwtoken, { complete: true });
|
decoded = jwt.decode(opts.auth, { complete: true });
|
||||||
auth = Auths.get(decoded.payload.id);
|
auth = Auths.get(decoded.payload.id);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log("[ext token] Token Did Not Parse");
|
console.log("[wss.ext.authenticate] [Error] could not parse token");
|
||||||
decoded = null;
|
decoded = null;
|
||||||
}
|
}
|
||||||
|
console.log("[wss.ext.authenticate] incoming token decoded:");
|
||||||
console.log("[ext token] decoded auth token:");
|
|
||||||
console.log(decoded);
|
console.log(decoded);
|
||||||
|
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
console.log("[ext token] did not find auth object");
|
console.log("[wss.ext.authenticate] no session / auth handshake. Pass to default auth");
|
||||||
|
return state.defaults.authenticate(opts.auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO technically this could leak the token through a timing attack
|
// TODO technically this could leak the token through a timing attack
|
||||||
// but it would require already knowing the semi-secret id and having
|
// but it would require already knowing the semi-secret id and having
|
||||||
// completed the pair code
|
// completed the pair code
|
||||||
if (auth && (auth.authn === jwtoken || auth.authz === jwtoken)) {
|
if (auth.authn === opts.auth || auth.authz === opts.auth) {
|
||||||
if (!auth.authz) {
|
if (!auth.authz) {
|
||||||
console.log("[ext token] Promise Authz");
|
console.log("[wss.ext.authenticate] Create authz promise and passthru");
|
||||||
return getPromise(auth);
|
return getPromise(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[ext token] Use Available Authz");
|
|
||||||
// If they used authn but now authz is available, use authz
|
// If they used authn but now authz is available, use authz
|
||||||
// (i.e. connects, but no domains or ports)
|
// (i.e. connects, but no domains or ports)
|
||||||
opts.auth = auth.authz;
|
opts.auth = auth.authz;
|
||||||
|
@ -304,8 +597,8 @@ module.exports.authenticate = function (opts) {
|
||||||
auth._claimed = true;
|
auth._claimed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[ext token] Continue With Auth Token");
|
console.log("[wss.ext.authenticate] Already using authz, skipping promise");
|
||||||
return state.defaults.authenticate(opts.auth);
|
return module.exports.authHelper({ state: state, session: auth });
|
||||||
};
|
};
|
||||||
|
|
||||||
//var loaded = false;
|
//var loaded = false;
|
||||||
|
@ -319,9 +612,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;
|
||||||
|
@ -345,7 +665,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 = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
102
lib/relay.js
102
lib/relay.js
|
@ -1,10 +1,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var url = require('url');
|
var url = require('url');
|
||||||
var PromiseA = require('bluebird');
|
|
||||||
var sni = require('sni');
|
var sni = require('sni');
|
||||||
var Packer = require('proxy-packer');
|
var Packer = require('proxy-packer');
|
||||||
var PortServers = {};
|
var PortServers = {};
|
||||||
|
var PromiseA;
|
||||||
|
try {
|
||||||
|
PromiseA = require('bluebird');
|
||||||
|
} catch(e) {
|
||||||
|
PromiseA = global.Promise;
|
||||||
|
}
|
||||||
|
|
||||||
function timeoutPromise(duration) {
|
function timeoutPromise(duration) {
|
||||||
return new PromiseA(function (resolve) {
|
return new PromiseA(function (resolve) {
|
||||||
|
@ -240,10 +245,10 @@ var Server = {
|
||||||
|
|
||||||
return result || srv.socketId;
|
return result || srv.socketId;
|
||||||
}
|
}
|
||||||
, onAuth: function onAuth(state, srv, newAuth, grant) {
|
, onAuth: function onAuth(state, srv, rawAuth, grant) {
|
||||||
console.log('\n[relay.js] onAuth');
|
console.log('\n[relay.js] onAuth');
|
||||||
console.log(newAuth);
|
console.log(rawAuth);
|
||||||
console.log(grant);
|
//console.log(grant);
|
||||||
//var stringauth;
|
//var stringauth;
|
||||||
var err;
|
var err;
|
||||||
if (!grant || 'object' !== typeof grant) {
|
if (!grant || 'object' !== typeof grant) {
|
||||||
|
@ -253,25 +258,37 @@ var Server = {
|
||||||
return state.Promise.reject(err);
|
return state.Promise.reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('string' !== typeof newAuth) {
|
if ('string' !== typeof rawAuth) {
|
||||||
newAuth = JSON.stringify(newAuth);
|
rawAuth = JSON.stringify(rawAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('check for upgrade token');
|
// TODO don't fire the onAuth event on non-authz updates
|
||||||
if (grant.jwt && newAuth !== grant.jwt) {
|
if (!grant.jwt && !(grant.domains||[]).length && !(grant.ports||[]).length) {
|
||||||
console.log('new token to send back');
|
console.log("[onAuth] nothing to offer at all");
|
||||||
// Access Token
|
return null;
|
||||||
Server.sendTunnelMsg(
|
}
|
||||||
srv
|
|
||||||
, null
|
console.log('[onAuth] check for upgrade token');
|
||||||
, [ 3
|
//console.log(grant);
|
||||||
, 'access_token'
|
if (grant.jwt) {
|
||||||
, { jwt: grant.jwt }
|
if (rawAuth !== grant.jwt) {
|
||||||
]
|
console.log('[onAuth] new token to send back');
|
||||||
, 'control'
|
}
|
||||||
);
|
// TODO only send token when new
|
||||||
// these aren't needed internally once they're sent
|
if (true) {
|
||||||
grant.jwt = null;
|
// Access Token
|
||||||
|
Server.sendTunnelMsg(
|
||||||
|
srv
|
||||||
|
, null
|
||||||
|
, [ 3
|
||||||
|
, 'access_token'
|
||||||
|
, { jwt: grant.jwt }
|
||||||
|
]
|
||||||
|
, 'control'
|
||||||
|
);
|
||||||
|
// these aren't needed internally once they're sent
|
||||||
|
grant.jwt = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -288,18 +305,20 @@ var Server = {
|
||||||
return state.Promise.reject(err);
|
return state.Promise.reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('strolling through pleasantries');
|
console.log('[onAuth] strolling through pleasantries');
|
||||||
// Add the custom properties we need to manage this remote, then add it to all the relevant
|
// Add the custom properties we need to manage this remote, then add it to all the relevant
|
||||||
// domains and the list of all this websocket's grants.
|
// domains and the list of all this websocket's grants.
|
||||||
grant.domains.forEach(function (domainname) {
|
grant.domains.forEach(function (domainname) {
|
||||||
console.log('add', domainname, 'to device lists');
|
console.log('add', domainname, 'to device lists');
|
||||||
srv.domainsMap[domainname] = true;
|
srv.domainsMap[domainname] = true;
|
||||||
Devices.add(state.deviceLists, domainname, srv);
|
Devices.add(state.deviceLists, domainname, srv);
|
||||||
|
// TODO allow subs to go to individual devices
|
||||||
|
Devices.alias(state.deviceLists, domainname, '*.' + domainname);
|
||||||
});
|
});
|
||||||
srv.domains = Object.keys(srv.domainsMap);
|
srv.domains = Object.keys(srv.domainsMap);
|
||||||
srv.currentDesc = (grant.device && (grant.device.id || grant.device.hostname)) || srv.domains.join(',');
|
srv.currentDesc = (grant.device && (grant.device.id || grant.device.hostname)) || srv.domains.join(',');
|
||||||
grant.currentDesc = (grant.device && (grant.device.id || grant.device.hostname)) || grant.domains.join(',');
|
grant.currentDesc = (grant.device && (grant.device.id || grant.device.hostname)) || grant.domains.join(',');
|
||||||
grant.srv = srv;
|
//grant.srv = srv;
|
||||||
//grant.ws = srv.ws;
|
//grant.ws = srv.ws;
|
||||||
//grant.upgradeReq = srv.upgradeReq;
|
//grant.upgradeReq = srv.upgradeReq;
|
||||||
grant.clients = {};
|
grant.clients = {};
|
||||||
|
@ -344,7 +363,7 @@ var Server = {
|
||||||
}
|
}
|
||||||
grant.ports.forEach(openPort);
|
grant.ports.forEach(openPort);
|
||||||
|
|
||||||
srv.grants[newAuth] = grant;
|
srv.grants[rawAuth] = grant;
|
||||||
console.info("[ws] authorized", srv.socketId, "for", grant.currentDesc);
|
console.info("[ws] authorized", srv.socketId, "for", grant.currentDesc);
|
||||||
|
|
||||||
console.log('notify of grants', grant.domains, grant.ports);
|
console.log('notify of grants', grant.domains, grant.ports);
|
||||||
|
@ -416,31 +435,35 @@ var Server = {
|
||||||
process.nextTick(function () { conn.resume(); });
|
process.nextTick(function () { conn.resume(); });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, addToken: function addToken(state, srv, newAuth) {
|
, addToken: function addToken(state, srv, rawAuth) {
|
||||||
console.log("addToken", newAuth);
|
console.log("[addToken]", rawAuth);
|
||||||
if (srv.grants[newAuth]) {
|
if (srv.grants[rawAuth]) {
|
||||||
console.log("addToken - duplicate");
|
console.log("addToken - duplicate");
|
||||||
// return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" };
|
// return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" };
|
||||||
return state.Promise.resolve(null);
|
return state.Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.authenticate({ auth: newAuth }).then(function (authnToken) {
|
return state.authenticate({ auth: rawAuth }).then(function (validatedTokenData) {
|
||||||
|
console.log('\n[relay.js] rawAuth');
|
||||||
console.log('\n[relay.js] newAuth');
|
console.log(rawAuth);
|
||||||
console.log(newAuth);
|
|
||||||
|
|
||||||
console.log('\n[relay.js] authnToken');
|
console.log('\n[relay.js] authnToken');
|
||||||
console.log(authnToken);
|
console.log(validatedTokenData);
|
||||||
|
|
||||||
if (authnToken.id) {
|
// For tracking state between token exchanges
|
||||||
state.srvs[authnToken.id] = state.srvs[authnToken.id] || {};
|
// and tacking on extra attributes (i.e. for extensions)
|
||||||
state.srvs[authnToken.id].updateAuth = function (validToken) {
|
// TODO close on delete
|
||||||
return Server.onAuth(state, srv, newAuth, validToken);
|
if (!state.srvs[validatedTokenData.id]) {
|
||||||
|
state.srvs[validatedTokenData.id] = {};
|
||||||
|
}
|
||||||
|
if (!state.srvs[validatedTokenData.id].updateAuth) {
|
||||||
|
// be sure to always pass latest srv since the connection may change
|
||||||
|
// and reuse the same token
|
||||||
|
state.srvs[validatedTokenData.id].updateAuth = function (srv, validatedTokenData) {
|
||||||
|
return Server.onAuth(state, srv, rawAuth, validatedTokenData);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
state.srvs[validatedTokenData.id].updateAuth(srv, validatedTokenData);
|
||||||
// will return rejection if necessary
|
|
||||||
return state.srvs[authnToken.id].updateAuth(authnToken);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, removeToken: function removeToken(state, srv, jwtoken) {
|
, removeToken: function removeToken(state, srv, jwtoken) {
|
||||||
|
@ -587,6 +610,7 @@ module.exports.create = function (state) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (initToken) {
|
if (initToken) {
|
||||||
|
console.log('[wss.onConnection] token provided in http headers');
|
||||||
return Server.addToken(state, srv, initToken).then(function () {
|
return Server.addToken(state, srv, initToken).then(function () {
|
||||||
Server.init(state, srv);
|
Server.init(state, srv);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
|
|
@ -46,6 +46,12 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
function tryTls() {
|
function tryTls() {
|
||||||
var vhost;
|
var vhost;
|
||||||
|
|
||||||
|
if (!servername) {
|
||||||
|
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
|
||||||
|
deferData('httpsInvalid');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.servernames.length) {
|
if (!state.servernames.length) {
|
||||||
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
||||||
deferData('httpsSetupServer');
|
deferData('httpsSetupServer');
|
||||||
|
@ -62,12 +68,6 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
console.log("TODO: use www bare redirect");
|
console.log("TODO: use www bare redirect");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!servername) {
|
|
||||||
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
|
|
||||||
deferData('httpsInvalid');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
var nextDevice = Devices.next(state.deviceLists, servername);
|
var nextDevice = Devices.next(state.deviceLists, servername);
|
||||||
if (!nextDevice) {
|
if (!nextDevice) {
|
||||||
|
|
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.3.2",
|
||||||
"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