diff --git a/public/locales/en/common.json b/public/locales/en/common.json index cc946e783..c4b41f057 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -13,7 +13,11 @@ "widget": { "missing_type": "Missing Widget Type: {{type}}", "api_error": "API Error", - "status": "Status" + "status": "Status", + "debug_info": "Debug Information", + "url": "URL", + "raw_error": "Raw Error", + "response_data": "Response Data" }, "weather": { "current": "Current Location", diff --git a/src/components/services/widget/error.jsx b/src/components/services/widget/error.jsx new file mode 100644 index 000000000..a6d400e8d --- /dev/null +++ b/src/components/services/widget/error.jsx @@ -0,0 +1,48 @@ +import { useTranslation } from "react-i18next"; + +function displayError(error) { + return JSON.stringify(error[1] ? error[1] : error, null, 4); +} + +function displayData(data) { + return (data.type === 'Buffer') ? Buffer.from(data).toString() : JSON.stringify(data, 4); +} + +export default function Error({ error }) { + const { t } = useTranslation(); + + if (error?.data?.error) { + error = error.data.error; // eslint-disable-line no-param-reassign + } + + return ( +
+
Something went wrong.
+
+ {t("widget.debug_info")} +
+
    +
  • + {t("widget.api_error")}: {error.message} +
  • + {error.url &&
  • + {t("widget.url")}: {error.url} +
  • } + {error.rawError &&
  • + {t("widget.raw_error")}: +
    + {displayError(error.rawError)} +
    +
  • } + {error.data &&
  • + {t("widget.response_data")}: +
    + {displayData(error.data)} +
    +
  • } +
+
+
+
+ ); +} diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 54c393b17..f1436d511 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -1,5 +1,6 @@ import getServiceWidget from "utils/config/service-helpers"; import { formatApiCall } from "utils/proxy/api-helpers"; +import validateWidgetData from "utils/proxy/validate-widget-data"; import { httpProxy } from "utils/proxy/http"; import createLogger from "utils/logger"; import widgets from "widgets/widgets"; @@ -54,6 +55,10 @@ export default async function credentialedProxyHandler(req, res) { logger.debug("HTTP Error %d calling %s//%s%s...", status, url.protocol, url.hostname, url.pathname); } + if (!validateWidgetData(widget, endpoint, data)) { + return res.status(500).json({error: {message: "Invalid data", url, data}}); + } + if (contentType) res.setHeader("Content-Type", contentType); return res.status(status).send(data); } diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index f93c83f2f..02c3d4c38 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -1,5 +1,6 @@ import getServiceWidget from "utils/config/service-helpers"; import { formatApiCall } from "utils/proxy/api-helpers"; +import validateWidgetData from "utils/proxy/validate-widget-data"; import { httpProxy } from "utils/proxy/http"; import createLogger from "utils/logger"; import widgets from "widgets/widgets"; @@ -32,6 +33,11 @@ export default async function genericProxyHandler(req, res, map) { }); let resultData = data; + + if (!validateWidgetData(widget, endpoint, resultData)) { + return res.status(status).json({error: {message: "Invalid data", url, data: resultData}}); + } + if (status === 200 && map) { resultData = map(data); } @@ -44,6 +50,7 @@ export default async function genericProxyHandler(req, res, map) { if (status >= 400) { logger.debug("HTTP Error %d calling %s//%s%s...", status, url.protocol, url.hostname, url.pathname); + return res.status(status).json({error: {message: "HTTP Error", url, data}}); } return res.status(status).send(resultData); diff --git a/src/utils/proxy/http.js b/src/utils/proxy/http.js index 4eba83f32..93538202d 100644 --- a/src/utils/proxy/http.js +++ b/src/utils/proxy/http.js @@ -98,6 +98,6 @@ export async function httpProxy(url, params = {}) { catch (err) { logger.error("Error calling %s//%s%s...", url.protocol, url.hostname, url.pathname); logger.error(err); - return [500, "application/json", { error: "Unexpected error" }, null]; + return [500, "application/json", { error: {message: err?.message ?? "Unknown error", url, rawError: err} }, null]; } } diff --git a/src/utils/proxy/validate-widget-data.js b/src/utils/proxy/validate-widget-data.js new file mode 100644 index 000000000..a9664363c --- /dev/null +++ b/src/utils/proxy/validate-widget-data.js @@ -0,0 +1,22 @@ +import widgets from "widgets/widgets"; + +export default function validateWidgetData(widget, endpoint, data) { + let valid = true; + let dataParsed; + try { + dataParsed = JSON.parse(data); + } catch (e) { + valid = false; + } + + if (dataParsed) { + const validate = widgets[widget.type]?.mappings?.[endpoint]?.validate; + validate.forEach(key => { + if (dataParsed[key] === undefined) { + valid = false; + } + }); + } + + return valid; +}