From 2dcf44ed08b7a577bfb2a5b87aac8687a92444d6 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Tue, 10 Sep 2024 09:25:38 +0200 Subject: [PATCH] PB-977: Don't use service-proxy for internal domain and for GPX that support CORS GPX file used always the service-proxy even if the server supported CORS. Also on the service-proxy we could see that some requests were made over service-proxy for internal server like public.geo.admin.ch. This was due to the fact that if for any reason the initial request failed (network failure) we fallback to the service proxy. Now in that case we don't fallback anymore. NOTE unfortunately there is no way for a web application to check for CORS support see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors --- src/api/file-proxy.api.js | 38 ---------- src/api/files.api.js | 69 +++++++++++++----- src/config/baseUrl.config.js | 3 + .../ImportFile/ImportFileOnlineTab.vue | 17 +---- src/store/plugins/load-gpx-data.plugin.js | 5 +- src/utils/kmlUtils.js | 2 +- src/utils/utils.js | 11 +++ tests/cypress/tests-e2e/importToolFile.cy.js | 70 ++++++++++++++++--- 8 files changed, 134 insertions(+), 81 deletions(-) diff --git a/src/api/file-proxy.api.js b/src/api/file-proxy.api.js index a14104e89..1e73b53a6 100644 --- a/src/api/file-proxy.api.js +++ b/src/api/file-proxy.api.js @@ -1,4 +1,3 @@ -import axios from 'axios' import { isString } from 'lodash' import { getServiceProxyBaseUrl } from '@/config/baseUrl.config' @@ -57,40 +56,3 @@ export function proxifyUrl(url) { } return `${getServiceProxyBaseUrl()}${fileAsPath}` } - -/** - * Get a file through our service-proxy backend, taking care of CORS headers in the process. - * - * That means that a file for which there is no defined CORS header will still be accessible through - * this function (i.e. Dropbox/name your cloud share links) - * - * @param {String} fileUrl - * @param {Object} [options] - * @param {Number} [options.timeout] How long should the call wait before timing out - * @returns {Promise} A promise which resolve to the proxy response - */ -export default function getFileThroughProxy(fileUrl, options = {}) { - const { timeout = null } = options - return new Promise((resolve, reject) => { - try { - axios({ - method: 'get', - url: proxifyUrl(fileUrl), - timeout, - }) - .then((response) => { - resolve(response) - }) - .catch((error) => { - log.error( - 'Error while accessing file URL through service-proxy', - fileUrl, - error - ) - reject(error) - }) - } catch (error) { - reject(error) - } - }) -} diff --git a/src/api/files.api.js b/src/api/files.api.js index f63f0c2ea..12629adc5 100644 --- a/src/api/files.api.js +++ b/src/api/files.api.js @@ -1,10 +1,11 @@ -import axios, { AxiosError } from 'axios' +import axios from 'axios' import FormData from 'form-data' import pako from 'pako' -import getFileThroughProxy from '@/api/file-proxy.api' +import { proxifyUrl } from '@/api/file-proxy.api' import { getServiceKmlBaseUrl } from '@/config/baseUrl.config' import log from '@/utils/logging' +import { isInternalUrl } from '@/utils/utils' /** * KML links @@ -312,8 +313,7 @@ export function loadKmlData(kmlLayer) { new Error(`No file URL defined in this KML layer, cannot load data ${kmlLayer.id}`) ) } - axios - .get(kmlLayer.kmlFileUrl) + getFileFromUrl(kmlLayer.kmlFileUrl) .then((response) => { if (response.status === 200 && response.data) { resolve(response.data) @@ -324,19 +324,54 @@ export function loadKmlData(kmlLayer) { } }) .catch((error) => { - if (error instanceof AxiosError) { - getFileThroughProxy(kmlLayer.kmlFileUrl) - .then((response) => { - resolve(response.data) - }) - .catch((error) => { - reject(error) - }) - } else { - const msg = `Failed to load KML data: ${error}` - log.error(msg) - reject(new Error(msg)) - } + const msg = `Failed to load KML data: ${error}` + log.error(msg) + reject(new Error(msg)) }) }) } + +/** + * Generic function to load a file from a given URL. + * + * When the URL is not an internal url and it doesn't support CORS or use HTTP it is sent over a + * proxy. + * + * @param {string} url URL to fetch + * @param {Number} [options.timeout] How long should the call wait before timing out + * @returns {Promise>} + */ +export async function getFileFromUrl(url, options = {}) { + const { timeout = null } = options + if (/^https?:\/\/localhost/.test(url) || isInternalUrl(url)) { + // don't go through proxy if it is on localhost or the internal server + return axios.get(url, { timeout }) + } else if (url.startsWith('http://')) { + // HTTP request goes through the proxy + return axios.get(proxifyUrl(url), { timeout }) + } + + // For other urls we need to check if they support CORS + let supportCORS = false + try { + // unfortunately we cannot do a real preflight call using options because browser don't + // allow to set the Access-Control-* headers ! Also there is no way to find out if a request + // is failing due to network reason or due to CORS issue, + // see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors + // Therefore here we try to get the resource using head instead + await axios.head(url, { timeout }) + supportCORS = true + } catch (error) { + log.error( + `URL ${url} failed with ${error}. It might be due to CORS issue, ` + + `therefore the request will be fallback to the service-proxy` + ) + } + + if (supportCORS) { + // Server support CORS + return axios.get(url, { timeout }) + } + // server don't support CORS use proxy + return axios.get(proxifyUrl(url), { timeout }) +} diff --git a/src/config/baseUrl.config.js b/src/config/baseUrl.config.js index 0c9d0b79d..4f1e105a8 100644 --- a/src/config/baseUrl.config.js +++ b/src/config/baseUrl.config.js @@ -206,3 +206,6 @@ export function get3dTilesBaseUrl() { export function getVectorTilesBaseUrl() { return getBaseUrl('vectorTiles') } + +export const internalDomainRegex = + import.meta.env.VITE_APP_INTERNAL_DOMAIN_REGEX ?? /^https:\/\/[^/]*(bgdi|admin)\.ch/ diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue index 2773f8fb2..25d594cea 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue @@ -1,9 +1,9 @@