MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -7,7 +7,7 @@ | |||||||
|   <div class="v-app"> |   <div class="v-app"> | ||||||
|     <h1>Telebit (Remote) Setup</h1> |     <h1>Telebit (Remote) Setup</h1> | ||||||
| 
 | 
 | ||||||
|     <section v-if="views.section.create"> |     <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"> | ||||||
| 
 | 
 | ||||||
| @ -43,12 +43,23 @@ | |||||||
|         </small> |         </small> | ||||||
| 
 | 
 | ||||||
|         <details><summary><small>Advanced</small></summary> |         <details><summary><small>Advanced</small></summary> | ||||||
|         <label for="-relay">Relay:</label><input id="-relay" v-model="init.relay" type="text" placeholder="telebit.cloud"> | 
 | ||||||
|  |         <label for="-relay">Relay:</label> | ||||||
|  |           <input id="-relay" v-model="init.relay" type="text" placeholder="telebit.cloud"> | ||||||
|         <br> |         <br> | ||||||
|         <button type="button" v-on:click="defaultRelay">Use Default</button> |         <button type="button" v-on:click="defaultRelay">Use Default</button> | ||||||
|         <button type="button" v-on:click="betaRelay">Use Beta</button> |         <button type="button" v-on:click="betaRelay">Use Beta</button> | ||||||
|         <br> |         <br> | ||||||
|         <br> |         <br> | ||||||
|  | 
 | ||||||
