From 6c2f039533f26fd37531be73bf34a4c1a514121b Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 23 Jun 2018 01:08:11 +0000 Subject: [PATCH 01/20] add subdomain config --- examples/telebit-relay.yml | 27 +++++++++++++++------------ examples/telebitd.real.yml | 5 +++++ installer/get.sh | 3 +++ 3 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 examples/telebitd.real.yml diff --git a/examples/telebit-relay.yml b/examples/telebit-relay.yml index 74e83d6..3afcdc4 100644 --- a/examples/telebit-relay.yml +++ b/examples/telebit-relay.yml @@ -1,17 +1,20 @@ -email: 'jon@example.com' # must be valid (for certificate recovery and security alerts) -agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes -community_member: true # receive infrequent relevant updates -telemetry: true # contribute to project telemetric data +email: coolaj86@gmail.com +agree_tos: true +community_member: true +telemetry: true webmin_domain: example.com -shared_domain: xm.pl -servernames: # hostnames that direct to the Telebit Relay admin console - - telebit.example.com - - telebit.example.net -vhost: /srv/www/:hostname # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public) +api_domain: example.com +shared_domain: example.com +servernames: + - www.example.com + - example.com + - api.example.com +vhost: /srv/www/:hostname greenlock: version: 'draft-11' server: 'https://acme-v02.api.letsencrypt.org/directory' store: - strategy: le-store-certbot # certificate storage plugin - config_dir: /etc/acme # directory for ssl certificates -secret: '' # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))" + strategy: le-store-certbot + config_dir: /opt/telebit-relay/etc/acme +secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +debug: true diff --git a/examples/telebitd.real.yml b/examples/telebitd.real.yml new file mode 100644 index 0000000..41c8cd7 --- /dev/null +++ b/examples/telebitd.real.yml @@ -0,0 +1,5 @@ +servernames: [ 'telebit.cloud' ] +email: 'coolaj86@gmail.com' +agree_tos: true +community_member: false +vhost: /srv/www/:hostname diff --git a/installer/get.sh b/installer/get.sh index 4e9e17d..f1eb20f 100644 --- a/installer/get.sh +++ b/installer/get.sh @@ -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 '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 '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" fi From 7fd28d55a171d22c03792d99e5621a9050dc67ee Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 27 Jun 2018 20:26:45 +0000 Subject: [PATCH 02/20] be more specific with your feedback, please (and fix missing email addr) --- lib/extensions/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 41a45dc..47d7085 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -156,12 +156,14 @@ module.exports.pairRequest = function (opts) { , aud: state.config.webminDomain , iat: Math.round(now / 1000) , id: authReq.id + , sub: authReq.subject , pin: pin , hostname: authReq.hostname }; auth = { id: authReq.id , secret: authReq.secret + , subject: authReq.subject , pin: pin , dt: now , exp: now + (2 * 60 * 60 * 1000) @@ -179,10 +181,14 @@ module.exports.pairPin = function (opts) { return state.Promise.resolve().then(function () { var pin = opts.pin; var secret = opts.secret; - var auth = Auths.getBySecretAndPin(secret, pin); + var auth = Auths.getBySecret(secret); 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) { From dc67bee735d23730f75d9529e02f505392b1c30c Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 11:02:13 +0000 Subject: [PATCH 03/20] passthru authn and await authz, better logging --- lib/extensions/index.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 47d7085..5ee664d 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -226,8 +226,6 @@ module.exports.pairPin = function (opts) { // From a WS connection module.exports.authenticate = function (opts) { var jwt = require('jsonwebtoken'); - var jwtoken = opts.auth; - var authReq = opts.auth; var state = opts.state; var auth; var decoded; @@ -267,41 +265,44 @@ module.exports.authenticate = function (opts) { return auth.promise; } - if ('object' === typeof authReq && /^.+@.+\..+$/.test(authReq.subject)) { - console.log("[ext token] Looks Like Auth Object"); + // Promise Authz on Auth Creds + // 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) { - 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); return getPromise(auth); + //getPromise(auth); + //return state.defaults.authenticate(authnData.jwt); }); } - console.log("[ext token] Trying Token Parse"); try { - decoded = jwt.decode(jwtoken, { complete: true }); + decoded = jwt.decode(opts.auth, { complete: true }); auth = Auths.get(decoded.payload.id); } catch(e) { - console.log("[ext token] Token Did Not Parse"); + console.log("[wss.ext.authenticate] [Error] could not parse token"); decoded = null; } - console.log("[ext token] decoded auth token:"); + console.log("[wss.ext.authenticate] incoming token decoded:"); console.log(decoded); if (!auth) { - console.log("[ext token] did not find auth object"); + console.log("[wss.ext.authenticate] missing auth object (incoming token stale?)"); } // TODO technically this could leak the token through a timing attack // but it would require already knowing the semi-secret id and having // completed the pair code - if (auth && (auth.authn === jwtoken || auth.authz === jwtoken)) { + if (auth && (auth.authn === opts.auth || auth.authz === opts.auth)) { if (!auth.authz) { - console.log("[ext token] Promise Authz"); - return getPromise(auth); + console.log("[wss.ext.authenticate] Create authz promise and passthru"); + getPromise(auth); + return state.defaults.authenticate(opts.auth); } - console.log("[ext token] Use Available Authz"); // If they used authn but now authz is available, use authz // (i.e. connects, but no domains or ports) opts.auth = auth.authz; @@ -310,7 +311,7 @@ module.exports.authenticate = function (opts) { auth._claimed = true; } - console.log("[ext token] Continue With Auth Token"); + console.log("[wss.ext.authenticate] Using authz"); return state.defaults.authenticate(opts.auth); }; From a3b8cd6799828b4232dc39be429db01111b16c97 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 11:02:44 +0000 Subject: [PATCH 04/20] better logging --- lib/relay.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/relay.js b/lib/relay.js index c6b3278..65656ad 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -257,7 +257,7 @@ var Server = { newAuth = JSON.stringify(newAuth); } - console.log('check for upgrade token'); + console.log('[onAuth] check for upgrade token'); if (grant.jwt && newAuth !== grant.jwt) { console.log('new token to send back'); // Access Token @@ -288,7 +288,7 @@ var Server = { 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 // domains and the list of all this websocket's grants. grant.domains.forEach(function (domainname) { @@ -417,7 +417,7 @@ var Server = { }); } , addToken: function addToken(state, srv, newAuth) { - console.log("addToken", newAuth); + console.log("[addToken]", newAuth); if (srv.grants[newAuth]) { console.log("addToken - duplicate"); // return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" }; @@ -587,6 +587,7 @@ module.exports.create = function (state) { }); if (initToken) { + console.log('[wss.onConnection] token provided in http headers'); return Server.addToken(state, srv, initToken).then(function () { Server.init(state, srv); }).catch(function (err) { From 8aecbb1f56c4da63df1e306c657f402f5df19b32 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 12:05:29 +0000 Subject: [PATCH 05/20] return to former glory --- lib/extensions/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 5ee664d..3e6cf52 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -299,8 +299,8 @@ module.exports.authenticate = function (opts) { if (auth && (auth.authn === opts.auth || auth.authz === opts.auth)) { if (!auth.authz) { console.log("[wss.ext.authenticate] Create authz promise and passthru"); - getPromise(auth); - return state.defaults.authenticate(opts.auth); + return getPromise(auth); + //return state.defaults.authenticate(opts.auth); } // If they used authn but now authz is available, use authz From 5b90d5ef38102c13097bc0b9e5fc75ffb2a1988f Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 12:06:34 +0000 Subject: [PATCH 06/20] correct order of operations --- lib/relay.js | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/lib/relay.js b/lib/relay.js index 65656ad..449b3b2 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -257,21 +257,31 @@ var Server = { newAuth = JSON.stringify(newAuth); } + // TODO don't fire the onAuth event on non-authz updates + if (!grant.jwt && !(grant.domains||[]).length && !(grant.ports||[]).length) { + return null; + } + console.log('[onAuth] check for upgrade token'); - if (grant.jwt && newAuth !== grant.jwt) { - console.log('new token to send back'); - // 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; + if (grant.jwt) { + if (newAuth !== grant.jwt) { + console.log('[onAuth] new token to send back'); + } + // TODO only send token when new + if (true) { + // 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; + } } /* @@ -425,22 +435,24 @@ var Server = { } return state.authenticate({ auth: newAuth }).then(function (authnToken) { - console.log('\n[relay.js] newAuth'); console.log(newAuth); console.log('\n[relay.js] authnToken'); console.log(authnToken); - if (authnToken.id) { - state.srvs[authnToken.id] = state.srvs[authnToken.id] || {}; + // For tracking state between token exchanges + // and tacking on extra attributes (i.e. for extensions) + // TODO close on delete + if (!state.srvs[authnToken.id]) { + state.srvs[authnToken.id] = {}; + } + if (!state.srvs[authnToken.id].updateAuth) { state.srvs[authnToken.id].updateAuth = function (validToken) { return Server.onAuth(state, srv, newAuth, validToken); }; } - - // will return rejection if necessary - return state.srvs[authnToken.id].updateAuth(authnToken); + state.srvs[authnToken.id].updateAuth(authnToken); }); } , removeToken: function removeToken(state, srv, jwtoken) { From bbee69832225ab0756b9914f56f33525b9e4e73a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 12:07:25 +0000 Subject: [PATCH 07/20] disable auto-https triggering for now --- lib/extensions/admin/login/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions/admin/login/js/app.js b/lib/extensions/admin/login/js/app.js index c717a12..2b23cf9 100644 --- a/lib/extensions/admin/login/js/app.js +++ b/lib/extensions/admin/login/js/app.js @@ -19,7 +19,7 @@ function checkStatus() { } if ('complete' === data.status) { setTimeout(function () { - window.document.body.innerHTML += (''); + //window.document.body.innerHTML += (''); // TODO once this is loaded (even error) Let's Encrypt is done, // then it's time to redirect to the domain. Yay! }, 1 * 1000); From 57f1de5f2dbd0949b5771f7b112dc5502423d38a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 20:05:24 +0000 Subject: [PATCH 08/20] regression fix: pass updated jwts with grant --- lib/extensions/index.js | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 3e6cf52..9e6dc36 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -167,11 +167,13 @@ module.exports.pairRequest = function (opts) { , pin: pin , dt: now , exp: now + (2 * 60 * 60 * 1000) - , authnData: authnData - , authn: jwt.sign(authnData, state.secret) , request: authReq }; + + // Setting extra authnData + auth.authn = jwt.sign(authnData, state.secret); authnData.jwt = auth.authn; + auth.authnData = authnData; Auths.set(auth, authReq.id, authReq.secret); return authnData; }); @@ -183,6 +185,7 @@ module.exports.pairPin = function (opts) { var secret = opts.secret; var auth = Auths.getBySecret(secret); + console.log('[pairPin] validating secret and pin'); if (!auth) { throw new Error("Invalid magic link token '" + secret + "'"); } @@ -192,9 +195,11 @@ module.exports.pairPin = function (opts) { } if (auth._offered) { + console.log('[pairPin] already has offer to return'); return auth._offered; } + console.log('[pairPin] generating offer'); var hri = require('human-readable-ids').hri; var hrname = hri.random() + '.' + state.config.sharedDomain; // TODO check used / unused names and ports @@ -208,9 +213,14 @@ module.exports.pairPin = function (opts) { }; var pathname = path.join(__dirname, 'emails', auth.subject + '.' + hrname + '.data'); auth.authz = jwt.sign(authzData, state.secret); + auth.authzData = authzData; authzData.jwt = auth.authz; + auth._offered = authzData; if (auth.resolve) { + console.log('[pairPin] resolving'); auth.resolve(auth); + } else { + console.log('[pairPin] not resolvable'); } fs.writeFile(pathname, JSON.stringify(authzData), function (err) { if (err) { @@ -218,12 +228,24 @@ module.exports.pairPin = function (opts) { console.error(err); } }); - auth._offered = authzData; return authzData; }); }; // From a WS connection +module.exports.authHelper = function (meta) { + var state = meta.state; + return state.Promise.resolve().then(function () { + var auth = meta.session; + if ('string' !== typeof auth.authz || 'object' !== typeof auth.authzData) { + console.error("[SANITY FAIL] should not complete auth without authz data and access_token"); + console.error(auth); + return; + } + return auth.authzData; + }); +}; +// opts = { state: state, auth: auth_request OR access_token } module.exports.authenticate = function (opts) { var jwt = require('jsonwebtoken'); var state = opts.state; @@ -245,7 +267,6 @@ module.exports.authenticate = function (opts) { // this will cause the websocket to disconnect auth.resolve = function (auth) { - opts.auth = auth.authz; auth.resolve = null; auth.reject = null; // NOTE XXX: This is premature in the sense that we can't be 100% sure @@ -253,7 +274,12 @@ module.exports.authenticate = function (opts) { // sort of check that the client actually received the token // (i.e. when the grant event gets an ack) 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.resolve = null; @@ -312,7 +338,7 @@ module.exports.authenticate = function (opts) { } console.log("[wss.ext.authenticate] Using authz"); - return state.defaults.authenticate(opts.auth); + return module.exports.authHelper({ state: state, session: auth }); }; //var loaded = false; From 6b6ffd4647648f9fe80dad1a4a221219d60aee8c Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 20:05:59 +0000 Subject: [PATCH 09/20] name more clearly --- lib/relay.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/relay.js b/lib/relay.js index 449b3b2..0df9fc8 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -259,10 +259,12 @@ var Server = { // TODO don't fire the onAuth event on non-authz updates if (!grant.jwt && !(grant.domains||[]).length && !(grant.ports||[]).length) { + console.log("[onAuth] nothing to offer at all"); return null; } console.log('[onAuth] check for upgrade token'); + console.log(grant); if (grant.jwt) { if (newAuth !== grant.jwt) { console.log('[onAuth] new token to send back'); @@ -275,7 +277,7 @@ var Server = { , null , [ 3 , 'access_token' - , { jwt: grant.jwt } + , { jwt: grant.jwt || grant.access_token } ] , 'control' ); @@ -426,33 +428,33 @@ var Server = { process.nextTick(function () { conn.resume(); }); }); } -, addToken: function addToken(state, srv, newAuth) { - console.log("[addToken]", newAuth); - if (srv.grants[newAuth]) { +, addToken: function addToken(state, srv, rawAuth) { + console.log("[addToken]", rawAuth); + if (srv.grants[rawAuth]) { console.log("addToken - duplicate"); // return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" }; return state.Promise.resolve(null); } - return state.authenticate({ auth: newAuth }).then(function (authnToken) { - console.log('\n[relay.js] newAuth'); - console.log(newAuth); + return state.authenticate({ auth: rawAuth }).then(function (validatedTokenData) { + console.log('\n[relay.js] rawAuth'); + console.log(rawAuth); console.log('\n[relay.js] authnToken'); - console.log(authnToken); + console.log(validatedTokenData); // For tracking state between token exchanges // and tacking on extra attributes (i.e. for extensions) // TODO close on delete - if (!state.srvs[authnToken.id]) { - state.srvs[authnToken.id] = {}; + if (!state.srvs[validatedTokenData.id]) { + state.srvs[validatedTokenData.id] = {}; } - if (!state.srvs[authnToken.id].updateAuth) { - state.srvs[authnToken.id].updateAuth = function (validToken) { - return Server.onAuth(state, srv, newAuth, validToken); + if (!state.srvs[validatedTokenData.id].updateAuth) { + state.srvs[validatedTokenData.id].updateAuth = function (validatedTokenData) { + return Server.onAuth(state, srv, rawAuth, validatedTokenData); }; } - state.srvs[authnToken.id].updateAuth(authnToken); + state.srvs[validatedTokenData.id].updateAuth(validatedTokenData); }); } , removeToken: function removeToken(state, srv, jwtoken) { From 5deefa98327d9bc872e4b6854c9eb5642de51ef4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 21:39:49 +0000 Subject: [PATCH 10/20] grant pre-authorized tokens, duh --- lib/extensions/index.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 9e6dc36..15a671e 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -234,14 +234,20 @@ module.exports.pairPin = function (opts) { // 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; - if ('string' !== typeof auth.authz || 'object' !== typeof auth.authzData) { + 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; }); }; @@ -311,22 +317,21 @@ module.exports.authenticate = function (opts) { console.log("[wss.ext.authenticate] [Error] could not parse token"); decoded = null; } - console.log("[wss.ext.authenticate] incoming token decoded:"); console.log(decoded); if (!auth) { - console.log("[wss.ext.authenticate] missing auth object (incoming token stale?)"); + 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 // but it would require already knowing the semi-secret id and having // completed the pair code - if (auth && (auth.authn === opts.auth || auth.authz === opts.auth)) { + if (auth.authn === opts.auth || auth.authz === opts.auth) { if (!auth.authz) { console.log("[wss.ext.authenticate] Create authz promise and passthru"); return getPromise(auth); - //return state.defaults.authenticate(opts.auth); } // If they used authn but now authz is available, use authz @@ -337,7 +342,7 @@ module.exports.authenticate = function (opts) { auth._claimed = true; } - console.log("[wss.ext.authenticate] Using authz"); + console.log("[wss.ext.authenticate] Already using authz, skipping promise"); return module.exports.authHelper({ state: state, session: auth }); }; From cda10951d8af9a2e19241c7d140cb3ddda738243 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 21:40:10 +0000 Subject: [PATCH 11/20] quiet down --- lib/relay.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/relay.js b/lib/relay.js index 0df9fc8..42df80c 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -243,7 +243,7 @@ var Server = { , onAuth: function onAuth(state, srv, newAuth, grant) { console.log('\n[relay.js] onAuth'); console.log(newAuth); - console.log(grant); + //console.log(grant); //var stringauth; var err; if (!grant || 'object' !== typeof grant) { @@ -264,7 +264,7 @@ var Server = { } console.log('[onAuth] check for upgrade token'); - console.log(grant); + //console.log(grant); if (grant.jwt) { if (newAuth !== grant.jwt) { console.log('[onAuth] new token to send back'); @@ -311,7 +311,7 @@ var Server = { srv.domains = Object.keys(srv.domainsMap); 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.srv = srv; + //grant.srv = srv; //grant.ws = srv.ws; //grant.upgradeReq = srv.upgradeReq; grant.clients = {}; From db4e5c4f600f778a2e2a7bdd7d869060f2330bbd Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 29 Jun 2018 22:14:08 +0000 Subject: [PATCH 12/20] don't use old socket --- lib/relay.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/relay.js b/lib/relay.js index 42df80c..156b392 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -240,9 +240,9 @@ var Server = { 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(newAuth); + console.log(rawAuth); //console.log(grant); //var stringauth; var err; @@ -253,8 +253,8 @@ var Server = { return state.Promise.reject(err); } - if ('string' !== typeof newAuth) { - newAuth = JSON.stringify(newAuth); + if ('string' !== typeof rawAuth) { + rawAuth = JSON.stringify(rawAuth); } // TODO don't fire the onAuth event on non-authz updates @@ -266,7 +266,7 @@ var Server = { console.log('[onAuth] check for upgrade token'); //console.log(grant); if (grant.jwt) { - if (newAuth !== grant.jwt) { + if (rawAuth !== grant.jwt) { console.log('[onAuth] new token to send back'); } // TODO only send token when new @@ -277,7 +277,7 @@ var Server = { , null , [ 3 , 'access_token' - , { jwt: grant.jwt || grant.access_token } + , { jwt: grant.jwt } ] , 'control' ); @@ -356,7 +356,7 @@ var Server = { } grant.ports.forEach(openPort); - srv.grants[newAuth] = grant; + srv.grants[rawAuth] = grant; console.info("[ws] authorized", srv.socketId, "for", grant.currentDesc); console.log('notify of grants', grant.domains, grant.ports); @@ -450,11 +450,13 @@ var Server = { state.srvs[validatedTokenData.id] = {}; } if (!state.srvs[validatedTokenData.id].updateAuth) { - state.srvs[validatedTokenData.id].updateAuth = function (validatedTokenData) { + // 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(validatedTokenData); + state.srvs[validatedTokenData.id].updateAuth(srv, validatedTokenData); }); } , removeToken: function removeToken(state, srv, jwtoken) { From 6a1df2ee05068302ecb341317eece55fe749e382 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 30 Jun 2018 23:09:38 +0000 Subject: [PATCH 13/20] https://git.coolaj86.com/coolaj86/telebit.js/issues/11 wildcards forward by default --- lib/device-tracker.js | 34 ++++++++++++++++++++++++++++++---- lib/relay.js | 2 ++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/device-tracker.js b/lib/device-tracker.js index 7194cdf..f5ed895 100644 --- a/lib/device-tracker.js +++ b/lib/device-tracker.js @@ -2,9 +2,24 @@ var Devices = module.exports; Devices.add = function (store, servername, newDevice) { - var devices = store[servername] || []; + if (!store[servername]) { + store[servername] = []; + } + var devices = store[servername]; 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) { var devices = store[servername] || []; @@ -17,9 +32,11 @@ Devices.remove = function (store, servername, device) { return devices.splice(index, 1)[0]; }; Devices.list = function (store, servername) { + // efficient lookup first 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 // first so the one with the biggest natural match with be found first. var deviceList = []; @@ -28,10 +45,19 @@ Devices.list = function (store, servername) { }).sort(function (a, b) { return b.length - a.length; }).some(function (pattern) { + // '.example.com' = '*.example.com'.split(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)) { - console.log('"'+servername+'" matches "'+pattern+'"'); + console.log('[Devices.list] "'+servername+'" matches "'+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; } }); diff --git a/lib/relay.js b/lib/relay.js index 156b392..78c159b 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -307,6 +307,8 @@ var Server = { console.log('add', domainname, 'to device lists'); srv.domainsMap[domainname] = true; 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.currentDesc = (grant.device && (grant.device.id || grant.device.hostname)) || srv.domains.join(','); From 3773abdfdb9c962d25b13ea65d332959dc7aaca3 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 30 Jun 2018 23:09:38 +0000 Subject: [PATCH 14/20] https://git.coolaj86.com/coolaj86/telebit.js/issues/11 wildcards forward by default --- lib/device-tracker.js | 34 ++++++++++++++++++++++++++++++---- lib/relay.js | 2 ++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/device-tracker.js b/lib/device-tracker.js index 7194cdf..f5ed895 100644 --- a/lib/device-tracker.js +++ b/lib/device-tracker.js @@ -2,9 +2,24 @@ var Devices = module.exports; Devices.add = function (store, servername, newDevice) { - var devices = store[servername] || []; + if (!store[servername]) { + store[servername] = []; + } + var devices = store[servername]; 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) { var devices = store[servername] || []; @@ -17,9 +32,11 @@ Devices.remove = function (store, servername, device) { return devices.splice(index, 1)[0]; }; Devices.list = function (store, servername) { + // efficient lookup first 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 // first so the one with the biggest natural match with be found first. var deviceList = []; @@ -28,10 +45,19 @@ Devices.list = function (store, servername) { }).sort(function (a, b) { return b.length - a.length; }).some(function (pattern) { + // '.example.com' = '*.example.com'.split(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)) { - console.log('"'+servername+'" matches "'+pattern+'"'); + console.log('[Devices.list] "'+servername+'" matches "'+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; } }); diff --git a/lib/relay.js b/lib/relay.js index 156b392..78c159b 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -307,6 +307,8 @@ var Server = { console.log('add', domainname, 'to device lists'); srv.domainsMap[domainname] = true; 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.currentDesc = (grant.device && (grant.device.id || grant.device.hostname)) || srv.domains.join(','); From d60b458f477931115028dea59b9340b632bd62ff Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 30 Jun 2018 23:10:18 +0000 Subject: [PATCH 15/20] more efficient checking --- lib/unwrap-tls.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/unwrap-tls.js b/lib/unwrap-tls.js index 58bb588..edab75d 100644 --- a/lib/unwrap-tls.js +++ b/lib/unwrap-tls.js @@ -46,6 +46,12 @@ module.exports.createTcpConnectionHandler = function (state) { function tryTls() { 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) { console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)"); deferData('httpsSetupServer'); @@ -62,12 +68,6 @@ module.exports.createTcpConnectionHandler = function (state) { 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() { var nextDevice = Devices.next(state.deviceLists, servername); if (!nextDevice) { From c045e4c7125c26abb59e824aed6c7a17c7a00148 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 6 Jul 2018 08:13:59 +0000 Subject: [PATCH 16/20] add login --- lib/extensions/admin/.well-known | 1 + lib/extensions/admin/_apis/oauth3 | 1 + lib/extensions/admin/_apis/oauth3.org | 1 + lib/extensions/admin/assets/oauth3.org | 1 + lib/extensions/admin/index.html | 68 ++++++++++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 120000 lib/extensions/admin/.well-known create mode 120000 lib/extensions/admin/_apis/oauth3 create mode 120000 lib/extensions/admin/_apis/oauth3.org create mode 160000 lib/extensions/admin/assets/oauth3.org diff --git a/lib/extensions/admin/.well-known b/lib/extensions/admin/.well-known new file mode 120000 index 0000000..5a61f7b --- /dev/null +++ b/lib/extensions/admin/.well-known @@ -0,0 +1 @@ +_apis \ No newline at end of file diff --git a/lib/extensions/admin/_apis/oauth3 b/lib/extensions/admin/_apis/oauth3 new file mode 120000 index 0000000..bde9539 --- /dev/null +++ b/lib/extensions/admin/_apis/oauth3 @@ -0,0 +1 @@ +oauth3.org \ No newline at end of file diff --git a/lib/extensions/admin/_apis/oauth3.org b/lib/extensions/admin/_apis/oauth3.org new file mode 120000 index 0000000..4f73018 --- /dev/null +++ b/lib/extensions/admin/_apis/oauth3.org @@ -0,0 +1 @@ +../assets/oauth3.org/_apis/oauth3.org \ No newline at end of file diff --git a/lib/extensions/admin/assets/oauth3.org b/lib/extensions/admin/assets/oauth3.org new file mode 160000 index 0000000..8e2e09f --- /dev/null +++ b/lib/extensions/admin/assets/oauth3.org @@ -0,0 +1 @@ +Subproject commit 8e2e09f5823ae919c615c9c3b21114e01096b1ee diff --git a/lib/extensions/admin/index.html b/lib/extensions/admin/index.html index 977d2f7..d6b500c 100644 --- a/lib/extensions/admin/index.html +++ b/lib/extensions/admin/index.html @@ -139,6 +139,74 @@ TCP +
+ + +
+ + From 4e459ea6174df8d5906a81ede5dc2ac7fa5ec239 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 7 Jul 2018 09:45:33 +0000 Subject: [PATCH 17/20] handle accounts --- lib/extensions/admin/account.html | 90 ++++++++++ lib/extensions/admin/index.html | 71 +------- lib/extensions/index.js | 274 +++++++++++++++++++++++++++++- package.json | 11 +- 4 files changed, 372 insertions(+), 74 deletions(-) create mode 100644 lib/extensions/admin/account.html diff --git a/lib/extensions/admin/account.html b/lib/extensions/admin/account.html new file mode 100644 index 0000000..94f5f3d --- /dev/null +++ b/lib/extensions/admin/account.html @@ -0,0 +1,90 @@ + + + Telebit Account + + +
+ + +
+ + + + + diff --git a/lib/extensions/admin/index.html b/lib/extensions/admin/index.html index d6b500c..1acb285 100644 --- a/lib/extensions/admin/index.html +++ b/lib/extensions/admin/index.html @@ -29,6 +29,9 @@

