diff --git a/lib/admin/index.html b/lib/admin/index.html
index c63b5d1..b10cbb7 100644
--- a/lib/admin/index.html
+++ b/lib/admin/index.html
@@ -4,9 +4,19 @@
Telebit Setup
+
+
Telebit (Remote) Setup
+
+ {{ views.flash.error }}
+
+
+
+
+
+
+
+
diff --git a/lib/admin/js/app.js b/lib/admin/js/app.js
index f937748..2cfedd5 100644
--- a/lib/admin/js/app.js
+++ b/lib/admin/js/app.js
@@ -34,20 +34,17 @@ api.config = function apiConfig() {
api.status = function apiStatus() {
return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) {
var json = resp.body;
- appData.status = json;
return json;
});
};
api.initialize = function apiInitialize() {
var opts = {
- url: "/api/init"
+ url: "/api/xxinitxx"
, method: "POST"
, headers: {
'Content-Type': 'application/json'
}
- , body: JSON.stringify({
- foo: 'bar'
- })
+ , body: JSON.stringify(telebitState.config)
};
return Telebit.reqLocalAsync(opts).then(function (resp) {
var json = resp.body;
@@ -59,6 +56,47 @@ api.initialize = function apiInitialize() {
});
};
+function showOtp(otp, pollUrl) {
+ localStorage.setItem('poll_url', pollUrl);
+ telebitState.pollUrl = pollUrl;
+ appData.init.otp = otp;
+ changeState('otp');
+}
+function doConfigure() {
+ if (telebitState.dir.pair_request) {
+ telebitState._can_pair = true;
+ }
+
+ //
+ // Read config from form
+ //
+
+ // Create Empty Config, If Necessary
+ if (!telebitState.config) { telebitState.config = {}; }
+ if (!telebitState.config.greenlock) { telebitState.config.greenlock = {}; }
+
+ // Populate Config
+ if (appData.init.teletos && appData.init.letos) { telebitState.config.agreeTos = true; }
+ if (appData.init.relay) { telebitState.config.relay = appData.init.relay; }
+ if (appData.init.email) { telebitState.config.email = appData.init.email; }
+ if ('undefined' !== typeof appData.init.letos) { telebitState.config.greenlock.agree = appData.init.letos; }
+ if ('newsletter' === appData.init.notifications) {
+ telebitState.config.newsletter = true; telebitState.config.communityMember = true;
+ }
+ if ('important' === appData.init.notifications) { telebitState.config.communityMember = true; }
+ if (appData.init.acmeVersion) { telebitState.config.greenlock.version = appData.init.acmeVersion; }
+ if (appData.init.acmeServer) { telebitState.config.greenlock.server = appData.init.acmeServer; }
+
+ // Temporary State
+ telebitState._otp = Telebit.otp();
+ appData.init.otp = telebitState._otp;
+
+ return Telebit.authorize(telebitState, showOtp).then(function () {
+ console.log('1 api.init...');
+ return api.initialize();
+ });
+}
+
// TODO test for internet connectivity (and telebit connectivity)
var DEFAULT_RELAY = 'telebit.cloud';
var BETA_RELAY = 'telebit.ppl.family';
@@ -83,9 +121,15 @@ var appData = {
, tcp: null
, ssh: null
, views: {
- section: {
- setup: false
+ flash: {
+ error: ""
+ }
+ , section: {
+ loading: true
+ , setup: false
, advanced: false
+ , otp: false
+ , status: false
}
}
};
@@ -98,34 +142,28 @@ var appMethods = {
}
appData.init.relay = appData.init.relay.toLowerCase();
telebitState = { relay: appData.init.relay };
+
return Telebit.api.directory(telebitState).then(function (dir) {
if (!dir.api_host) {
window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service");
return;
}
+
+ telebitState.dir = dir;
+
+ // If it's one of the well-known relays
if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
- if (!telebitState.config) { telebitState.config = {}; }
- if (!telebitState.config.relay) { telebitState.config.relay = telebitState.relay; }
- telebitState.config.email = appData.init.email;
- telebitState.config._otp = Telebit.otp();
- return Telebit.authorize(telebitState).then(function () {
- console.log('1 api.init...');
- return api.initialize();
- }).catch(function (err) {
- console.error(err);
- window.alert("Error: [authorize] " + (err.message || JSON.stringify(err, null, 2)));
- });
+ return doConfigure();
} else {
changeState('advanced');
}
}).catch(function (err) {
console.error(err);
- window.alert("Error: [directory] " + (err.message || JSON.stringify(err, null, 2)));
+ window.alert("Error: [initialize] " + (err.message || JSON.stringify(err, null, 2)));
});
}
, advance: function () {
- console.log('2 api.init...');
- return api.initialize();
+ return doConfigure();
}
, productionAcme: function () {
console.log("prod acme:");
@@ -157,10 +195,26 @@ var appStates = {
, advanced: function () {
appData.views.section = { advanced: true };
}
+, otp: function () {
+ appData.views.section = { otp: true };
+ }
+, status: function () {
+ appData.views.section = { status: true };
+ return api.status().then(function (status) {
+ appData.status = status;
+ });
+ }
};
function changeState(newstate) {
- location.hash = '#/' + newstate + '/';
+ var newhash = '#/' + newstate + '/';
+ if (location.hash === newhash) {
+ if (!telebitState.firstState) {
+ telebitState.firstState = true;
+ setState();
+ }
+ }
+ location.hash = newhash;
}
window.addEventListener('hashchange', setState, false);
function setState(/*ev*/) {
@@ -183,11 +237,52 @@ new Vue({
});
-api.config();
-api.status().then(function () {
- changeState('setup');
- setState();
+api.config().then(function (config) {
+ telebitState.config = config;
+ if (config.greenlock) {
+ appData.init.acmeServer = config.greenlock.server;
+ }
+ if (config.relay) {
+ appData.init.relay = config.relay;
+ }
+ if (config.email) {
+ appData.init.email = config.email;
+ }
+ if (config.agreeTos) {
+ appData.init.letos = config.agreeTos;
+ appData.init.teletos = config.agreeTos;
+ }
+ if (config._otp) {
+ appData.init.otp = config._otp;
+ }
+
+ telebitState.pollUrl = config._pollUrl || localStorage.getItem('poll_url');
+
+ if ((!config.token && !config._otp) || !config.relay || !config.email || !config.agreeTos) {
+ changeState('setup');
+ setState();
+ return;
+ }
+ if (!config.token && config._otp) {
+ changeState('otp');
+ setState();
+ // this will skip ahead as necessary
+ return Telebit.authorize(telebitState, showOtp).then(function () {
+ console.log('2 api.init...');
+ return api.initialize();
+ });
+ }
+
+ // TODO handle default state
+ changeState('status');
+}).catch(function (err) {
+ appData.views.flash.error = err.message || JSON.stringify(err, null, 2);
});
window.api = api;
+
+setTimeout(function () {
+ document.body.hidden = false;
+}, 50);
+
}());
diff --git a/lib/admin/js/telebit-token.js b/lib/admin/js/telebit-token.js
index 2cb3204..0c3fcfe 100644
--- a/lib/admin/js/telebit-token.js
+++ b/lib/admin/js/telebit-token.js
@@ -11,19 +11,14 @@ if ('undefined' !== typeof Promise) {
var common = exports.TELEBIT || require('./lib/common.js');
-common.authorize = common.getToken = function getToken(state) {
+common.authorize = common.getToken = function getToken(state, showOtp) {
state.relay = state.config.relay;
// { _otp, config: {} }
return common.api.token(state, {
- error: function (err) {
- console.error("[Error] common.api.token handlers.error:");
- console.error(err);
- return PromiseA.reject(err);
- }
+ error: function (err) { console.error("[Error] common.api.token handlers.error: \n", err); return PromiseA.reject(err); }
, directory: function (dir) {
- //console.log('[directory] Telebit Relay Discovered:');
- //console.log(dir);
+ /*console.log('[directory] Telebit Relay Discovered:', dir);*/
state._apiDirectory = dir;
return PromiseA.resolve();
}
@@ -32,12 +27,13 @@ common.authorize = common.getToken = function getToken(state) {
state.wss = tunnelUrl;
return PromiseA.resolve();
}
- , requested: function (authReq) {
+ , requested: function (authReq, pollUrl) {
console.log("[requested] Pairing Requested");
- state.config._otp = state.config._otp = authReq.otp;
+ state._otp = state._otp = authReq.otp;
if (!state.config.token && state._can_pair) {
- console.info("0000".replace(/0000/g, state.config._otp));
+ console.info("0000".replace(/0000/g, state._otp));
+ showOtp(authReq.otp, pollUrl);
}
return PromiseA.resolve();
@@ -47,7 +43,9 @@ common.authorize = common.getToken = function getToken(state) {
state.config.pretoken = pretoken;
state._connecting = true;
- return common.reqLocalAsync({ url: '/api/config', method: 'POST', body: state.config }).then(function () {
+ // This will only be saved to the session
+ state.config._otp = state._otp;
+ return common.reqLocalAsync({ url: '/api/config', method: 'POST', body: state.config, json: true }).then(function () {
console.info("waiting...");
return PromiseA.resolve();
}).catch(function (err) {
@@ -59,6 +57,7 @@ common.authorize = common.getToken = function getToken(state) {
}
, offer: function (token) {
//console.log("[offer] Pairing Enabled by Relay");
+ state.token = token;
state.config.token = token;
if (state._error) {
return;
@@ -77,7 +76,7 @@ common.authorize = common.getToken = function getToken(state) {
} catch(e) {
console.warn("[warning] could not decode token");
}
- return common.reqLocalAsync({ url: '/api/config', method: 'POST', body: state.config }).then(function () {
+ return common.reqLocalAsync({ url: '/api/config', method: 'POST', body: state.config, json: true }).then(function () {
//console.log("Pairing Enabled Locally");
return PromiseA.resolve();
}).catch(function (err) {
@@ -87,12 +86,9 @@ common.authorize = common.getToken = function getToken(state) {
return PromiseA.reject(err);
});
}
- , granted: function (/*_*/) {
- //console.log("[grant] Pairing complete!");
- return PromiseA.resolve();
- }
+ , granted: function (/*_*/) { /*console.log("[grant] Pairing complete!");*/ return PromiseA.resolve(); }
, end: function () {
- return common.reqLocalAsync({ url: '/api/enable', method: 'POST', body: [] }).then(function () {
+ return common.reqLocalAsync({ url: '/api/enable', method: 'POST', body: [], json: true }).then(function () {
console.info("Success");
// workaround for https://github.com/nodejs/node/issues/21319
@@ -112,9 +108,6 @@ common.authorize = common.getToken = function getToken(state) {
// end workaround
//parseCli(state);
- }).catch(function (err) {
- console.error('[end] [error]', err);
- return PromiseA.reject(err);
});
}
});
diff --git a/lib/admin/js/telebit.js b/lib/admin/js/telebit.js
index e6c8907..d0ab85c 100644
--- a/lib/admin/js/telebit.js
+++ b/lib/admin/js/telebit.js
@@ -48,11 +48,15 @@ if ('undefined' !== typeof fetch) {
if (!opts) { opts = {}; }
if (opts.json && true !== opts.json) {
opts.body = opts.json;
+ opts.json = true;
}
if (opts.json) {
if (!opts.headers) { opts.headers = {}; }
if (opts.body) {
opts.headers['Content-Type'] = 'application/json';
+ if ('string' !== typeof opts.body) {
+ opts.body = JSON.stringify(opts.body);
+ }
} else {
opts.headers.Accepts = 'application/json';
}
@@ -126,15 +130,16 @@ common.signToken = function (state) {
return jwt.sign(tokenData, state.config.secret);
};
common.promiseTimeout = function (ms) {
- var x = new PromiseA(function (resolve) {
- x._tok = setTimeout(function () {
+ var tok;
+ var p = new PromiseA(function (resolve) {
+ tok = setTimeout(function () {
resolve();
}, ms);
});
- x.cancel = function () {
- clearTimeout(x._tok);
+ p.cancel = function () {
+ clearTimeout(tok);
};
- return x;
+ return p;
};
common.api = {};
common.api.directory = function (state) {
@@ -145,15 +150,10 @@ common.api.directory = function (state) {
if (state._relays[state._relayUrl]) {
return PromiseA.resolve(state._relays[state._relayUrl]);
}
- console.error('aaaaaaaaabsnthsnth');
return common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) {
- console.error('123aaaaaaaaabsnthsnth');
var dir = resp.body;
state._relays[state._relayUrl] = dir;
return dir;
- }).catch(function (err) {
- console.error('bsnthsnth');
- return PromiseA.reject(err);
});
};
common.api._parseWss = function (state, dir) {
@@ -169,15 +169,63 @@ common.api.wss = function (state) {
});
};
common.api.token = function (state, handlers) {
+
+ var firstReady = true;
+ function pollStatus(req) {
+ if (common.debug) { console.log('[debug] pollStatus called'); }
+ if (common.debug) { console.log(req); }
+ return common.requestAsync(req).then(function checkLocation(resp) {
+ var body = resp.body;
+ if (common.debug) { console.log('[debug] checkLocation'); }
+ if (common.debug) { console.log(body); }
+ // pending, try again
+ if ('pending' === body.status && resp.headers.location) {
+ if (common.debug) { console.log('[debug] pending'); }
+ return common.promiseTimeout(2 * 1000).then(function () {
+ return pollStatus({ url: resp.headers.location, json: true });
+ });
+ } else if ('ready' === body.status) {
+ if (common.debug) { console.log('[debug] ready'); }
+ if (firstReady) {
+ if (common.debug) { console.log('[debug] first ready'); }
+ firstReady = false;
+ // falls through on purpose
+ PromiseA.resolve(handlers.offer(body.access_token)).then(function () {
+ /*ignore*/
+ });
+ }
+ return common.promiseTimeout(2 * 1000).then(function () {
+ return pollStatus(req);
+ });
+ } else if ('complete' === body.status) {
+ if (common.debug) { console.log('[debug] complete'); }
+ return PromiseA.resolve(handlers.granted(null)).then(function () {
+ return PromiseA.resolve(handlers.end(null)).then(function () {});
+ });
+ } else {
+ if (common.debug) { console.log('[debug] bad status'); }
+ var err = new Error("Bad State:" + body.status);
+ err._request = req;
+ return PromiseA.reject(err);
+ }
+ }).catch(function (err) {
+ if (common.debug) { console.log('[debug] pollStatus error'); }
+ err._request = req;
+ err._hint = '[telebitd.js] pair request';
+ return PromiseA.resolve(handlers.error(err)).then(function () {});
+ });
+ }
+
// directory, requested, connect, tunnelUrl, offer, granted, end
- function afterDir(dir) {
+ function requestAuth(dir) {
if (common.debug) { console.log('[debug] after dir'); }
state.wss = common.api._parseWss(state, dir);
return PromiseA.resolve(handlers.tunnelUrl(state.wss)).then(function () {
if (common.debug) { console.log('[debug] after tunnelUrl'); }
if (state.config.secret /* && !state.config.token */) {
- state.config._token = common.signToken(state);
+ // TODO make token here in the browser
+ //state.config._token = common.signToken(state);
}
state.token = state.token || state.config.token || state.config._token;
if (state.token) {
@@ -190,13 +238,13 @@ common.api.token = function (state, handlers) {
if (!dir.pair_request) {
if (common.debug) { console.log('[debug] no dir, connect'); }
- return PromiseA.resolve(handlers.error(err || new Error("No token found or generated, and no pair_request api found.")));
+ return PromiseA.resolve(handlers.error(new Error("No token found or generated, and no pair_request api found.")));
}
// TODO sign token with own private key, including public key and thumbprint
// (much like ACME JOSE account)
// TODO handle agree
- var otp = state.config._otp; // common.otp();
+ var otp = state._otp; // common.otp();
var authReq = {
subject: state.config.email
, subject_scheme: 'mailto'
@@ -236,88 +284,39 @@ common.api.token = function (state, handlers) {
, method: dir.pair_request.method
, json: authReq
};
- var firstReq = true;
- var firstReady = true;
- function gotoNext(req) {
- if (common.debug) { console.log('[debug] gotoNext called'); }
- if (common.debug) { console.log(req); }
- return common.requestAsync(req).then(function (resp) {
- var body = resp.body;
-
- function checkLocation() {
- if (common.debug) { console.log('[debug] checkLocation'); }
- if (common.debug) { console.log(body); }
- // pending, try again
- if ('pending' === body.status && resp.headers.location) {
- if (common.debug) { console.log('[debug] pending'); }
- return common.promiseTimeout(2 * 1000).then(function () {
- return gotoNext({ url: resp.headers.location, json: true });
- });
- } else if ('ready' === body.status) {
- if (common.debug) { console.log('[debug] ready'); }
- if (firstReady) {
- if (common.debug) { console.log('[debug] first ready'); }
- firstReady = false;
- state.token = body.access_token;
- state.config.token = state.token;
- // falls through on purpose
- PromiseA.resolve(handlers.offer(body.access_token)).then(function () {
- /*ignore*/
- });
- }
- return common.promiseTimeout(2 * 1000).then(function () {
- return gotoNext(req);
- });
- } else if ('complete' === body.status) {
- if (common.debug) { console.log('[debug] complete'); }
- return PromiseA.resolve(handlers.granted(null)).then(function () {
- return PromiseA.resolve(handlers.end(null)).then(function () {});
- });
- } else {
- if (common.debug) { console.log('[debug] bad status'); }
- var err = new Error("Bad State:" + body.status);
- err._request = req;
- return PromiseA.resolve(handlers.error(err));
+ return common.requestAsync(req).then(function doFirst(resp) {
+ var body = resp.body;
+ if (common.debug) { console.log('[debug] first req'); }
+ if (!body.access_token && !body.jwt) {
+ return PromiseA.reject(new Error("something wrong with pre-authorization request"));
+ }
+ return PromiseA.resolve(handlers.requested(authReq, resp.headers.location)).then(function () {
+ return PromiseA.resolve(handlers.connect(body.access_token || body.jwt)).then(function () {
+ var err;
+ if (!resp.headers.location) {
+ err = new Error("bad authentication request response");
+ err._resp = resp.toJSON && resp.toJSON();
+ return PromiseA.resolve(handlers.error(err)).then(function () {});
}
- }
-
- if (firstReq) {
- if (common.debug) { console.log('[debug] first req'); }
- if (!body.access_token && !body.jwt) {
- return PromiseA.reject(new Error("something wrong with pre-authorization request"));
- }
- firstReq = false;
- return PromiseA.resolve(handlers.requested(authReq)).then(function () {
- return PromiseA.resolve(handlers.connect(body.access_token || body.jwt)).then(function () {
- var err;
- if (!resp.headers.location) {
- err = new Error("bad authentication request response");
- err._resp = resp.toJSON && resp.toJSON();
- return PromiseA.resolve(handlers.error(err)).then(function () {});
- }
- return common.promiseTimeout(2 * 1000).then(function () {
- return gotoNext({ url: resp.headers.location, json: true });
- });
- });
+ return common.promiseTimeout(2 * 1000).then(function () {
+ return pollStatus({ url: resp.headers.location, json: true });
});
- } else {
- if (common.debug) { console.log('[debug] other req'); }
- return checkLocation();
- }
- }).catch(function (err) {
- if (common.debug) { console.log('[debug] gotoNext error'); }
- err._request = req;
- err._hint = '[telebitd.js] pair request';
- return PromiseA.resolve(handlers.error(err)).then(function () {});
+ });
});
- }
-
- return gotoNext(req);
-
+ }).catch(function (err) {
+ if (common.debug) { console.log('[debug] gotoFirst error'); }
+ err._request = req;
+ err._hint = '[telebitd.js] pair request';
+ return PromiseA.resolve(handlers.error(err)).then(function () {});
+ });
});
}
+ if (state.pollUrl) {
+ return pollStatus({ url: state.pollUrl, json: true });
+ }
+
// backwards compat (TODO verify we can remove this)
var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }';
return common.api.directory(state).then(function (dir) {
@@ -331,9 +330,9 @@ common.api.token = function (state, handlers) {
}).then(function (dir) {
return PromiseA.resolve(handlers.directory(dir)).then(function () {
console.log('[debug] [directory]', dir);
- return afterDir(dir);
+ return requestAuth(dir);
});
});
-
};
+
}('undefined' !== typeof module ? module.exports : window));