|  |         <label for="-acme-server">ACME (Let's Encrypt) Server:</label> | ||||||
|  |           <input id="-acme-server" v-model="init.acmeServer" type="text" placeholder="https://acme-v02.api.letsencrypt.org/directory"> | ||||||
|  |         <br> | ||||||
|  |         <button type="button" v-on:click="productionAcme">Use Production</button> | ||||||
|  |         <button type="button" v-on:click="stagingAcme">Use Staging</button> | ||||||
|  |         <br> | ||||||
|  |         <br> | ||||||
|  | 
 | ||||||
|         </details> |         </details> | ||||||
| 
 | 
 | ||||||
|         <button type="submit">Accept & Continue</button> |         <button type="submit">Accept & Continue</button> | ||||||
| @ -58,22 +69,32 @@ | |||||||
|     </section> |     </section> | ||||||
| 
 | 
 | ||||||
|     <section v-if="views.section.advanced"> |     <section v-if="views.section.advanced"> | ||||||
|       <h2>Advanced Setup</h2> |       <h2>Advanced Setup for {{ init.relay }}</h2> | ||||||
|       <form v-on:submit.stop.prevent="initialize"> |       <form v-on:submit.stop.prevent="advance"> | ||||||
| 
 | 
 | ||||||
|         <label for="-secret">Relay Secret:</label> |         <strong><label for="-secret">Relay Shared Secret:</label></strong> | ||||||
|         <input id="-secret" v-model="init.secret" type="text" placeholder="ex: xxxxxxxxxxxx"> |         <input id="-secret" v-model="init.secret" type="text" placeholder="ex: xxxxxxxxxxxx"> | ||||||
|         <br> |         <br> | ||||||
| 
 | 
 | ||||||
|  |         <strong><label for="-domains">Domains:</label></strong> | ||||||
|  |         <br> | ||||||
|  |         <small>(comma separated list of domains to use for http, tls, https, etc)</small> | ||||||
|  |         <br> | ||||||
|  |         <input id="-domains" v-model="init.domains" type="text" placeholder="ex: whatever.com, example.com"> | ||||||
|  |         <br> | ||||||
|  | 
 | ||||||
|  |         <strong><label for="-ports">TCP Ports:</label></strong> | ||||||
|  |         <br> | ||||||
|  |         <small>(comman separated list of ports, excluding 80 and 443, typically port over 1024)</small> | ||||||
|  |         <br> | ||||||
|  |         <input id="-ports" v-model="init.ports" type="text" placeholder="ex: 5050, 3000, 8080"> | ||||||
|  |         <br> | ||||||
|  | 
 | ||||||
|         <label for="-telemetry"><input id="-telemetry" v-model="init.telemetry" type="checkbox"> |         <label for="-telemetry"><input id="-telemetry" v-model="init.telemetry" type="checkbox"> | ||||||
|           Contribute to Telebit by sharing telemetry</label> |           Contribute to Telebit by sharing telemetry</label> | ||||||
|         <br> |         <br> | ||||||
| 
 | 
 | ||||||
|         <label for="-relay">[Advanced] Relay:</label> |         <button type="submit">Finish</button> | ||||||
|         <input id="-relay" v-model="init.relay" type="text" placeholder="telebit.cloud"> |  | ||||||
|         <br> |  | ||||||
| 
 |  | ||||||
|         <button type="submit">Accept & Continue</button> |  | ||||||
| 
 | 
 | ||||||
|       </form> |       </form> | ||||||
|       <pre><code>{{ init }}</code></pre> |       <pre><code>{{ init }}</code></pre> | ||||||
|  | |||||||
| @ -1,14 +1,29 @@ | |||||||
| ;(function () { | ;(function () { | ||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| console.log("hello"); |  | ||||||
| 
 |  | ||||||
| var Vue = window.Vue; | var Vue = window.Vue; | ||||||
| var Telebit = window.TELEBIT; | var Telebit = window.TELEBIT; | ||||||
| var api = {}; | var api = {}; | ||||||
| 
 | 
 | ||||||
|  | /*globals AbortController*/ | ||||||
|  | function safeFetch(url, opts) { | ||||||
|  |   var controller = new AbortController(); | ||||||
|  |   var tok = setTimeout(function () { | ||||||
|  |     controller.abort(); | ||||||
|  |   }, 4000); | ||||||
|  |   if (!opts) { | ||||||
|  |     opts = {}; | ||||||
|  |   } | ||||||
|  |   opts.signal = controller.signal; | ||||||
|  |   return window.fetch(url, opts).finally(function () { | ||||||
|  |     clearTimeout(tok); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| api.config = function apiConfig() { | api.config = function apiConfig() { | ||||||
|   return window.fetch("/api/config", { method: "GET" }).then(function (resp) { |   return safeFetch("/api/config", { | ||||||
|  |     method: "GET" | ||||||
|  |   }).then(function (resp) { | ||||||
|     return resp.json().then(function (json) { |     return resp.json().then(function (json) { | ||||||
|       appData.config = json; |       appData.config = json; | ||||||
|       return json; |       return json; | ||||||
| @ -16,17 +31,43 @@ api.config = function apiConfig() { | |||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| api.status = function apiStatus() { | api.status = function apiStatus() { | ||||||
|   return window.fetch("/api/status", { method: "GET" }).then(function (resp) { |   return safeFetch("/api/status", { method: "GET" }).then(function (resp) { | ||||||
|     return resp.json().then(function (json) { |     return resp.json().then(function (json) { | ||||||
|       appData.status = json; |       appData.status = json; | ||||||
|       return json; |       return json; | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  | api.initialize = function apiInitialize() { | ||||||
|  |   var opts = { | ||||||
|  |     method: "POST" | ||||||
|  |   , headers: { | ||||||
|  |       'Content-Type': 'application/json' | ||||||
|  |     } | ||||||
|  |   , body: JSON.stringify({ | ||||||
|  |       foo: 'bar' | ||||||
|  |     }) | ||||||
|  |   }; | ||||||
|  |   return safeFetch("/api/init", opts).then(function (resp) { | ||||||
|  |     return resp.json().then(function (json) { | ||||||
|  |       appData.initResult = json; | ||||||
|  |       window.alert("Error: [success] " + JSON.stringify(json, null, 2)); | ||||||
|  |       return json; | ||||||
|  |     }).catch(function (err) { | ||||||
|  |       window.alert("Error: [init] " + (err.message || JSON.stringify(err, null, 2))); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| // 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'; | ||||||
|  | var TELEBIT_RELAYS = [ | ||||||
|  |   DEFAULT_RELAY | ||||||
|  | , BETA_RELAY | ||||||
|  | ]; | ||||||
|  | var PRODUCTION_ACME = 'https://acme-v02.api.letsencrypt.org/directory'; | ||||||
|  | var STAGING_ACME = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||||
| var appData = { | var appData = { | ||||||
|   config: null |   config: null | ||||||
| , status: null | , status: null | ||||||
| @ -35,40 +76,93 @@ var appData = { | |||||||
|   , letos: true |   , letos: true | ||||||
|   , notifications: "important" |   , notifications: "important" | ||||||
|   , relay: DEFAULT_RELAY |   , relay: DEFAULT_RELAY | ||||||
|  |   , telemetry: true | ||||||
|  |   , acmeServer: PRODUCTION_ACME | ||||||
|   } |   } | ||||||
| , http: null | , http: null | ||||||
| , tcp: null | , tcp: null | ||||||
| , ssh: null | , ssh: null | ||||||
| , views: { | , views: { | ||||||
|     section: { |     section: { | ||||||
|       create: true |       setup: false | ||||||
|  |     , advanced: false | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  | var telebitState = {}; | ||||||
| var appMethods = { | var appMethods = { | ||||||
|   initialize: function () { |   initialize: function () { | ||||||
|     console.log("call initialize"); |     console.log("call initialize"); | ||||||
|     if (!appData.init.relay) { |     if (!appData.init.relay) { | ||||||
|       appData.init.relay = DEFAULT_RELAY; |       appData.init.relay = DEFAULT_RELAY; | ||||||
|     } |     } | ||||||
|     if (DEFAULT_RELAY !== appData.init.relay) { |     appData.init.relay = appData.init.relay.toLowerCase(); | ||||||
|       window.alert("TODO: Custom Relay Not Implemented Yet"); |     telebitState = { relay: appData.init.relay }; | ||||||
|     } |     return Telebit.api.directory(telebitState).then(function (dir) { | ||||||
|     Telebit.api.directory({ relay: appData.init.relay }, function (err, dir) { |       if (!dir.api_host) { | ||||||
|       if (err) { |         window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service"); | ||||||
|         window.alert("Error:" + (err.message || JSON.stringify(err, null, 2))); |  | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       window.alert("Success:" + JSON.stringify(dir, null, 2)); |       if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) { | ||||||
|  |         return api.initialize(); | ||||||
|  |       } else { | ||||||
|  |         changeState('advanced'); | ||||||
|  |       } | ||||||
|  |     }).catch(function (err) { | ||||||
|  |       window.alert("Error: [directory] " + (err.message || JSON.stringify(err, null, 2))); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | , advance: function () { | ||||||
|  |     return api.initialize(); | ||||||
|  |   } | ||||||
|  | , productionAcme: function () { | ||||||
|  |     console.log("prod acme:"); | ||||||
|  |     appData.init.acmeServer = PRODUCTION_ACME; | ||||||
|  |     console.log(appData.init.acmeServer); | ||||||
|  |   } | ||||||
|  | , stagingAcme: function () { | ||||||
|  |     console.log("staging acme:"); | ||||||
|  |     appData.init.acmeServer = STAGING_ACME; | ||||||
|  |     console.log(appData.init.acmeServer); | ||||||
|  |   } | ||||||
| , defaultRelay: function () { | , defaultRelay: function () { | ||||||
|     appData.init.relay = DEFAULT_RELAY; |     appData.init.relay = DEFAULT_RELAY; | ||||||
|   } |   } | ||||||
| , betaRelay: function () { | , betaRelay: function () { | ||||||
|     appData.init.relay = BETA_RELAY; |     appData.init.relay = BETA_RELAY; | ||||||
|   } |   } | ||||||
|  | , defaultRhubarb: function () { | ||||||
|  |     appData.init.rhubarb = DEFAULT_RELAY; | ||||||
|  |   } | ||||||
|  | , betaRhubarb: function () { | ||||||
|  |     appData.init.rhubarb = BETA_RELAY; | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  | var appStates = { | ||||||
|  |   setup: function () { | ||||||
|  |     appData.views.section = { setup: true }; | ||||||
|  |   } | ||||||
|  | , advanced: function () { | ||||||
|  |     appData.views.section = { advanced: true }; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function changeState(newstate) { | ||||||
|  |   location.hash = '#/' + newstate + '/'; | ||||||
|  | } | ||||||
|  | window.addEventListener('hashchange', setState, false); | ||||||
|  | function setState(/*ev*/) { | ||||||
|  |   //ev.oldURL
 | ||||||
|  |   //ev.newURL
 | ||||||
|  |   var parts = location.hash.substr(1).replace(/^\//, '').replace(/\/$/, '').split('/'); | ||||||
|  |   var fn = appStates; | ||||||
|  |   parts.forEach(function (s) { | ||||||
|  |     console.log("state:", s); | ||||||
|  |     fn = fn[s]; | ||||||
|  |   }); | ||||||
|  |   fn(); | ||||||
|  |   //appMethods.states[newstate]();
 | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| new Vue({ | new Vue({ | ||||||
|   el: ".v-app" |   el: ".v-app" | ||||||
| @ -76,8 +170,12 @@ new Vue({ | |||||||
| , methods: appMethods | , methods: appMethods | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| api.config(); | api.config(); | ||||||
| api.status(); | api.status().then(function () { | ||||||
|  |   changeState('setup'); | ||||||
|  |   setState(); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| window.api = api; | window.api = api; | ||||||
| }()); | }()); | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ if ('undefined' !== typeof Promise) { | |||||||
|   throw new Error("no Promise implementation defined"); |   throw new Error("no Promise implementation defined"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /*globals AbortController*/ | ||||||
| if ('undefined' !== typeof fetch) { | if ('undefined' !== typeof fetch) { | ||||||
|   common.requestAsync = function (opts) { |   common.requestAsync = function (opts) { | ||||||
|     /* |     /* | ||||||
| @ -37,7 +38,16 @@ if ('undefined' !== typeof fetch) { | |||||||
|       } |       } | ||||||
|     , body: JSON.stringify(opts) |     , body: JSON.stringify(opts) | ||||||
|     }; |     }; | ||||||
|  |     var controller = new AbortController(); | ||||||
|  |     var tok = setTimeout(function () { | ||||||
|  |       controller.abort(); | ||||||
|  |     }, 4000); | ||||||
|  |     if (!relayOpts) { | ||||||
|  |       relayOpts = {}; | ||||||
|  |     } | ||||||
|  |     relayOpts.signal = controller.signal; | ||||||
|     return window.fetch(relayOpts.url, relayOpts).then(function (resp) { |     return window.fetch(relayOpts.url, relayOpts).then(function (resp) { | ||||||
|  |       clearTimeout(tok); | ||||||
|       return resp.json().then(function (json) { |       return resp.json().then(function (json) { | ||||||
|         /* |         /* | ||||||
|         var headers = {}; |         var headers = {}; | ||||||
| @ -100,17 +110,20 @@ common.signToken = function (state) { | |||||||
|   return jwt.sign(tokenData, state.config.secret); |   return jwt.sign(tokenData, state.config.secret); | ||||||
| }; | }; | ||||||
| common.api = {}; | common.api = {}; | ||||||
| common.api.directory = function (state, next) { | common.api.directory = function (state) { | ||||||
|   console.log('state:'); |   console.log('[DEBUG] state:'); | ||||||
|   console.log(state); |   console.log(state); | ||||||
|   state._relayUrl = common.parseUrl(state.relay); |   state._relayUrl = common.parseUrl(state.relay); | ||||||
|   common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) { |   if (!state._relays) { state._relays = {}; } | ||||||
|  |   if (state._relays[state._relayUrl]) { | ||||||
|  |     return PromiseA.resolve(state._relays[state._relayUrl]); | ||||||
|  |   } | ||||||
|  |   return common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) { | ||||||
|     var dir = resp.body; |     var dir = resp.body; | ||||||
|     if (!dir) { dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; } |     state._relays[state._relayUrl] = dir; | ||||||
|     state._apiDirectory = dir; |     return dir; | ||||||
|     next(null, dir); |  | ||||||
|   }).catch(function (err) { |   }).catch(function (err) { | ||||||
|     next(err); |     return PromiseA.reject(err); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| common.api._parseWss = function (state, dir) { | common.api._parseWss = function (state, dir) { | ||||||
| @ -121,14 +134,13 @@ common.api._parseWss = function (state, dir) { | |||||||
|   return 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; | ||||||
| }; | }; | ||||||
| common.api.wss = function (state, cb) { | common.api.wss = function (state, cb) { | ||||||
|   common.api.directory(state, function (err, dir) { |   common.api.directory(state).then(function (dir) { | ||||||
|     cb(err, common.api._parseWss(state, dir)); |     cb(null, common.api._parseWss(state, dir)); | ||||||
|   }); |   }).catch(cb); | ||||||
| }; | }; | ||||||
| common.api.token = function (state, handlers) { | common.api.token = function (state, handlers) { | ||||||
|   common.api.directory(state, function (err, dir) { |  | ||||||
|   // directory, requested, connect, tunnelUrl, offer, granted, end
 |   // directory, requested, connect, tunnelUrl, offer, granted, end
 | ||||||
|     function afterDir() { |   function afterDir(err, 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); | ||||||
| 
 | 
 | ||||||
| @ -261,13 +273,20 @@ common.api.token = function (state, handlers) { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|     if (dir && dir.api_host) { |   // backwards compat (TODO verify we can remove this)
 | ||||||
|       handlers.directory(dir, afterDir); |   var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }'; | ||||||
|     } else { |   common.api.directory(state).then(function (dir) { | ||||||
|       // backwards compat
 |     if (!dir.api_host) { | ||||||
|       dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; |       dir = JSON.parse(failoverDir); | ||||||
|       afterDir(); |       return afterDir(null, dir); | ||||||
|     } |     } | ||||||
|  |     handlers.directory(dir).then(function (dir) { | ||||||
|  |       return afterDir(null, dir); | ||||||
|  |     }).catch(function (err) { | ||||||
|  |       return PromiseA.reject(err); | ||||||
|  |     }); | ||||||
|  |   }).catch(function (err) { | ||||||
|  |     return afterDir(err, JSON.parse(failoverDir)); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user