Friends enable friends to share anything, access anywhere, connect anytime.

+ Login + Create Account +

Share and Test over HTTPS

@@ -139,74 +142,6 @@ TCP
-
- - -
- - diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 15a671e..5cf5e9b 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -6,7 +6,9 @@ var util = require('util'); var crypto = require('crypto'); var escapeHtml = require('escape-html'); 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 = {}; @@ -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) { 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) { console.log("It's auth'n time!"); var state = opts.state; @@ -357,9 +598,36 @@ var urls = { pairState: '/api/telebit.cloud/pair_state/:id' }; 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/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) app.post('/api/telebit.cloud/pair_request', function (req, res) { 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) { var secret = req.params.secret; var auth = Auths.getBySecret(secret); - var crypto = require('crypto'); + //var crypto = require('crypto'); var response = {}; diff --git a/package.json b/package.json index f3a665d..92d905f 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,7 @@ }, "homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js", "dependencies": { - "@coolaj86/urequest": "^1.1.1", - "bluebird": "^3.5.1", + "@coolaj86/urequest": "^1.2.1", "body-parser": "^1.18.3", "cluster-store": "^2.0.8", "connect-cors": "^0.5.6", @@ -48,16 +47,22 @@ "greenlock": "^2.2.4", "human-readable-ids": "^1.0.4", "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", "proxy-packer": "^1.4.3", "recase": "^1.0.4", "redirect-https": "^1.1.5", "request": "^2.87.0", + "safe-replace": "^1.0.3", "serve-static": "^1.13.2", "sni": "^1.0.0", "ws": "^5.1.1" }, + "trulyOptionalDependencies": { + "bluebird": "^3.5.1" + }, "engineStrict": true, "engines": { "node": "10.2.1" From 36ab30c9f2353f717a010066a53b3ddb7845bf88 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 7 Jul 2018 09:47:00 +0000 Subject: [PATCH 18/20] remove bluebird --- lib/relay.js | 7 ++++++- package.json | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/relay.js b/lib/relay.js index 78c159b..9b66d26 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -1,10 +1,15 @@ 'use strict'; var url = require('url'); -var PromiseA = require('bluebird'); var sni = require('sni'); var Packer = require('proxy-packer'); var PortServers = {}; +var PromiseA; +try { + PromiseA = require('bluebird'); +} catch(e) { + PromiseA = global.Promise; +} function timeoutPromise(duration) { return new PromiseA(function (resolve) { diff --git a/package.json b/package.json index 2b89e05..e3ec8b0 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ }, "homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js", "dependencies": { - "bluebird": "^3.5.1", "cluster-store": "^2.0.8", "finalhandler": "^1.1.1", "greenlock": "^2.2.4", @@ -51,6 +50,9 @@ "sni": "^1.0.0", "ws": "^5.1.1" }, + "trulyOptionalDependencies": { + "bluebird": "^3.5.1" + }, "engineStrict": true, "engines": { "node": "10.2.1" From d12cddb1aa5eb60433b9298cbc1eb66ddb1bdc8a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 7 Jul 2018 20:38:36 +0000 Subject: [PATCH 19/20] update urequest for form-data fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 92d905f..22e6339 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js", "dependencies": { - "@coolaj86/urequest": "^1.2.1", + "@coolaj86/urequest": "^1.3.2", "body-parser": "^1.18.3", "cluster-store": "^2.0.8", "connect-cors": "^0.5.6", From b5fd940429ef58795da13f1a1dae784cb6c16df0 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 7 Jul 2018 22:31:41 +0000 Subject: [PATCH 20/20] check sendMail return code --- lib/extensions/index.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/extensions/index.js b/lib/extensions/index.js index 5cf5e9b..46d3084 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -10,6 +10,13 @@ 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 = {}; Auths._no_pin = { @@ -200,8 +207,15 @@ function sendMail(state, auth) { console.error(err); } }); - console.log("[DEBUG] email was sent, or so they say"); - console.log(resp.body); + // anything in the 200 range + 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)); + } }); }