From d498d484973fc0547b06df9469f56cf21906f3e0 Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Fri, 29 May 2026 21:10:22 -0700 Subject: [PATCH] fix: replace misleading webui CORS error toast with generic network error message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browsers throw an opaque TypeError for all network-level failures — CORS policy violations, TLS/certificate rejections (e.g. self-signed certs), DNS failures, and unreachable hosts — with no way to distinguish between them. Asserting "CORS blocked" on every TypeError caused users to chase a CORS misconfiguration when the real problem was an untrusted certificate or unreachable gateway. The error message now lists some plausible causes so users have possible diagnostics regardless of the actual failure mode. Fixes #2143 --- webui/web/index.html | 2 +- webui/web/js/api.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/webui/web/index.html b/webui/web/index.html index ecb2fbd84..0b05825fe 100644 --- a/webui/web/index.html +++ b/webui/web/index.html @@ -633,7 +633,7 @@ api.logout(); console.error('Login error:', error); - if (error.message.includes('CORS blocked')) { + if (error.message.startsWith('Network error:')) { showError(error.message); } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) { showError('Unable to connect to the gateway. Please check the endpoint URL and ensure the server is running.'); diff --git a/webui/web/js/api.js b/webui/web/js/api.js index 005a7b17a..afea37ad0 100644 --- a/webui/web/js/api.js +++ b/webui/web/js/api.js @@ -717,9 +717,10 @@ class VersityAPI { signal: signal || undefined, }); } catch (e) { - // Browsers surface CORS blocks as a generic TypeError. + // Browsers surface network-level failures (CORS, TLS/certificate errors, + // unreachable host) as a generic TypeError with no further detail. if (e instanceof TypeError) { - throw new Error(`CORS blocked by gateway. Allow origin ${window.location.origin} and headers Authorization, X-Amz-Date, X-Amz-Content-Sha256, Content-Type.`); + throw new Error(`Network error: cannot reach gateway. Common causes: CORS policy (gateway must allow origin ${window.location.origin}), TLS/certificate error (untrusted or self-signed certificate rejected by the browser), or the gateway is unreachable.`); } throw e; } @@ -788,9 +789,9 @@ class VersityAPI { await this.listBucketsS3(); this.setAdminRole(false); } catch (s3Error) { - // If the gateway is reachable but the browser blocks the response due to CORS, - // surface that as an error so the UI can show a useful message. - if (s3Error && typeof s3Error.message === 'string' && s3Error.message.includes('CORS blocked')) { + // If the request failed at the network level (CORS, TLS, or unreachable), + // surface that error so the UI can show a useful diagnostic message. + if (s3Error && typeof s3Error.message === 'string' && s3Error.message.startsWith('Network error:')) { throw s3Error; } return 'none'; @@ -931,8 +932,10 @@ class VersityAPI { try { httpResponse = await fetch(presignedUrl, { method: 'GET' }); } catch (e) { + // Browsers surface network-level failures (CORS, TLS/certificate errors, + // unreachable host) as a generic TypeError with no further detail. if (e instanceof TypeError) { - throw new Error(`CORS blocked by gateway. Allow origin ${window.location.origin} for S3 responses (GET / and bucket/object operations).`); + throw new Error(`Network error: cannot reach gateway. Common causes: CORS policy (gateway must allow origin ${window.location.origin}), TLS/certificate error (untrusted or self-signed certificate rejected by the browser), or the gateway is unreachable.`); } throw e; }