From 99b891fd996dea284ed0168bf78edb4e914940f6 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 21 Jun 2018 06:10:49 +0000 Subject: [PATCH] handle pairing request via API --- bin/telebit.js | 3 +- bin/telebitd.js | 173 ++++++++++++++++++++++++------------------ lib/cli-common.js | 54 +------------ package.json | 5 +- tests/pair-request.js | 7 +- 5 files changed, 111 insertions(+), 131 deletions(-) diff --git a/bin/telebit.js b/bin/telebit.js index 6462cc0..bec769c 100755 --- a/bin/telebit.js +++ b/bin/telebit.js @@ -14,6 +14,7 @@ var recase = require('recase').create({}); var camelCopy = recase.camelCopy.bind(recase); //var snakeCopy = recase.snakeCopy.bind(recase); +var urequest = require('@coolaj86/urequest'); var common = require('../lib/cli-common.js'); var argv = process.argv.slice(2); @@ -141,7 +142,7 @@ function askForConfig(answers, mainCb) { if (!relay) { relay = 'telebit.cloud'; } relay = relay.trim(); 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) { console.error("[Network Error] Failed to retrieve '" + urlstr + "'"); console.error(err); diff --git a/bin/telebitd.js b/bin/telebitd.js index a7dd116..b1d2198 100755 --- a/bin/telebitd.js +++ b/bin/telebitd.js @@ -512,75 +512,9 @@ function connectTunnel() { state.sortingHat = state.config.sortingHat; // 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 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(); state.greenlockConfig = { version: state.greenlockConf.version || 'draft-11' @@ -696,31 +630,122 @@ function rawTunnel(cb) { , 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) { cb(null, connectTunnel()); return; } // 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 = { - url: pairRequest + url: pairRequestUrl , method: body.pair_request.method , json: state._auth }; console.log('[telebitd.js] 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); - // TODO poll for token - //cb(null, connectTunnel()); - } - ); + console.info('Device Pair Code: 0000'.replace('0000', state.otp)); + + // 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); }()); diff --git a/lib/cli-common.js b/lib/cli-common.js index 1eca15f..67ca9af 100644 --- a/lib/cli-common.js +++ b/lib/cli-common.js @@ -52,7 +52,8 @@ common.parseHostname = function (hostname) { common.apiDirectory = '_apis/telebit.cloud/index.json'; function leftpad(i, n, c) { - while (i.toString().length < (n || 4)) { + i = i.toString(); + while (i.length < (n || 4)) { i = (c || '0') + i; } return i; @@ -61,57 +62,6 @@ common.otp = function getOtp() { 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 { mkdirp.sync(path.join(__dirname, '..', 'var', 'log')); mkdirp.sync(path.join(__dirname, '..', 'var', 'run')); diff --git a/package.json b/package.json index 853819f..7341375 100644 --- a/package.json +++ b/package.json @@ -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.", "main": "lib/remote.js", "bin": { - "telebit": "bin/telebit.js" - , "telebitd": "bin/telebitd.js" + "telebit": "bin/telebit.js", + "telebitd": "bin/telebitd.js" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -48,6 +48,7 @@ }, "homepage": "https://git.coolaj86.com/coolaj86/telebit.js#readme", "dependencies": { + "@coolaj86/urequest": "^1.1.1", "bluebird": "^3.5.1", "commander": "^2.9.0", "finalhandler": "^1.1.1", diff --git a/tests/pair-request.js b/tests/pair-request.js index 0c52be8..e7ad3a1 100644 --- a/tests/pair-request.js +++ b/tests/pair-request.js @@ -1,6 +1,9 @@ 'use strict'; 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 req = { @@ -11,8 +14,8 @@ var req = { subject: email , subject_scheme: 'mailto' , scope: '' - , otp: '321654' - , hostname: "Jon's Macbook Pro" + , otp: pin + , hostname: "User's Macbook Pro" , os_type: 'Linux' , os_platform: 'linux' , os_release: '4.4.0-116-generic'