handle pairing request via API

This commit is contained in:
AJ ONeal 2018-06-21 06:10:49 +00:00
parent a763ead434
commit 99b891fd99
5 changed files with 111 additions and 131 deletions

View File

@ -14,6 +14,7 @@ var recase = require('recase').create({});
var camelCopy = recase.camelCopy.bind(recase); var camelCopy = recase.camelCopy.bind(recase);
//var snakeCopy = recase.snakeCopy.bind(recase); //var snakeCopy = recase.snakeCopy.bind(recase);
var urequest = require('@coolaj86/urequest');
var common = require('../lib/cli-common.js'); var common = require('../lib/cli-common.js');
var argv = process.argv.slice(2); var argv = process.argv.slice(2);
@ -141,7 +142,7 @@ function askForConfig(answers, mainCb) {
if (!relay) { relay = 'telebit.cloud'; } if (!relay) { relay = 'telebit.cloud'; }
relay = relay.trim(); relay = relay.trim();
var urlstr = common.parseUrl(relay) + common.apiDirectory; var urlstr = common.parseUrl(relay) + common.apiDirectory;
common.urequest({ url: urlstr, json: true }, function (err, resp, body) { urequest({ url: urlstr, json: true }, function (err, resp, body) {
if (err) { if (err) {
console.error("[Network Error] Failed to retrieve '" + urlstr + "'"); console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
console.error(err); console.error(err);

View File

@ -512,75 +512,9 @@ function connectTunnel() {
state.sortingHat = state.config.sortingHat; state.sortingHat = state.config.sortingHat;
// TODO sortingHat.print(); ? // TODO sortingHat.print(); ?
if (state.config.email && !state.token) {
console.info();
console.info('====================================');
console.info('= HEY! LISTEN! =');
console.info('====================================');
console.info('= =');
console.info('= 1. Open your email =');
console.info('= =');
console.info('= 2. Click the magic login link =');
console.info('= Login Code (if needed): 0000 ='.replace('0000', state.otp));
console.info('= =');
console.info('= 3. Check back here for deets =');
console.info('= =');
console.info('= =');
console.info('====================================');
console.info();
}
// TODO Check undefined vs false for greenlock config // TODO Check undefined vs false for greenlock config
var remote = require('../'); var remote = require('../');
state.handlers = {
grant: function (grants) {
console.info("");
console.info("Connect to your device by any of the following means:");
console.info("");
grants.forEach(function (arr) {
if ('https' === arr[0]) {
if (!state.servernames[arr[1]]) {
state.servernames[arr[1]] = {};
}
} else if ('tcp' === arr[0]) {
if (!state.ports[arr[2]]) {
state.ports[arr[2]] = {};
}
}
if ('ssh+https' === arr[0]) {
console.info("SSH+HTTPS");
} else if ('ssh' === arr[0]) {
console.info("SSH");
} else if ('tcp' === arr[0]) {
console.info("TCP");
} else if ('https' === arr[0]) {
console.info("HTTPS");
}
console.info('\t' + arr[0] + '://' + arr[1] + (arr[2] ? (':' + arr[2]) : ''));
if ('ssh+https' === arr[0]) {
console.info("\tex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -servername %h -quiet' " + arr[1] + " -p 443\n");
} else if ('ssh' === arr[0]) {
console.info("\tex: ssh " + arr[1] + " -p " + arr[2] + "\n");
} else if ('tcp' === arr[0]) {
console.info("\tex: netcat " + arr[1] + " " + arr[2] + "\n");
} else if ('https' === arr[0]) {
console.info("\tex: curl https://" + arr[1] + "\n");
}
});
}
, access_token: function (opts) {
state.token = opts.jwt;
state.config.token = opts.jwt;
console.info("Updating '" + tokenpath + "' with new token:");
try {
require('fs').writeFileSync(tokenpath, opts.jwt);
} catch (e) {
console.error("Token not saved:");
console.error(e);
}
}
};
console.log(); console.log();
state.greenlockConfig = { state.greenlockConfig = {
version: state.greenlockConf.version || 'draft-11' version: state.greenlockConf.version || 'draft-11'
@ -696,31 +630,122 @@ function rawTunnel(cb) {
, os_arch: os.arch() , os_arch: os.arch()
}; };
if (state.config.email && !state.token) {
console.info();
console.info('====================================');
console.info('= HEY! LISTEN! =');
console.info('====================================');
console.info('= =');
console.info('= 1. Open your email =');
console.info('= =');
console.info('= 2. Click the magic login link =');
console.info('= Login Code (if needed): 0000 ='.replace('0000', state.otp));
console.info('= =');
console.info('= 3. Check back here for deets =');
console.info('= =');
console.info('= =');
console.info('====================================');
console.info();
}
if (err || !body || !body.pair_request) { if (err || !body || !body.pair_request) {
cb(null, connectTunnel()); cb(null, connectTunnel());
return; return;
} }
// TODO do auth stuff // TODO do auth stuff
var pairRequest = url.resolve('https://' + body.api_host.replace(/:hostname/g, state.relayHostname), body.pair_request.pathname); var pairRequestUrl = url.resolve('https://' + body.api_host.replace(/:hostname/g, state.relayHostname), body.pair_request.pathname);
var req = { var req = {
url: pairRequest url: pairRequestUrl
, method: body.pair_request.method , method: body.pair_request.method
, json: state._auth , json: state._auth
}; };
console.log('[telebitd.js] req'); console.log('[telebitd.js] req');
console.log(req); console.log(req);
urequest(req, function (err, resp, body) {
if (err) { console.error('[telebitd.js] pair request', err); }
function gotoNext(req) {
urequest(req, function (err, resp, body) {
if (err) { console.error('[telebitd.js] pair request', err); return; }
console.log('\nToken Request Body:');
console.log(resp.headers);
console.log(body); console.log(body);
// TODO poll for token console.info('Device Pair Code: 0000'.replace('0000', state.otp));
//cb(null, connectTunnel());
// pending, try again
if (resp.headers.location) {
setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true });
return;
} }
);
if ('ready' !== body.status) {
console.error("\n[error] neither ready nor pending...");
console.error(body);
return;
}
state.token = body.access_token;
state.config.token = state.token;
state.handlers.access_token({ jwt: state.token });
cb(null, connectTunnel());
});
}
gotoNext(req);
}); });
} }
state.handlers = {
grant: function (grants) {
console.info("");
console.info("Connect to your device by any of the following means:");
console.info("");
grants.forEach(function (arr) {
if ('https' === arr[0]) {
if (!state.servernames[arr[1]]) {
state.servernames[arr[1]] = {};
}
} else if ('tcp' === arr[0]) {
if (!state.ports[arr[2]]) {
state.ports[arr[2]] = {};
}
}
if ('ssh+https' === arr[0]) {
console.info("SSH+HTTPS");
} else if ('ssh' === arr[0]) {
console.info("SSH");
} else if ('tcp' === arr[0]) {
console.info("TCP");
} else if ('https' === arr[0]) {
console.info("HTTPS");
}
console.info('\t' + arr[0] + '://' + arr[1] + (arr[2] ? (':' + arr[2]) : ''));
if ('ssh+https' === arr[0]) {
console.info("\tex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -servername %h -quiet' " + arr[1] + " -p 443\n");
} else if ('ssh' === arr[0]) {
console.info("\tex: ssh " + arr[1] + " -p " + arr[2] + "\n");
} else if ('tcp' === arr[0]) {
console.info("\tex: netcat " + arr[1] + " " + arr[2] + "\n");
} else if ('https' === arr[0]) {
console.info("\tex: curl https://" + arr[1] + "\n");
}
});
}
, access_token: function (opts) {
state.token = opts.jwt;
state.config.token = opts.jwt;
console.info("Updating '" + tokenpath + "' with new token:");
try {
require('fs').writeFileSync(tokenpath, opts.jwt);
} catch (e) {
console.error("Token not saved:");
console.error(e);
}
}
};
require('fs').readFile(confpath, 'utf8', parseConfig); require('fs').readFile(confpath, 'utf8', parseConfig);
}()); }());

