MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -4,9 +4,19 @@ | |||||||
|   <title>Telebit Setup</title> |   <title>Telebit Setup</title> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
|  |   <script>document.body.hidden = true;</script> | ||||||
|  | 
 | ||||||
|   <div class="v-app"> |   <div class="v-app"> | ||||||
|     <h1>Telebit (Remote) Setup</h1> |     <h1>Telebit (Remote) Setup</h1> | ||||||
| 
 | 
 | ||||||
|  |     <section v-if="views.flash.error"> | ||||||
|  |       {{ views.flash.error }} | ||||||
|  |     </section> | ||||||
|  | 
 | ||||||
|  |     <section v-if="views.section.loading"> | ||||||
|  |       Loading... | ||||||
|  |     </section> | ||||||
|  | 
 | ||||||
|     <section v-if="views.section.setup"> |     <section v-if="views.section.setup"> | ||||||
|       <h2>Create Account</h2> |       <h2>Create Account</h2> | ||||||
|       <form v-on:submit.stop.prevent="initialize"> |       <form v-on:submit.stop.prevent="initialize"> | ||||||
| @ -100,6 +110,14 @@ | |||||||
|       <pre><code>{{ init }}</code></pre> |       <pre><code>{{ init }}</code></pre> | ||||||
|     </section> |     </section> | ||||||
| 
 | 
 | ||||||
|  |     <section v-if="views.section.otp"> | ||||||
|  |       <pre><code><h2>{{ init.otp }}</h2></code></pre> | ||||||
|  |     </section> | ||||||
|  | 
 | ||||||
|  |     <section v-if="views.section.status"> | ||||||
|  |       <pre><code>{{ status }}</code></pre> | ||||||
|  |     </section> | ||||||
|  | 
 | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|   <script src="/js/vue.js"></script> |   <script src="/js/vue.js"></script> | ||||||
