From 95656895c083a818dd6ac587b0f115814dbe37fc Mon Sep 17 00:00:00 2001 From: Curt Tudor Date: Fri, 4 Aug 2023 15:17:17 -0600 Subject: [PATCH] feat: BrowZer error page (#180) --- src/constants.js | 7 +++ src/runtime.js | 74 +++++++++++++++++------- src/urlon.js | 146 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 21 deletions(-) create mode 100644 src/urlon.js diff --git a/src/constants.js b/src/constants.js index 3dbc2d0..44057fe 100644 --- a/src/constants.js +++ b/src/constants.js @@ -29,6 +29,13 @@ const ZBR_CONSTANTS = AZURE_AD_URL_REGEX: /login\.microsoftonline\.com/, AZURE_AD_SCOPES: ['User.Read', 'openid', 'email'], + ZBR_ERROR_CODE_INVALID_AUTH: 1001, + ZBR_ERROR_CODE_CONTROLLER_REQ_FAIL: 1002, + ZBR_ERROR_CODE_SERVICE_NOT_IN_LIST: 1003, + ZBR_ERROR_CODE_SERVICE_UNREACHABLE: 1004, + ZBR_ERROR_CODE_SERVICE_HAS_NO_CONFIG: 1005, + ZBR_ERROR_CODE_UNSUPPORTED_BROWSER: 1006, + }; diff --git a/src/runtime.js b/src/runtime.js index 1af73b1..0b5a753 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -42,6 +42,7 @@ import { Auth0Client } from '@auth0/auth0-spa-js'; import Bowser from 'bowser'; import uPlot from 'uplot'; import * as msal from '@azure/msal-browser'; +import { stringify } from './urlon'; /** @@ -186,8 +187,12 @@ class ZitiBrowzerRuntime { let errStr = `The browser you are using:\n\n${this.ua.browser.name} v${this.ua.browser.version}\n\nis currently unsupported by\nOpenZiti BrowZer Runtime v${_options.version}.`; - alert(errStr); - throw new Error(errStr); + this.browzer_error({ + status: 409, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_UNSUPPORTED_BROWSER, + title: `The browser you are using is: ${this.ua.browser.name} v${this.ua.browser.version}.`, + message: `This browser is currently unsupported by BrowZer Runtime v${_options.version}.` + }); } @@ -841,13 +846,26 @@ class ZitiBrowzerRuntime { } } + browzer_error( browzer_error_data_json ) { + + setTimeout(function() { + window.location.href = ` + ${zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.scheme}://${zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host}:${zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.port}/browzer_error?browzer_error_data=${stringify(JSON.stringify(browzer_error_data_json))} + `; + }, 10); + + } + noConfigForServiceEventHandler(noConfigForServiceEvent) { this.logger.trace(`noConfigForServiceEventHandler() `, noConfigForServiceEvent); - let errStr = `Ziti Service [${noConfigForServiceEvent.serviceName}] has no associated configs.\n\nContact your Ziti Network admin.`; - - alert(errStr); + window.zitiBrowzerRuntime.browzer_error({ + status: 409, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_SERVICE_HAS_NO_CONFIG, + title: `Ziti Service [${noConfigForServiceEvent.serviceName}] has no associated configs.`, + message: `Possible network configuration issue exists.` + }); } @@ -898,12 +916,14 @@ class ZitiBrowzerRuntime { return showOccurrences ? found : found[0]; }; - let cm = closestMatch(noServiceEvent.serviceName, noServiceEvent.serviceList, true); - let errStr = `Ziti Service [${noServiceEvent.serviceName}] not found in list of Services your Identity can access -- closest match [${cm}].\n\nContact your Ziti Network admin.`; - - alert(errStr); + window.zitiBrowzerRuntime.browzer_error({ + status: 409, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_SERVICE_NOT_IN_LIST, + title: `Ziti Service [${noServiceEvent.serviceName}] not found in list of Services your Identity can access.`, + message: `Closest match [${cm}] -- Possible network configuration issue exists.` + }); } @@ -911,9 +931,12 @@ class ZitiBrowzerRuntime { this.logger.trace(`sessionCreationErrorEventHandler() `, sessionCreationErrorEvent); - let errStr = `Ziti Service [${window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service}] cannot be reached -- [${sessionCreationErrorEvent.error}].\n\nContact your Ziti Network admin.`; - - alert(errStr); + window.zitiBrowzerRuntime.browzer_error({ + status: 409, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_SERVICE_UNREACHABLE, + title: `Ziti Service '${window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service}' cannot be reached -- [${sessionCreationErrorEvent.error}]`, + message: `The request conflicts with the current state of the network.` + }); } @@ -921,9 +944,12 @@ class ZitiBrowzerRuntime { this.logger.trace(`invalidAuthEventHandler() `, invalidAuthEvent); - let errStr = `User [${invalidAuthEvent.email}] cannot be authenticated onto Ziti Network.\n\nContact your Ziti Network admin.`; - - alert(errStr); + window.zitiBrowzerRuntime.browzer_error({ + status: 511, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_INVALID_AUTH, + title: `User '${invalidAuthEvent.email}' cannot be authenticated onto Ziti Network`, + message: `The client needs to authenticate to gain network access.` + }); } @@ -931,9 +957,12 @@ class ZitiBrowzerRuntime { this.logger.trace(`channelConnectFailEventHandler() `, channelConnectFailEvent); - let errStr = `Service [${channelConnectFailEvent.serviceName}] connect attempt failed on Ziti Network. Is the web server up?\n\nContact your Ziti Network admin.`; - - alert(errStr); + window.zitiBrowzerRuntime.browzer_error({ + status: 409, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_SERVICE_UNREACHABLE, + title: `Ziti Service '${channelConnectFailEvent.serviceName}' connect attempt failed on Ziti Network.`, + message: `The web server might be down.` + }); } @@ -941,9 +970,12 @@ class ZitiBrowzerRuntime { this.logger.trace(`requestFailedWithNoResponseEventHandler() `, requestFailedWithNoResponseEvent); - let errStr = `HTTP Request to [${requestFailedWithNoResponseEvent.url}] failed - possible server-side certificate issue exists.\n\nContact your Ziti Network admin.`; - - alert(errStr); + window.zitiBrowzerRuntime.browzer_error({ + status: 503, + code: ZBR_CONSTANTS.ZBR_ERROR_CODE_CONTROLLER_REQ_FAIL, + title: `HTTP Request to [${requestFailedWithNoResponseEvent.url}] failed.`, + message: `Possible server-side certificate issue exists.` + }); } diff --git a/src/urlon.js b/src/urlon.js new file mode 100644 index 0000000..10f267a --- /dev/null +++ b/src/urlon.js @@ -0,0 +1,146 @@ +'use strict' + +var keyStringifyRegexp = /([=:@$/])/g +var valueStringifyRegexp = /([&;/])/g +var keyParseRegexp = /[=:@$]/ +var valueParseRegexp = /[&;]/ + +function encodeString(str, regexp) { + return encodeURI(str.replace(regexp, '/$1')) +} + +function trim(res) { + return typeof res === 'string' ? res.replace(/;+$/g, '') : res +} + +function stringify(input, recursive) { + if (!recursive) { + return trim(stringify(input, true)) + } + // Number, Boolean or Null + if ( + typeof input === 'number' || + input === true || + input === false || + input === null + ) { + return ':' + input + } + var res = [] + // Array + if (input instanceof Array) { + for (var i = 0; i < input.length; ++i) { + typeof input[i] === 'undefined' + ? res.push(':null') + : res.push(stringify(input[i], true)) + } + return '@' + res.join('&') + ';' + } + // Object + if (typeof input === 'object') { + for (var key in input) { + var val = stringify(input[key], true) + if (val) { + res.push(encodeString(key, keyStringifyRegexp) + val) + } + } + return '$' + res.join('&') + ';' + } + // undefined + if (typeof input === 'undefined') { + return + } + // String + return '=' + encodeString(input.toString(), valueStringifyRegexp) +} + +function parse(str) { + var pos = 0 + str = decodeURI(str) + + function readToken(regexp) { + var token = '' + for (; pos !== str.length; ++pos) { + if (str.charAt(pos) === '/') { + pos += 1 + if (pos === str.length) { + token += ';' + break + } + } else if (str.charAt(pos).match(regexp)) { + break + } + token += str.charAt(pos) + } + return token + } + + function parseToken() { + var type = str.charAt(pos++) + // String + if (type === '=') { + return readToken(valueParseRegexp) + } + // Number, Boolean or Null + if (type === ':') { + var value = readToken(valueParseRegexp) + if (value === 'true') { + return true + } + if (value === 'false') { + return false + } + value = parseFloat(value) + return isNaN(value) ? null : value + } + var res + // Array + if (type === '@') { + res = [] + loop: { + // empty array + if (pos >= str.length || str.charAt(pos) === ';') { + break loop + } + // parse array items + while (1) { + res.push(parseToken()) + if (pos >= str.length || str.charAt(pos) === ';') { + break loop + } + pos += 1 + } + } + pos += 1 + return res + } + // Object + if (type === '$') { + res = {} + loop: { + if (pos >= str.length || str.charAt(pos) === ';') { + break loop + } + while (1) { + var name = readToken(keyParseRegexp) + res[name] = parseToken() + if (pos >= str.length || str.charAt(pos) === ';') { + break loop + } + pos += 1 + } + } + pos += 1 + return res + } + // Error + throw new Error('Unexpected char ' + type) + } + + return parseToken() +} + +export { + stringify, + parse, +};