View File

@ -52,7 +52,8 @@ common.parseHostname = function (hostname) {
common.apiDirectory = '_apis/telebit.cloud/index.json'; common.apiDirectory = '_apis/telebit.cloud/index.json';
function leftpad(i, n, c) { function leftpad(i, n, c) {
while (i.toString().length < (n || 4)) { i = i.toString();
while (i.length < (n || 4)) {
i = (c || '0') + i; i = (c || '0') + i;
} }
return i; return i;
@ -61,57 +62,6 @@ common.otp = function getOtp() {
return leftpad(Math.round(Math.random() * 9999), 4, '0'); return leftpad(Math.round(Math.random() * 9999), 4, '0');
}; };
common.urequest = function (opts, cb) {
var https = require('https');
// request.js behavior:
// encoding: null + json ? unknown
// json => attempt to parse, fail silently
// encoding => buffer.toString(encoding)
// null === encoding => Buffer.concat(buffers)
https.get(opts.url, function (resp) {
var encoding = opts.encoding;
if (null === encoding) {
resp._body = [];
} else {
resp.body = '';
}
if (!resp.headers['content-length'] || 0 === parseInt(resp.headers['content-length'], 10)) {
cb(resp);
}
resp._bodyLength = 0;
resp.on('data', function (chunk) {
if ('string' === typeof resp.body) {
resp.body += chunk.toString(encoding);
} else {
resp._body.push(chunk);
resp._bodyLength += chunk.length;
}
});
resp.on('end', function () {
if ('string' !== typeof resp.body) {
if (1 === resp._body.length) {
resp.body = resp._body[0];
} else {
resp.body = Buffer.concat(resp._body, resp._bodyLength);
}
resp._body = null;
}
if (opts.json && 'string' === typeof resp.body) {
// TODO I would parse based on Content-Type
// but request.js doesn't do that.
try {
resp.body = JSON.parse(resp.body);
} catch(e) {
// ignore
}
}
cb(null, resp, resp.body);
});
}).on('error', function (e) {
cb(e);
});
};
try { try {
mkdirp.sync(path.join(__dirname, '..', 'var', 'log')); mkdirp.sync(path.join(__dirname, '..', 'var', 'log'));
mkdirp.sync(path.join(__dirname, '..', 'var', 'run')); mkdirp.sync(path.join(__dirname, '..', 'var', 'run'));

View File

@ -4,8 +4,8 @@
"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",
"bin": { "bin": {
"telebit": "bin/telebit.js" "telebit": "bin/telebit.js",
, "telebitd": "bin/telebitd.js" "telebitd": "bin/telebitd.js"
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@ -48,6 +48,7 @@
}, },
"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",
"bluebird": "^3.5.1", "bluebird": "^3.5.1",
"commander": "^2.9.0", "commander": "^2.9.0",
"finalhandler": "^1.1.1", "finalhandler": "^1.1.1",

View File

@ -1,6 +1,9 @@
'use strict'; 'use strict';
var email = 'jon@example.com'; var email = 'jon@example.com';
var pin = Math.round(Math.random() * 999999).toString().padStart(6, '0'); // '321654'
console.log('Pair Code:', pin);
var urequest = require('@coolaj86/urequest'); var urequest = require('@coolaj86/urequest');
var req = { var req = {
@ -11,8 +14,8 @@ var req = {
subject: email subject: email
, subject_scheme: 'mailto' , subject_scheme: 'mailto'
, scope: '' , scope: ''
, otp: '321654' , otp: pin
, hostname: "Jon's Macbook Pro" , hostname: "User's Macbook Pro"
, os_type: 'Linux' , os_type: 'Linux'
, os_platform: 'linux' , os_platform: 'linux'
, os_release: '4.4.0-116-generic' , os_release: '4.4.0-116-generic'