|  | |||||||
| @ -34,20 +34,17 @@ api.config = function apiConfig() { | |||||||
| api.status = function apiStatus() { | api.status = function apiStatus() { | ||||||
|   return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) { |   return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) { | ||||||
|     var json = resp.body; |     var json = resp.body; | ||||||
|     appData.status = json; |  | ||||||
|     return json; |     return json; | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| api.initialize = function apiInitialize() { | api.initialize = function apiInitialize() { | ||||||
|   var opts = { |   var opts = { | ||||||
|     url: "/api/init" |     url: "/api/xxinitxx" | ||||||
|   , method: "POST" |   , method: "POST" | ||||||
|   , headers: { |   , headers: { | ||||||
|       'Content-Type': 'application/json' |       'Content-Type': 'application/json' | ||||||
|     } |     } | ||||||
|   , body: JSON.stringify({ |   , body: JSON.stringify(telebitState.config) | ||||||
|       foo: 'bar' |  | ||||||
|     }) |  | ||||||
|   }; |   }; | ||||||
|   return Telebit.reqLocalAsync(opts).then(function (resp) { |   return Telebit.reqLocalAsync(opts).then(function (resp) { | ||||||
|     var json = resp.body; |     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)
 | // TODO test for internet connectivity (and telebit connectivity)
 | ||||||
| var DEFAULT_RELAY = 'telebit.cloud'; | var DEFAULT_RELAY = 'telebit.cloud'; | ||||||
| var BETA_RELAY = 'telebit.ppl.family'; | var BETA_RELAY = 'telebit.ppl.family'; | ||||||
| @ -83,9 +121,15 @@ var appData = { | |||||||
| , tcp: null | , tcp: null | ||||||
| , ssh: null | , ssh: null | ||||||
| , views: { | , views: { | ||||||
|     section: { |     flash: { | ||||||
|       setup: false |       error: "" | ||||||
|  |     } | ||||||
|  |   , section: { | ||||||
|  |       loading: true | ||||||
|  |     , setup: false | ||||||
|     , advanced: false |     , advanced: false | ||||||
|  |     , otp: false | ||||||
|  |     , status: false | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| @ -98,34 +142,28 @@ var appMethods = { | |||||||
|     } |     } | ||||||
|     appData.init.relay = appData.init.relay.toLowerCase(); |     appData.init.relay = appData.init.relay.toLowerCase(); | ||||||
|     telebitState = { relay: appData.init.relay }; |     telebitState = { relay: appData.init.relay }; | ||||||
|  | 
 | ||||||
|     return Telebit.api.directory(telebitState).then(function (dir) { |     return Telebit.api.directory(telebitState).then(function (dir) { | ||||||
|       if (!dir.api_host) { |       if (!dir.api_host) { | ||||||
|         window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service"); |         window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service"); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  | 
 | ||||||
|  |       telebitState.dir = dir; | ||||||
|  | 
 | ||||||
|  |       // If it's one of the well-known relays
 | ||||||
|       if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) { |       if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) { | ||||||
|         if (!telebitState.config) { telebitState.config = {}; } |         return doConfigure(); | ||||||
|         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))); |  | ||||||
|         }); |  | ||||||
|       } else { |       } else { | ||||||
|         changeState('advanced'); |         changeState('advanced'); | ||||||
|       } |       } | ||||||
|     }).catch(function (err) { |     }).catch(function (err) { | ||||||
|       console.error(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 () { | , advance: function () { | ||||||
|     console.log('2 api.init...'); |     return doConfigure(); | ||||||
|     return api.initialize(); |  | ||||||
|   } |   } | ||||||
| , productionAcme: function () { | , productionAcme: function () { | ||||||
|     console.log("prod acme:"); |     console.log("prod acme:"); | ||||||
| @ -157,10 +195,26 @@ var appStates = { | |||||||
| , advanced: function () { | , advanced: function () { | ||||||
|     appData.views.section = { advanced: true }; |     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) { | 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); | window.addEventListener('hashchange', setState, false); | ||||||
| function setState(/*ev*/) { | function setState(/*ev*/) { | ||||||
| @ -183,11 +237,52 @@ new Vue({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| api.config(); | api.config().then(function (config) { | ||||||
| api.status().then(function () { |   telebitState.config = config; | ||||||
|   changeState('setup'); |   if (config.greenlock) { | ||||||
|   setState(); |     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; | window.api = api; | ||||||
|  | 
 | ||||||
|  | setTimeout(function () { | ||||||
|  |   document.body.hidden = false; | ||||||
|  | }, 50); | ||||||
|  | 
 | ||||||
| }()); | }()); | ||||||
|  | |||||||
| @ -11,19 +11,14 @@ if ('undefined' !== typeof Promise) { | |||||||
| 
 | 
 | ||||||
| var common = exports.TELEBIT || require('./lib/common.js'); | 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; |   state.relay = state.config.relay; | ||||||
| 
 | 
 | ||||||
|   // { _otp, config: {} }
 |   // { _otp, config: {} }
 | ||||||
|   return common.api.token(state, { |   return common.api.token(state, { | ||||||
|     error: function (err) { |     error: function (err) { console.error("[Error] common.api.token handlers.error: \n", err); return PromiseA.reject(err); } | ||||||
|       console.error("[Error] common.api.token handlers.error:"); |  | ||||||
|       console.error(err); |  | ||||||
|       return PromiseA.reject(err); |  | ||||||
|     } |  | ||||||
|   , directory: function (dir) { |   , directory: function (dir) { | ||||||
|       //console.log('[directory] Telebit Relay Discovered:');
 |       /*console.log('[directory] Telebit Relay Discovered:', dir);*/ | ||||||
|       //console.log(dir);
 |  | ||||||
|       state._apiDirectory = dir; |       state._apiDirectory = dir; | ||||||
|       return PromiseA.resolve(); |       return PromiseA.resolve(); | ||||||
|     } |     } | ||||||
| @ -32,12 +27,13 @@ common.authorize = common.getToken = function getToken(state) { | |||||||
|       state.wss = tunnelUrl; |       state.wss = tunnelUrl; | ||||||
|       return PromiseA.resolve(); |       return PromiseA.resolve(); | ||||||
|     } |     } | ||||||
|   , requested: function (authReq) { |   , requested: function (authReq, pollUrl) { | ||||||
|       console.log("[requested] Pairing Requested"); |       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) { |       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(); |       return PromiseA.resolve(); | ||||||
| @ -47,7 +43,9 @@ common.authorize = common.getToken = function getToken(state) { | |||||||
|       state.config.pretoken = pretoken; |       state.config.pretoken = pretoken; | ||||||
|       state._connecting = true; |       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..."); |         console.info("waiting..."); | ||||||
|         return PromiseA.resolve(); |         return PromiseA.resolve(); | ||||||
|       }).catch(function (err) { |       }).catch(function (err) { | ||||||
| @ -59,6 +57,7 @@ common.authorize = common.getToken = function getToken(state) { | |||||||
|     } |     } | ||||||
|   , offer: function (token) { |   , offer: function (token) { | ||||||
|       //console.log("[offer] Pairing Enabled by Relay");
 |       //console.log("[offer] Pairing Enabled by Relay");
 | ||||||
|  |       state.token = token; | ||||||
|       state.config.token = token; |       state.config.token = token; | ||||||
|       if (state._error) { |       if (state._error) { | ||||||
|         return; |         return; | ||||||
| @ -77,7 +76,7 @@ common.authorize = common.getToken = function getToken(state) { | |||||||
|       } catch(e) { |       } catch(e) { | ||||||
|         console.warn("[warning] could not decode token"); |         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");
 |         //console.log("Pairing Enabled Locally");
 | ||||||
|         return PromiseA.resolve(); |         return PromiseA.resolve(); | ||||||
|       }).catch(function (err) { |       }).catch(function (err) { | ||||||
| @ -87,12 +86,9 @@ common.authorize = common.getToken = function getToken(state) { | |||||||
|         return PromiseA.reject(err); |         return PromiseA.reject(err); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , granted: function (/*_*/) { |   , granted: function (/*_*/) { /*console.log("[grant] Pairing complete!");*/ return PromiseA.resolve(); } | ||||||
|       //console.log("[grant] Pairing complete!");
 |  | ||||||
|       return PromiseA.resolve(); |  | ||||||
|     } |  | ||||||
|   , end: function () { |   , 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"); |         console.info("Success"); | ||||||
| 
 | 
 | ||||||
|         // workaround for https://github.com/nodejs/node/issues/21319
 |         // workaround for https://github.com/nodejs/node/issues/21319
 | ||||||
| @ -112,9 +108,6 @@ common.authorize = common.getToken = function getToken(state) { | |||||||
|         // end workaround
 |         // end workaround
 | ||||||
| 
 | 
 | ||||||
|         //parseCli(state);
 |         //parseCli(state);
 | ||||||
|       }).catch(function (err) { |  | ||||||
|         console.error('[end] [error]', err); |  | ||||||
|         return PromiseA.reject(err); |  | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  | |||||||
| @ -48,11 +48,15 @@ if ('undefined' !== typeof fetch) { | |||||||
|     if (!opts) { opts = {}; } |     if (!opts) { opts = {}; } | ||||||
|     if (opts.json && true !== opts.json) { |     if (opts.json && true !== opts.json) { | ||||||
|       opts.body = opts.json; |       opts.body = opts.json; | ||||||
|  |       opts.json = true; | ||||||
|     } |     } | ||||||
|     if (opts.json) { |     if (opts.json) { | ||||||
|       if (!opts.headers) { opts.headers = {}; } |       if (!opts.headers) { opts.headers = {}; } | ||||||
|       if (opts.body) { |       if (opts.body) { | ||||||
|         opts.headers['Content-Type'] = 'application/json'; |         opts.headers['Content-Type'] = 'application/json'; | ||||||
|  |         if ('string' !== typeof opts.body) { | ||||||
|  |           opts.body = JSON.stringify(opts.body); | ||||||
|  |         } | ||||||
|       } else { |       } else { | ||||||
|         opts.headers.Accepts = 'application/json'; |         opts.headers.Accepts = 'application/json'; | ||||||
|       } |       } | ||||||
| @ -126,15 +130,16 @@ common.signToken = function (state) { | |||||||
|   return jwt.sign(tokenData, state.config.secret); |   return jwt.sign(tokenData, state.config.secret); | ||||||
| }; | }; | ||||||
| common.promiseTimeout = function (ms) { | common.promiseTimeout = function (ms) { | ||||||
|   var x = new PromiseA(function (resolve) { |   var tok; | ||||||
|     x._tok = setTimeout(function () { |   var p = new PromiseA(function (resolve) { | ||||||
|  |     tok = setTimeout(function () { | ||||||
|       resolve(); |       resolve(); | ||||||
|     }, ms); |     }, ms); | ||||||
|   }); |   }); | ||||||
|   x.cancel = function () { |   p.cancel = function () { | ||||||
|     clearTimeout(x._tok); |     clearTimeout(tok); | ||||||
|   }; |   }; | ||||||
|   return x; |   return p; | ||||||
| }; | }; | ||||||
| common.api = {}; | common.api = {}; | ||||||
| common.api.directory = function (state) { | common.api.directory = function (state) { | ||||||
| @ -145,15 +150,10 @@ common.api.directory = function (state) { | |||||||
|   if (state._relays[state._relayUrl]) { |   if (state._relays[state._relayUrl]) { | ||||||
|     return PromiseA.resolve(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) { |   return common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) { | ||||||
|     console.error('123aaaaaaaaabsnthsnth'); |  | ||||||
|     var dir = resp.body; |     var dir = resp.body; | ||||||
|     state._relays[state._relayUrl] = dir; |     state._relays[state._relayUrl] = dir; | ||||||
|     return dir; |     return dir; | ||||||
|   }).catch(function (err) { |  | ||||||
|     console.error('bsnthsnth'); |  | ||||||
|     return PromiseA.reject(err); |  | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| common.api._parseWss = function (state, dir) { | common.api._parseWss = function (state, dir) { | ||||||
| @ -169,15 +169,63 @@ common.api.wss = function (state) { | |||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| common.api.token = function (state, handlers) { | 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
 |   // directory, requested, connect, tunnelUrl, offer, granted, end
 | ||||||
|   function afterDir(dir) { |   function requestAuth(dir) { | ||||||
|     if (common.debug) { console.log('[debug] after dir'); } |     if (common.debug) { console.log('[debug] after dir'); } | ||||||
|     state.wss = common.api._parseWss(state, dir); |     state.wss = common.api._parseWss(state, dir); | ||||||
| 
 | 
 | ||||||
|     return PromiseA.resolve(handlers.tunnelUrl(state.wss)).then(function () { |     return PromiseA.resolve(handlers.tunnelUrl(state.wss)).then(function () { | ||||||
|       if (common.debug) { console.log('[debug] after tunnelUrl'); } |       if (common.debug) { console.log('[debug] after tunnelUrl'); } | ||||||
|       if (state.config.secret /* && !state.config.token */) { |       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; |       state.token = state.token || state.config.token || state.config._token; | ||||||
|       if (state.token) { |       if (state.token) { | ||||||
| @ -190,13 +238,13 @@ common.api.token = function (state, handlers) { | |||||||
| 
 | 
 | ||||||
|       if (!dir.pair_request) { |       if (!dir.pair_request) { | ||||||
|         if (common.debug) { console.log('[debug] no dir, connect'); } |         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
 |       // TODO sign token with own private key, including public key and thumbprint
 | ||||||
|       //      (much like ACME JOSE account)
 |       //      (much like ACME JOSE account)
 | ||||||
|       // TODO handle agree
 |       // TODO handle agree
 | ||||||
|       var otp = state.config._otp; // common.otp();
 |       var otp = state._otp; // common.otp();
 | ||||||
|       var authReq = { |       var authReq = { | ||||||
|         subject: state.config.email |         subject: state.config.email | ||||||
|       , subject_scheme: 'mailto' |       , subject_scheme: 'mailto' | ||||||
| @ -236,88 +284,39 @@ common.api.token = function (state, handlers) { | |||||||
|       , method: dir.pair_request.method |       , method: dir.pair_request.method | ||||||
|       , json: authReq |       , json: authReq | ||||||
|       }; |       }; | ||||||
|       var firstReq = true; |  | ||||||
|       var firstReady = true; |  | ||||||
| 
 | 
 | ||||||
|       function gotoNext(req) { |       return common.requestAsync(req).then(function doFirst(resp) { | ||||||
|         if (common.debug) { console.log('[debug] gotoNext called'); } |         var body = resp.body; | ||||||
|         if (common.debug) { console.log(req); } |         if (common.debug) { console.log('[debug] first req'); } | ||||||
|         return common.requestAsync(req).then(function (resp) { |         if (!body.access_token && !body.jwt) { | ||||||
|           var body = resp.body; |           return PromiseA.reject(new Error("something wrong with pre-authorization request")); | ||||||
| 
 |         } | ||||||
|           function checkLocation() { |         return PromiseA.resolve(handlers.requested(authReq, resp.headers.location)).then(function () { | ||||||
|             if (common.debug) { console.log('[debug] checkLocation'); } |           return PromiseA.resolve(handlers.connect(body.access_token || body.jwt)).then(function () { | ||||||
|             if (common.debug) { console.log(body); } |             var err; | ||||||
|             // pending, try again
 |             if (!resp.headers.location) { | ||||||
|             if ('pending' === body.status && resp.headers.location) { |               err = new Error("bad authentication request response"); | ||||||
|               if (common.debug) { console.log('[debug] pending'); } |               err._resp = resp.toJSON && resp.toJSON(); | ||||||
|               return common.promiseTimeout(2 * 1000).then(function () { |               return PromiseA.resolve(handlers.error(err)).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.promiseTimeout(2 * 1000).then(function () { | ||||||
| 
 |               return pollStatus({ url: resp.headers.location, json: true }); | ||||||
|           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 }); |  | ||||||
|                 }); |  | ||||||
|               }); |  | ||||||
|             }); |             }); | ||||||
|           } 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 () {}); |  | ||||||
|         }); |         }); | ||||||
|       } |       }).catch(function (err) { | ||||||
| 
 |         if (common.debug) { console.log('[debug] gotoFirst error'); } | ||||||
|       return gotoNext(req); |         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)
 |   // backwards compat (TODO verify we can remove this)
 | ||||||
|   var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }'; |   var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }'; | ||||||
|   return common.api.directory(state).then(function (dir) { |   return common.api.directory(state).then(function (dir) { | ||||||
| @ -331,9 +330,9 @@ common.api.token = function (state, handlers) { | |||||||
|   }).then(function (dir) { |   }).then(function (dir) { | ||||||
|     return PromiseA.resolve(handlers.directory(dir)).then(function () { |     return PromiseA.resolve(handlers.directory(dir)).then(function () { | ||||||
|       console.log('[debug] [directory]', dir); |       console.log('[debug] [directory]', dir); | ||||||
|       return afterDir(dir); |       return requestAuth(dir); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 |  | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
| }('undefined' !== typeof module ? module.exports : window)); | }('undefined' !== typeof module ? module.exports : window)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user