WIP get preauth clientside and realauth daemonside

This commit is contained in:
AJ ONeal 2018-06-29 04:15:23 -06:00
parent 02a89b52a4
commit 9441bf7f8e
6 changed files with 390 additions and 271 deletions

View File

@ -35,8 +35,8 @@ if (-1 !== argIndex) {
} }
function help() { function help() {
console.info(''); //console.info('');
console.info('Telebit Remote v' + pkg.version); //console.info('Telebit Remote v' + pkg.version);
console.info(''); console.info('');
console.info('Usage:'); console.info('Usage:');
console.info(''); console.info('');
@ -90,8 +90,7 @@ if (!confpath || /^--/.test(confpath)) {
process.exit(1); process.exit(1);
} }
function askForConfig(answers, mainCb) { function askForConfig(state, mainCb) {
answers = answers || {};
var fs = require('fs'); var fs = require('fs');
var ttyname = '/dev/tty'; var ttyname = '/dev/tty';
var stdin = useTty ? fs.createReadStream(ttyname, { var stdin = useTty ? fs.createReadStream(ttyname, {
@ -104,7 +103,7 @@ function askForConfig(answers, mainCb) {
// https://github.com/nodejs/node/issues/21319 // https://github.com/nodejs/node/issues/21319
, terminal: !useTty , terminal: !useTty
}); });
answers._useTty = useTty; state._useTty = useTty;
// NOTE: Use of setTimeout // NOTE: Use of setTimeout
// We're using setTimeout just to make the user experience a little // We're using setTimeout just to make the user experience a little
@ -115,7 +114,7 @@ function askForConfig(answers, mainCb) {
// ~ 150-250ms is the sweet spot for most humans (long enough to notice change and not be jarred, but stay on task) // ~ 150-250ms is the sweet spot for most humans (long enough to notice change and not be jarred, but stay on task)
var firstSet = [ var firstSet = [
function askEmail(cb) { function askEmail(cb) {
if (answers.email) { cb(); return; } if (state.config.email) { cb(); return; }
//console.info(""); //console.info("");
console.info("Welcome!"); console.info("Welcome!");
console.info(""); console.info("");
@ -130,8 +129,8 @@ function askForConfig(answers, mainCb) {
rl.question('email: ', function (email) { rl.question('email: ', function (email) {
email = /@/.test(email) && email.trim(); email = /@/.test(email) && email.trim();
if (!email) { askEmail(cb); return; } if (!email) { askEmail(cb); return; }
answers.email = email.trim(); state.config.email = email.trim();
answers.agree_tos = true; state.config.agreeTos = true;
console.info(""); console.info("");
setTimeout(cb, 250); setTimeout(cb, 250);
}); });
@ -159,14 +158,14 @@ function askForConfig(answers, mainCb) {
console.warn(""); console.warn("");
console.warn(body); console.warn(body);
} else if (body && body.pair_request) { } else if (body && body.pair_request) {
answers._can_pair = true; state._can_pair = true;
} }
answers.relay = relay; state.config.relay = relay;
cb(); cb();
}); });
} }
if (answers.relay) { checkRelay(); return; } if (state.config.relay) { checkRelay(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("What relay will you be using? (press enter for default)"); console.info("What relay will you be using? (press enter for default)");
@ -175,10 +174,10 @@ function askForConfig(answers, mainCb) {
} }
, function checkRelay(cb) { , function checkRelay(cb) {
nextSet = []; nextSet = [];
if ('telebit.cloud' !== answers.relay) { if ('telebit.cloud' !== state.config.relay) {
nextSet = nextSet.concat(standardSet); nextSet = nextSet.concat(standardSet);
} }
if (!answers._can_pair) { if (!state._can_pair) {
nextSet = nextSet.concat(fossSet); nextSet = nextSet.concat(fossSet);
} }
cb(); cb();
@ -188,7 +187,7 @@ function askForConfig(answers, mainCb) {
// There are questions that we need to aks in the CLI // There are questions that we need to aks in the CLI
// if we can't guarantee that they are being asked in the web interface // if we can't guarantee that they are being asked in the web interface
function askAgree(cb) { function askAgree(cb) {
if (answers.agree_tos) { cb(); return; } if (state.config.agreeTos) { cb(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("Do you accept the terms of service for each and all of the following?"); console.info("Do you accept the terms of service for each and all of the following?");
@ -204,7 +203,7 @@ function askForConfig(answers, mainCb) {
if (!/^y(es)?$/i.test(resp) && 'true' !== resp) { if (!/^y(es)?$/i.test(resp) && 'true' !== resp) {
throw new Error("You didn't accept the Terms of Service... not sure what to do..."); throw new Error("You didn't accept the Terms of Service... not sure what to do...");
} }
answers.agree_tos = true; state.config.agreeTos = true;
console.info(""); console.info("");
setTimeout(cb, 250); setTimeout(cb, 250);
}); });
@ -212,35 +211,35 @@ function askForConfig(answers, mainCb) {
, function askUpdates(cb) { , function askUpdates(cb) {
// required means transactional, security alerts, mandatory updates // required means transactional, security alerts, mandatory updates
var options = [ 'newsletter', 'important', 'required' ]; var options = [ 'newsletter', 'important', 'required' ];
if (-1 !== options.indexOf(answers.updates)) { cb(); return; } if (-1 !== options.indexOf(state._updates)) { cb(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("What updates would you like to receive? (" + options.join(',') + ")"); console.info("What updates would you like to receive? (" + options.join(',') + ")");
console.info(""); console.info("");
rl.question('messages (default: important): ', function (updates) { rl.question('messages (default: important): ', function (updates) {
updates = (updates || '').trim().toLowerCase(); state._updates = (updates || '').trim().toLowerCase();
if (!updates) { updates = 'important'; } if (!state._updates) { state._updates = 'important'; }
if (-1 === options.indexOf(updates)) { askUpdates(cb); return; } if (-1 === options.indexOf(state._updates)) { askUpdates(cb); return; }
if ('newsletter' === updates) { if ('newsletter' === state._updates) {
answers.newsletter = true; state.config.newsletter = true;
answers.communityMember = true; state.config.communityMember = true;
} else if ('important' === updates) { } else if ('important' === state._updates) {
answers.communityMember = true; state.config.communityMember = true;
} }
setTimeout(cb, 250); setTimeout(cb, 250);
}); });
} }
, function askTelemetry(cb) { , function askTelemetry(cb) {
if (answers.telemetry) { cb(); return; } if (state.config.telemetry) { cb(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("Contribute project telemetry data? (press enter for default [yes])"); console.info("Contribute project telemetry data? (press enter for default [yes])");
console.info(""); console.info("");
rl.question('telemetry [Y/n]: ', function (telemetry) { rl.question('telemetry [Y/n]: ', function (telemetry) {
if (!telemetry || /^y(es)?$/i.test(telemetry)) { if (!telemetry || /^y(es)?$/i.test(telemetry)) {
answers.telemetry = true; state.config.telemetry = true;
} }
setTimeout(cb, 250); setTimeout(cb, 250);
}); });
@ -248,10 +247,11 @@ function askForConfig(answers, mainCb) {
]; ];
var fossSet = [ var fossSet = [
function askTokenOrSecret(cb) { function askTokenOrSecret(cb) {
if (answers._can_pair || answers.token || answers.secret) { cb(); return; } if (state._can_pair || state.token || state.config.token
|| state.secret || state.config.secret) { cb(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("What's your authorization for '" + answers.relay + "'?"); console.info("What's your authorization for '" + state.config.relay + "'?");
console.info(""); console.info("");
// TODO check .well-known to learn supported token types // TODO check .well-known to learn supported token types
console.info("Currently supported:"); console.info("Currently supported:");
@ -264,24 +264,17 @@ function askForConfig(answers, mainCb) {
var jwt = require('jsonwebtoken'); var jwt = require('jsonwebtoken');
resp = (resp || '').trim(); resp = (resp || '').trim();
try { try {
answers.token = jwt.decode(resp); state.config.token = jwt.decode(resp);
} catch(e) { } catch(e) {
// is not jwt // is not jwt
try {
if (JSON.parse(resp).subject) {
answers.token = resp;
}
} catch(e) {
// is not authRequest either
}
} }
if (!answers.token) { if (!state.config.token) {
resp = resp.toLowerCase(); resp = resp.toLowerCase();
if (resp === Buffer.from(resp, 'hex').toString('hex')) { if (resp === Buffer.from(resp, 'hex').toString('hex')) {
answers.secret = resp; state.config.secret = resp;
} }
} }
if (!answers.token && !answers.secret) { if (!state.config.token && !state.config.secret) {
askTokenOrSecret(cb); askTokenOrSecret(cb);
return; return;
} }
@ -289,7 +282,7 @@ function askForConfig(answers, mainCb) {
}); });
} }
, function askServernames(cb) { , function askServernames(cb) {
if (!answers.secret || answers.servernames) { cb(); return; } if (!state.config.secret || state.config._servernames) { cb(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("What servername(s) will you be relaying here?"); console.info("What servername(s) will you be relaying here?");
@ -299,12 +292,12 @@ function askForConfig(answers, mainCb) {
resp = (resp || '').trim().split(/,/g); resp = (resp || '').trim().split(/,/g);
if (!resp.length) { askServernames(); return; } if (!resp.length) { askServernames(); return; }
// TODO validate the domains // TODO validate the domains
answers.servernames = resp; state.config._servernames = resp;
setTimeout(cb, 250); setTimeout(cb, 250);
}); });
} }
, function askPorts(cb) { , function askPorts(cb) {
if (!answers.secret || answers.ports) { cb(); return; } if (!state.config.secret || state.config._ports) { cb(); return; }
console.info(""); console.info("");
console.info(""); console.info("");
console.info("What tcp port(s) will you be relaying here?"); console.info("What tcp port(s) will you be relaying here?");
@ -314,7 +307,7 @@ function askForConfig(answers, mainCb) {
resp = (resp || '').trim().split(/,/g); resp = (resp || '').trim().split(/,/g);
if (!resp.length) { askPorts(); return; } if (!resp.length) { askPorts(); return; }
// TODO validate the domains // TODO validate the domains
answers.ports = resp; state.config._ports = resp;
setTimeout(cb, 250); setTimeout(cb, 250);
}); });
} }
@ -328,7 +321,7 @@ function askForConfig(answers, mainCb) {
if (useTty) { try { stdin.push(null); } catch(e) { /*ignore*/ } } if (useTty) { try { stdin.push(null); } catch(e) { /*ignore*/ } }
rl.close(); rl.close();
if (useTty) { try { stdin.close(); } catch(e) { /*ignore*/ } } if (useTty) { try { stdin.close(); } catch(e) { /*ignore*/ } }
mainCb(null, answers); mainCb(null, state);
return; return;
} }
q(next); q(next);
@ -403,6 +396,8 @@ var utils = {
}); });
} }
, putConfig: function putConfig(service, args, fn) { , putConfig: function putConfig(service, args, fn) {
//console.log('debug path:');
//console.log('/rpc/' + service + '?_body=' + JSON.stringify(args));
var req = http.get({ var req = http.get({
socketPath: state._ipc.path socketPath: state._ipc.path
, method: 'POST' , method: 'POST'
@ -465,53 +460,143 @@ var utils = {
} }
}; };
function parseConfig(err, text) { // Two styles:
// http 3000
// http modulename
function makeRpc(key) {
if (key !== argv[0]) {
return false;
}
utils.putConfig(argv[0], argv.slice(1));
return true;
}
console.info(""); function packConfig(config) {
console.info(verstr.join(' ')); return Object.keys(config).map(function (key) {
var val = config[key];
if (val && 'object' === typeof val && !Array.isArray(val)) {
val = JSON.stringify(val);
}
return key + ':' + val; // converts arrays to strings with ,
});
}
try { function getToken(err, state) {
state.config = JSON.parse(text || '{}'); if (err) {
} catch(e1) { console.error("Error while initializing config [init]:");
try { throw err;
state.config = YAML.safeLoad(text || '{}'); }
} catch(e2) {
console.error(e1.message); // { _otp, config: {} }
console.error(e2.message); common.api.token(state, {
process.exit(1); error: function (err/*, next*/) {
console.error("[Error] common.api.token:");
console.error(err);
return; return;
} }
} , directory: function (dir, next) {
//console.log('Telebit Relay Discovered:');
state.config = camelCopy(state.config || {}) || {}; state._apiDirectory = dir;
common._init( //console.log(dir);
state.config.root || path.join(os.homedir(), '.local/share/telebit') //console.log();
, (state.config.root && path.join(state.config.root, 'etc')) || path.join(os.homedir(), '.config/telebit') next();
);
state._ipc = common.pipename(state.config, true);
if (!Object.keys(state.config).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
}
console.info("");
if ((err && 'ENOENT' === err.code) || !Object.keys(state.config).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
} }
} , tunnelUrl: function (tunnelUrl, next) {
//console.log('Telebit Relay Tunnel Socket:', tunnelUrl);
// Two styles: state.wss = tunnelUrl;
// http 3000 next();
// http modulename
function makeRpc(key) {
if (key !== argv[0]) {
return false;
} }
utils.putConfig(argv[0], argv.slice(1)); , requested: function (authReq, next) {
return true; //console.log("Pairing Requested");
state.config._otp = state.config._otp = authReq.otp;
if (!state.config.token && state._can_pair) {
console.info("");
console.info("==============================================");
console.info(" Hey, Listen! ");
console.info("==============================================");
console.info(" ");
console.info(" GO CHECK YOUR EMAIL! ");
console.info(" ");
console.info(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, state.config._otp));
console.info(" ");
console.info("==============================================");
console.info("");
}
next();
}
, connect: function (pretoken, next) {
//console.log("Enabling Pairing Locally...");
state.config.pretoken = pretoken;
state._connecting = true;
// TODO use php-style object querification
// TODO XXX
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
if (err) {
state._error = err;
console.error("Error while initializing config [connect]:");
console.error(err);
return;
}
console.log("waiting...");
next();
});
}
, offer: function (token, next) {
//console.log("Pairing Enabled by Relay");
state.config.token = token;
if (state._error) {
return;
}
if (state._connecting) {
return;
}
state._connecting = true;
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
if (err) {
state._error = err;
console.error("Error while initializing config [offer]:");
console.error(err);
return;
}
//console.log("Pairing Enabled Locally");
next();
});
}
, granted: function (_, next) {
//console.log("Pairing complete!");
next();
}
, end: function () {
utils.putConfig('enable', [], function (err) {
if (err) { console.error(err); return; }
//console.info("Success");
// workaround for https://github.com/nodejs/node/issues/21319
if (state._useTty) {
setTimeout(function () {
console.info();
console.info("Press any key to continue...");
console.info();
process.exit(0);
}, 0.5 * 1000);
return;
}
// end workaround
parseCli(state);
});
}
});
}
function parseCli(/*state*/) {
if (-1 !== argv.indexOf('init')) {
utils.putConfig('list', []/*, function (err) {
}*/);
return;
} }
if ([ 'ssh', 'http', 'tcp' ].some(function (key) { if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
@ -522,124 +607,10 @@ function parseConfig(err, text) {
utils.putConfig(argv[0], argv.slice(1)); utils.putConfig(argv[0], argv.slice(1));
return true; return true;
} }
help();
return true; return true;
})) { })) {
return true; help();
} process.exit(13);
if (-1 !== argv.indexOf('init')) {
parsers.init(argv, function (err, answers) {
if (err) {
console.error("Error while initializing config [init]:");
throw err;
}
common.api.token(state, {
error: function (err/*, next*/) {
console.error("[Error] common.api.token:");
console.error(err);
return;
}
, directory: function (dir, next) {
//console.log('Telebit Relay Discovered:');
state._apiDirectory = dir;
//console.log(dir);
//console.log();
next();
}
, tunnelUrl: function (tunnelUrl, next) {
//console.log('Telebit Relay Tunnel Socket:', tunnelUrl);
state.wss = tunnelUrl;
next();
}
, requested: function (authReq, next) {
//console.log("Pairing Requested");
var pin = authReq.pin || authReq.otp || authReq.pairCode;
state.otp = state._otp = pin;
state.auth = state.authRequest = state._auth = authReq;
if (!answers.token && answers._can_pair) {
console.info("");
console.info("==============================================");
console.info(" Hey, Listen! ");
console.info("==============================================");
console.info(" ");
console.info(" GO CHECK YOUR EMAIL! ");
console.info(" ");
console.info(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, answers._otp));
console.info(" ");
console.info("==============================================");
console.info("");
}
next();
}
, connect: function (pretoken, next) {
//console.log("Enabling Pairing Locally...");
answers.token = pretoken;
answers._connecting = true;
// TODO use php-style object querification
utils.putConfig('config', Object.keys(answers).map(function (key) {
return key + ':' + answers[key];
}), function (err/*, body*/) {
if (err) {
answers._error = err;
console.error("Error while initializing config [connect]:");
console.error(err);
return;
}
//console.log("Pairing Enabled Locally");
next();
});
}
, offer: function (token, next) {
//console.log("Pairing Enabled by Relay");
answers.token = token;
if (answers._error) {
return;
}
if (answers._connecting) {
return;
}
answers._connecting = true;
utils.putConfig('config', Object.keys(answers).map(function (key) {
return key + ':' + answers[key];
}), function (err/*, body*/) {
if (err) {
answers._error = err;
console.error("Error while initializing config [offer]:");
console.error(err);
return;
}
//console.log("Pairing Enabled Locally");
next();
});
}
, granted: function (_, next) {
//console.log("Token has been granted!");
next();
}
, end: function () {
utils.putConfig('enable', [], function () {
utils.putConfig('list', [], function (err) {
if (err) { console.error(err); return; }
console.info("Success");
// workaround for https://github.com/nodejs/node/issues/21319
if (answers._useTty) {
setTimeout(function () {
console.info();
console.info("Press any key to continue...");
console.info();
process.exit(0);
}, 0.5 * 1000);
}
// end workaround
});
});
}
});
});
return; return;
} }
@ -648,59 +619,167 @@ function parseConfig(err, text) {
} }
help(); help();
process.exit(11);
}
function handleConfig(err, config) {
//console.log('CONFIG');
//console.log(config);
state.config = config;
if (err) { console.error(err); process.exit(101); return; }
//
// check for init first, before anything else
// because it has arguments that may help in
// the next steps
//
if (-1 !== argv.indexOf('init')) {
parsers.init(argv, getToken);
return;
}
if (!state.config.relay || !state.config.token) {
if (!state.config.relay) {
state.config.relay = 'telebit.cloud';
}
console.log("question the user?", Date.now());
askForConfig(state, function (err, state) {
// no errors actually get passed, so this is just future-proofing
if (err) { throw err; }
if (!state.config.token && state._can_pair) {
state.config._otp = common.otp();
}
console.log("done questioning:", Date.now());
state.relay = state.config.relay;
if (!state.token && !state.config.token) {
getToken(err, state);
} else {
parseCli(state);
}
});
return;
}
console.log("no questioning:");
parseCli(state);
}
function parseConfig(err, text) {
console.info("");
console.info(verstr.join(' '));
try {
state._clientConfig = JSON.parse(text || '{}');
} catch(e1) {
try {
state._clientConfig = YAML.safeLoad(text || '{}');
} catch(e2) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
common._init(
// make a default working dir and log dir
state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit')
, (state._clientConfig.root && path.join(state._clientConfig.root, 'etc'))
|| path.resolve(common.DEFAULT_CONFIG_PATH, '..')
);
state._ipc = common.pipename(state._clientConfig, true);
if (!Object.keys(state._clientConfig).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
console.info("");
}
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
utils.request({ service: 'config' }, handleConfig);
} }
var parsers = { var parsers = {
init: function (argv, cb) { init: function (argv, parseCb) {
var answers = {}; var answers = {};
var bool = [ var boolish = [ '--advanced' ];
'--advanced'
];
if ('init' !== argv[0]) { if ('init' !== argv[0]) {
throw new Error("init must be the first argument"); throw new Error("init must be the first argument");
} }
argv.shift(); argv.shift();
utils.request({ service: 'config' }, function (err/*, body*/) { // init --foo bar
if (err) { cb(err); return; } argv.forEach(function (arg, i) {
if (!/^--/.test(arg)) { return; }
if (-1 !== boolish.indexOf(arg)) {
answers['_' + arg.replace(/^--/, '')] = true;
}
if (/^-/.test(argv[i + 1])) {
throw new Error(argv[i + 1] + ' requires an argument');
}
answers[arg] = argv[i + 1];
});
// init --foo bar // init foo:bar
argv.forEach(function (arg, i) { argv.forEach(function (arg) {
if (!/^--/.test(arg)) { return; } if (/^--/.test(arg)) { return; }
if (-1 !== bool.indexOf(arg)) { var parts = arg.split(/:/g);
answers['_' + arg.replace(/^--/, '')] = true; if (2 !== parts.length) {
} throw new Error("bad option to init: '" + arg + "'");
if (/^-/.test(argv[i + 1])) { }
throw new Error(argv[i + 1] + ' requires an argument'); if (answers[parts[0]]) {
} throw new Error("duplicate key to init '" + parts[0] + "'");
answers[arg] = argv[i + 1]; }
}); answers[parts[0]] = parts[1];
// init foo:bar });
argv.forEach(function (arg) {
if (/^--/.test(arg)) { return; }
var parts = arg.split(/:/g);
if (2 !== parts.length) {
throw new Error("bad option to init: '" + arg + "'");
}
if (answers[parts[0]]) {
throw new Error("duplicate key to init '" + parts[0] + "'");
}
answers[parts[0]] = parts[1];
});
if (!answers._advanced && !answers.relay) { // things that aren't straight-forward copy-over
answers.relay = 'telebit.cloud'; if (!answers.advanced && !answers.relay) {
answers.relay = 'telebit.cloud';
}
if (Array.isArray(common._NOTIFICATIONS[answers.update])) {
common._NOTIFICATIONS[answers.update].forEach(function (name) {
state.config[name] = true;
});
}
if (answers.servernames) {
state.config._servernames = answers.servernames;
}
if (answers.ports) {
state.config._ports = answers.ports;
}
// things that are straight-forward copy-over
common.CONFIG_KEYS.forEach(function (key) {
if ('true' === answers[key]) { answers[key] = true; }
if ('false' === answers[key]) { answers[key] = false; }
if ('null' === answers[key]) { answers[key] = null; }
if ('undefined' === answers[key]) { delete answers[key]; }
if ('undefined' !== typeof answers[key]) {
state.config[key] = answers[key];
}
});
askForConfig(state, function (err, state) {
if (err) { parseCb(err); return; }
if (!state.config.token && state._can_pair) {
state.config._otp = common.otp();
} }
askForConfig(answers, function (err, answers) { parseCb(null, state);
if (err) { cb(err); return; }
if (!answers.token && answers._can_pair) {
answers._otp = common.otp();
}
cb(null, answers);
});
}); });
} }
}; };

View File

@ -168,8 +168,9 @@ function serveControlsHelper() {
// TODO camelCase query // TODO camelCase query
state.config.email = conf.email || state.config.email || ''; state.config.email = conf.email || state.config.email || '';
if ('undefined' !== typeof conf.agree_tos) { if ('undefined' !== typeof conf.agreeTos
state.config.agreeTos = conf.agree_tos; || 'undefined' !== typeof conf.agreeTos ) {
state.config.agreeTos = conf.agreeTos || conf.agree_tos;
} }
state.otp = conf._otp || '0000'; // this should only be done on the client side state.otp = conf._otp || '0000'; // this should only be done on the client side
state.config.relay = conf.relay || state.config.relay || ''; state.config.relay = conf.relay || state.config.relay || '';
@ -179,24 +180,28 @@ function serveControlsHelper() {
if (state.secret) { if (state.secret) {
state.token = common.signToken(state); state.token = common.signToken(state);
} }
if (!state.token) {
state.token = conf._token;
}
if ('undefined' !== typeof conf.newsletter) { if ('undefined' !== typeof conf.newsletter) {
state.config.newsletter = conf.newsletter; state.config.newsletter = conf.newsletter;
} }
if ('undefined' !== typeof conf.community_member) { if ('undefined' !== typeof conf.communityMember
state.config.communityMember = conf.community_member; || 'undefined' !== typeof conf.community_member) {
state.config.communityMember = conf.communityMember || conf.community_member;
} }
if ('undefined' !== typeof conf.telemetry) { if ('undefined' !== typeof conf.telemetry) {
state.config.telemetry = conf.telemetry; state.config.telemetry = conf.telemetry;
} }
if (conf.servernames) { if (conf._servernames) {
(conf.servernames||'').split(/,/g).forEach(function (key) { (conf._servernames||'').split(/,/g).forEach(function (key) {
if (!state.config.servernames[key]) { if (!state.config.servernames[key]) {
state.config.servernames[key] = {}; state.config.servernames[key] = {};
} }
}); });
} }
if (conf.ports) { if (conf._ports) {
(conf.ports||'').split(/,/g).forEach(function (key) { (conf._ports||'').split(/,/g).forEach(function (key) {
if (!state.config.ports[key]) { if (!state.config.ports[key]) {
state.config.ports[key] = {}; state.config.ports[key] = {};
} }
@ -475,7 +480,7 @@ function parseConfig(err, text) {
state.config = {}; state.config = {};
} }
common._init( common._init(
state.config.root || path.join(__dirname, '..') state.config.root || path.join(os.homedir(), '.local/share/telebit') // || path.join(__dirname, '..')
, (state.config.root && path.join(state.config.root, 'etc')) || path.resolve(common.DEFAULT_CONFIG_PATH, '..') , (state.config.root && path.join(state.config.root, 'etc')) || path.resolve(common.DEFAULT_CONFIG_PATH, '..')
); );
state._ipc = common.pipename(state.config, true); state._ipc = common.pipename(state.config, true);

View File

@ -9,6 +9,27 @@ var os = require('os');
var homedir = os.homedir(); var homedir = os.homedir();
var urequest = require('@coolaj86/urequest'); var urequest = require('@coolaj86/urequest');
common._NOTIFICATIONS = {
'newsletter': [ 'newsletter', 'communityMember' ]
, 'important': [ 'communityMember' ]
};
common.CONFIG_KEYS = [
'newsletter'
, 'communityMember'
, 'telemetry'
, 'sshAuto'
, 'email'
, 'agreeTos'
, 'relay'
, 'token'
, 'pretoken'
, 'secret'
];
//, '_servernames' // list instead of object
//, '_ports' // list instead of object
//, '_otp' // otp should not be saved
//, '_token' // temporary token
common.pipename = function (config, newApi) { common.pipename = function (config, newApi) {
var _ipc = { var _ipc = {
path: (config.sock || common.DEFAULT_SOCK_PATH) path: (config.sock || common.DEFAULT_SOCK_PATH)
@ -62,7 +83,7 @@ common.signToken = function (state) {
port = parseInt(port, 10); port = parseInt(port, 10);
return port > 0 && port <= 65535; return port > 0 && port <= 65535;
}) })
, aud: state.relayUrl , aud: state._relayUrl
, iss: Math.round(Date.now() / 1000) , iss: Math.round(Date.now() / 1000)
}; };
@ -70,8 +91,8 @@ common.signToken = function (state) {
}; };
common.api = {}; common.api = {};
common.api.directory = function (state, next) { common.api.directory = function (state, next) {
state.relayUrl = common.parseUrl(state.relay); state._relayUrl = common.parseUrl(state.relay);
urequest({ url: state.relayUrl + common.apiDirectory, json: true }, function (err, resp, dir) { urequest({ url: state._relayUrl + common.apiDirectory, json: true }, function (err, resp, dir) {
if (!dir) { dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; } if (!dir) { dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; }
state._apiDirectory = dir; state._apiDirectory = dir;
next(err, dir); next(err, dir);
@ -81,9 +102,8 @@ common.api._parseWss = function (state, dir) {
if (!dir || !dir.api_host) { if (!dir || !dir.api_host) {
dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } };
} }
state.relayHostname = common.parseHostname(state.relay); state._relayHostname = common.parseHostname(state.relay);
state.wss = dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state.relayHostname) + dir.tunnel.pathname; return dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state._relayHostname) + dir.tunnel.pathname;
return state.wss;
}; };
common.api.wss = function (state, cb) { common.api.wss = function (state, cb) {
common.api.directory(state, function (err, dir) { common.api.directory(state, function (err, dir) {
@ -99,12 +119,13 @@ common.api.token = function (state, handlers) {
handlers.tunnelUrl(state.wss, function () { handlers.tunnelUrl(state.wss, function () {
//console.log('[debug] after tunnelUrl'); //console.log('[debug] after tunnelUrl');
if (/*!state.config.token &&*/ state.config.secret) { if (state.config.secret /* && !state.config.token */) {
state.token = common.signToken(state); state.config._token = common.signToken(state);
} }
state.token = state.token || state.config.token; state.token = state.token || state.config.token || state.config._token;
if (state.token) { if (state.token) {
//console.log('[debug] token via token or secret'); //console.log('[debug] token via token or secret');
// { token, pretoken }
handlers.connect(state.token, function () { handlers.connect(state.token, function () {
handlers.end(null, function () {}); handlers.end(null, function () {});
}); });
@ -120,13 +141,13 @@ common.api.token = function (state, handlers) {
// TODO sign token with own private key, including public key and thumbprint // TODO sign token with own private key, including public key and thumbprint
// (much like ACME JOSE account) // (much like ACME JOSE account)
var otp = state.otp || state._otp || '0000'; // common.otp(); var otp = state.config._otp; // common.otp();
var authReq = state.authRequest || state._auth || { var authReq = {
subject: state.config.email subject: state.config.email
, subject_scheme: 'mailto' , subject_scheme: 'mailto'
// TODO create domains list earlier // TODO create domains list earlier
, scope: Object.keys(state.config.servernames || {}) , scope: (state.config._servernames || Object.keys(state.config.servernames || {}))
.concat(Object.keys(state.config.ports || {})).join(',') .concat(state.config._ports || Object.keys(state.config.ports || {})).join(',')
, otp: otp , otp: otp
, hostname: os.hostname() , hostname: os.hostname()
// Used for User-Agent // Used for User-Agent
@ -135,7 +156,7 @@ common.api.token = function (state, handlers) {
, os_release: os.release() , os_release: os.release()
, os_arch: os.arch() , os_arch: os.arch()
}; };
var pairRequestUrl = url.resolve('https://' + dir.api_host.replace(/:hostname/g, state.relayHostname), dir.pair_request.pathname); var pairRequestUrl = url.resolve('https://' + dir.api_host.replace(/:hostname/g, state._relayHostname), dir.pair_request.pathname);
var req = { var req = {
url: pairRequestUrl url: pairRequestUrl
, method: dir.pair_request.method , method: dir.pair_request.method

View File

@ -1,8 +1,13 @@
(function () { (function () {
'use strict'; 'use strict';
var PromiseA;
try {
PromiseA = require('bluebird');
} catch(e) {
PromiseA = global.Promise;
}
var WebSocket = require('ws'); var WebSocket = require('ws');
var PromiseA = require('bluebird');
var sni = require('sni'); var sni = require('sni');
var Packer = require('proxy-packer'); var Packer = require('proxy-packer');
var os = require('os'); var os = require('os');
@ -453,11 +458,11 @@ function _connect(state) {
if (!authenticated) { if (!authenticated) {
if(state.handlers.onError) { if(state.handlers.onError) {
let err = new Error('Failed to connect on first attempt... check authentication'); var err = new Error('Failed to connect on first attempt... check authentication');
state.handlers.onError(err); state.handlers.onError(err);
} }
if(state.handlers.onClose) { if(state.handlers.onClose) {
state.handlers.onClose() state.handlers.onClose();
} }
console.info('[close] failed on first attempt... check authentication.'); console.info('[close] failed on first attempt... check authentication.');
timeoutId = null; timeoutId = null;
@ -470,7 +475,7 @@ function _connect(state) {
timeoutId = setTimeout(connect, 5000); timeoutId = setTimeout(connect, 5000);
} else { } else {
if(state.handlers.onClose) { if(state.handlers.onClose) {
state.handlers.onClose() state.handlers.onClose();
} }
} }
} }

View File

@ -3,6 +3,10 @@
"version": "0.18.9", "version": "0.18.9",
"description": "Break out of localhost. Connect to any device from anywhere over any tcp port or securely in a browser. A secure tunnel. A poor man's reverse VPN.", "description": "Break out of localhost. Connect to any device from anywhere over any tcp port or securely in a browser. A secure tunnel. A poor man's reverse VPN.",
"main": "lib/remote.js", "main": "lib/remote.js",
"files": [
"bin",
"lib"
],
"bin": { "bin": {
"telebit": "bin/telebit.js", "telebit": "bin/telebit.js",
"telebitd": "bin/telebitd.js" "telebitd": "bin/telebitd.js"
@ -48,10 +52,9 @@
}, },
"homepage": "https://git.coolaj86.com/coolaj86/telebit.js#readme", "homepage": "https://git.coolaj86.com/coolaj86/telebit.js#readme",
"dependencies": { "dependencies": {
"@coolaj86/urequest": "^1.1.1", "@coolaj86/urequest": "^1.1.4",
"bluebird": "^3.5.1",
"finalhandler": "^1.1.1", "finalhandler": "^1.1.1",
"greenlock": "^2.2.20", "greenlock": "^2.3.1",
"js-yaml": "^3.11.0", "js-yaml": "^3.11.0",
"jsonwebtoken": "^7.1.9", "jsonwebtoken": "^7.1.9",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@ -63,6 +66,9 @@
"socket-pair": "^1.0.3", "socket-pair": "^1.0.3",
"ws": "^2.2.3" "ws": "^2.2.3"
}, },
"trulyOptionalDependencies": {
"bluebird": "^3.5.1"
},
"enginesStrict": true, "enginesStrict": true,
"engines": { "engines": {
"node": "10.2.1 10.4" "node": "10.2.1 10.4"

View File

@ -206,6 +206,9 @@ pushd $TELEBIT_TMP >/dev/null
echo -n "." echo -n "."
fi fi
$tmp_npm install >/dev/null 2>/dev/null & $tmp_npm install >/dev/null 2>/dev/null &
# ursa is now an entirely optional dependency for key generation
# but very much needed on ARM devices
$tmp_npm install ursa >/dev/null 2>/dev/null &
tmp_npm_pid=$! tmp_npm_pid=$!
while [ -n "$tmp_npm_pid" ]; do while [ -n "$tmp_npm_pid" ]; do
sleep 2 sleep 2