mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-07 09:35:54 -08:00
Run pre-commit hooks over existing codebase
Co-Authored-By: Ben Phelps <ben@phelps.io>
This commit is contained in:
@@ -5,7 +5,7 @@ export function formatApiCall(url, args) {
|
||||
return args[key] || "";
|
||||
};
|
||||
|
||||
return url.replace(/\/+$/, "").replace(find, replace).replace(find,replace);
|
||||
return url.replace(/\/+$/, "").replace(find, replace).replace(find, replace);
|
||||
}
|
||||
|
||||
function getURLSearchParams(widget, endpoint) {
|
||||
@@ -57,8 +57,8 @@ export function jsonArrayFilter(data, filter) {
|
||||
export function sanitizeErrorURL(errorURL) {
|
||||
// Dont display sensitive params on frontend
|
||||
const url = new URL(errorURL);
|
||||
["apikey", "api_key", "token", "t"].forEach(key => {
|
||||
if (url.searchParams.has(key)) url.searchParams.set(key, "***")
|
||||
["apikey", "api_key", "token", "t"].forEach((key) => {
|
||||
if (url.searchParams.has(key)) url.searchParams.set(key, "***");
|
||||
});
|
||||
return url.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,17 +28,12 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
headers["X-CMC_PRO_API_KEY"] = `${widget.key}`;
|
||||
} else if (widget.type === "gotify") {
|
||||
headers["X-gotify-Key"] = `${widget.key}`;
|
||||
} else if ([
|
||||
"authentik",
|
||||
"cloudflared",
|
||||
"ghostfolio",
|
||||
"mealie",
|
||||
"tailscale",
|
||||
"truenas",
|
||||
"pterodactyl",
|
||||
].includes(widget.type))
|
||||
{
|
||||
headers.Authorization = `Bearer ${widget.key}`;
|
||||
} else if (
|
||||
["authentik", "cloudflared", "ghostfolio", "mealie", "tailscale", "truenas", "pterodactyl"].includes(
|
||||
widget.type,
|
||||
)
|
||||
) {
|
||||
headers.Authorization = `Bearer ${widget.key}`;
|
||||
} else if (widget.type === "proxmox") {
|
||||
headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
|
||||
} else if (widget.type === "proxmoxbackupserver") {
|
||||
@@ -62,8 +57,7 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
} else {
|
||||
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
|
||||
}
|
||||
}
|
||||
else if (widget.type === "azuredevops") {
|
||||
} else if (widget.type === "azuredevops") {
|
||||
headers.Authorization = `Basic ${Buffer.from(`$:${widget.key}`).toString("base64")}`;
|
||||
} else if (widget.type === "glances") {
|
||||
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
|
||||
@@ -91,10 +85,12 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
if (status >= 400) {
|
||||
logger.error("HTTP Error %d calling %s", status, url.toString());
|
||||
}
|
||||
|
||||
|
||||
if (status === 200) {
|
||||
if (!validateWidgetData(widget, endpoint, resultData)) {
|
||||
return res.status(500).json({error: {message: "Invalid data", url: sanitizeErrorURL(url), data: resultData}});
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: { message: "Invalid data", url: sanitizeErrorURL(url), data: resultData } });
|
||||
}
|
||||
if (map) resultData = map(resultData);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ export default async function genericProxyHandler(req, res, map) {
|
||||
|
||||
if (widget) {
|
||||
// if there are more than one question marks, replace others to &
|
||||
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }).replace(/(?<=\?.*)\?/g, '&'));
|
||||
const url = new URL(
|
||||
formatApiCall(widgets[widget.type].api, { endpoint, ...widget }).replace(/(?<=\?.*)\?/g, "&"),
|
||||
);
|
||||
|
||||
const headers = req.extraHeaders ?? widget.headers ?? {};
|
||||
|
||||
|
||||
if (widget.username && widget.password) {
|
||||
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
|
||||
}
|
||||
@@ -30,7 +32,7 @@ export default async function genericProxyHandler(req, res, map) {
|
||||
const params = {
|
||||
method: widget.method ?? req.method,
|
||||
headers,
|
||||
}
|
||||
};
|
||||
if (req.body) {
|
||||
params.body = req.body;
|
||||
}
|
||||
@@ -38,14 +40,16 @@ export default async function genericProxyHandler(req, res, map) {
|
||||
const [status, contentType, data] = await httpProxy(url, params);
|
||||
|
||||
let resultData = data;
|
||||
|
||||
|
||||
if (resultData.error?.url) {
|
||||
resultData.error.url = sanitizeErrorURL(url);
|
||||
}
|
||||
|
||||
|
||||
if (status === 200) {
|
||||
if (!validateWidgetData(widget, endpoint, resultData)) {
|
||||
return res.status(status).json({error: {message: "Invalid data", url: sanitizeErrorURL(url), data: resultData}});
|
||||
return res
|
||||
.status(status)
|
||||
.json({ error: { message: "Invalid data", url: sanitizeErrorURL(url), data: resultData } });
|
||||
}
|
||||
if (map) resultData = map(resultData);
|
||||
}
|
||||
@@ -62,10 +66,10 @@ export default async function genericProxyHandler(req, res, map) {
|
||||
status,
|
||||
url.protocol,
|
||||
url.hostname,
|
||||
url.port ? `:${url.port}` : '',
|
||||
url.pathname
|
||||
url.port ? `:${url.port}` : "",
|
||||
url.pathname,
|
||||
);
|
||||
return res.status(status).json({error: {message: "HTTP Error", url: sanitizeErrorURL(url), resultData}});
|
||||
return res.status(status).json({ error: { message: "HTTP Error", url: sanitizeErrorURL(url), resultData } });
|
||||
}
|
||||
|
||||
return res.status(status).send(resultData);
|
||||
|
||||
@@ -11,8 +11,8 @@ const logger = createLogger("jsonrpcProxyHandler");
|
||||
export async function sendJsonRpcRequest(url, method, params, username, password) {
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
"accept": "application/json"
|
||||
}
|
||||
accept: "application/json",
|
||||
};
|
||||
|
||||
if (username && password) {
|
||||
headers.authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
||||
@@ -23,7 +23,7 @@ export async function sendJsonRpcRequest(url, method, params, username, password
|
||||
const httpRequestParams = {
|
||||
method: "POST",
|
||||
headers,
|
||||
body
|
||||
body,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@@ -33,7 +33,7 @@ export async function sendJsonRpcRequest(url, method, params, username, password
|
||||
|
||||
// in order to get access to the underlying error object in the JSON response
|
||||
// you must set `result` equal to undefined
|
||||
if (json.error && (json.result === null)) {
|
||||
if (json.error && json.result === null) {
|
||||
json.result = undefined;
|
||||
}
|
||||
return client.receive(json);
|
||||
@@ -45,15 +45,14 @@ export async function sendJsonRpcRequest(url, method, params, username, password
|
||||
try {
|
||||
const response = await client.request(method, params);
|
||||
return [200, "application/json", JSON.stringify(response)];
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
if (e instanceof JSONRPCErrorException) {
|
||||
logger.debug("Error calling JSONPRC endpoint: %s. %s", url, e.message);
|
||||
return [200, "application/json", JSON.stringify({result: null, error: {code: e.code, message: e.message}})];
|
||||
return [200, "application/json", JSON.stringify({ result: null, error: { code: e.code, message: e.message } })];
|
||||
}
|
||||
|
||||
logger.warn("Error calling JSONPRC endpoint: %s. %s", url, e);
|
||||
return [500, "application/json", JSON.stringify({result: null, error: {code: 2, message: e.toString()}})];
|
||||
return [500, "application/json", JSON.stringify({ result: null, error: { code: 2, message: e.toString() } })];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const INFO_ENDPOINT = "{url}/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query";
|
||||
const AUTH_ENDPOINT = "{url}/webapi/{path}?api=SYNO.API.Auth&version={maxVersion}&method=login&account={username}&passwd={password}&session=DownloadStation&format=cookie";
|
||||
const AUTH_ENDPOINT =
|
||||
"{url}/webapi/{path}?api=SYNO.API.Auth&version={maxVersion}&method=login&account={username}&passwd={password}&session=DownloadStation&format=cookie";
|
||||
const AUTH_API_NAME = "SYNO.API.Auth";
|
||||
|
||||
const proxyName = "synologyProxyHandler";
|
||||
@@ -40,7 +41,7 @@ async function login(loginUrl) {
|
||||
}
|
||||
|
||||
async function getApiInfo(serviceWidget, apiName, serviceName) {
|
||||
const cacheKey = `${proxyName}__${apiName}__${serviceName}`
|
||||
const cacheKey = `${proxyName}__${apiName}__${serviceName}`;
|
||||
let { cgiPath, maxVersion } = cache.get(cacheKey) ?? {};
|
||||
if (cgiPath && maxVersion) {
|
||||
return [cgiPath, maxVersion];
|
||||
@@ -56,12 +57,13 @@ async function getApiInfo(serviceWidget, apiName, serviceName) {
|
||||
if (json?.data?.[apiName]) {
|
||||
cgiPath = json.data[apiName].path;
|
||||
maxVersion = json.data[apiName].maxVersion;
|
||||
logger.debug(`Detected ${serviceWidget.type}: apiName '${apiName}', cgiPath '${cgiPath}', and maxVersion ${maxVersion}`);
|
||||
logger.debug(
|
||||
`Detected ${serviceWidget.type}: apiName '${apiName}', cgiPath '${cgiPath}', and maxVersion ${maxVersion}`,
|
||||
);
|
||||
cache.put(cacheKey, { cgiPath, maxVersion });
|
||||
return [cgiPath, maxVersion];
|
||||
}
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
logger.warn(`Error ${status} obtaining ${apiName} info`);
|
||||
}
|
||||
}
|
||||
@@ -124,7 +126,7 @@ function toError(url, synologyError) {
|
||||
error.error = synologyError.message ?? "Unknown error.";
|
||||
break;
|
||||
}
|
||||
logger.warn(`Unable to call ${url}. code: ${code}, error: ${error.error}.`)
|
||||
logger.warn(`Unable to call ${url}. code: ${code}, error: ${error.error}.`);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -144,7 +146,7 @@ export default async function synologyProxyHandler(req, res) {
|
||||
|
||||
const [cgiPath, maxVersion] = await getApiInfo(serviceWidget, mapping.apiName, service);
|
||||
if (!cgiPath || !maxVersion) {
|
||||
return res.status(400).json({ error: `Unrecognized API name: ${mapping.apiName}`})
|
||||
return res.status(400).json({ error: `Unrecognized API name: ${mapping.apiName}` });
|
||||
}
|
||||
|
||||
const url = formatApiCall(widget.api, {
|
||||
@@ -152,7 +154,7 @@ export default async function synologyProxyHandler(req, res) {
|
||||
apiMethod: mapping.apiMethod,
|
||||
cgiPath,
|
||||
maxVersion,
|
||||
...serviceWidget
|
||||
...serviceWidget,
|
||||
});
|
||||
let [status, contentType, data] = await httpProxy(url);
|
||||
if (status !== 200) {
|
||||
|
||||
@@ -25,21 +25,21 @@ function handleRequest(requestor, url, params) {
|
||||
addCookieHandler(url, params);
|
||||
if (params?.body) {
|
||||
params.headers = params.headers ?? {};
|
||||
params.headers['content-length'] = Buffer.byteLength(params.body);
|
||||
params.headers["content-length"] = Buffer.byteLength(params.body);
|
||||
}
|
||||
|
||||
const request = requestor.request(url, params, (response) => {
|
||||
const data = [];
|
||||
const contentEncoding = response.headers['content-encoding']?.trim().toLowerCase();
|
||||
const contentEncoding = response.headers["content-encoding"]?.trim().toLowerCase();
|
||||
|
||||
let responseContent = response;
|
||||
if (contentEncoding === 'gzip' || contentEncoding === 'deflate') {
|
||||
if (contentEncoding === "gzip" || contentEncoding === "deflate") {
|
||||
// https://github.com/request/request/blob/3c0cddc7c8eb60b470e9519da85896ed7ee0081e/request.js#L1018-L1025
|
||||
// Be more lenient with decoding compressed responses, in case of invalid gzip responses that are still accepted
|
||||
// by common browsers.
|
||||
responseContent = createUnzip({
|
||||
flush: zlibConstants.Z_SYNC_FLUSH,
|
||||
finishFlush: zlibConstants.Z_SYNC_FLUSH
|
||||
finishFlush: zlibConstants.Z_SYNC_FLUSH,
|
||||
});
|
||||
|
||||
// zlib errors
|
||||
@@ -100,14 +100,13 @@ export async function httpProxy(url, params = {}) {
|
||||
try {
|
||||
const [status, contentType, data, responseHeaders] = await request;
|
||||
return [status, contentType, data, responseHeaders];
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
"Error calling %s//%s%s%s...",
|
||||
constructedUrl.protocol,
|
||||
constructedUrl.hostname,
|
||||
constructedUrl.port ? `:${constructedUrl.port}` : '',
|
||||
constructedUrl.pathname
|
||||
constructedUrl.port ? `:${constructedUrl.port}` : "",
|
||||
constructedUrl.pathname,
|
||||
);
|
||||
logger.error(err);
|
||||
return [500, "application/json", { error: { message: err?.message ?? "Unknown error", url, rawError: err } }, null];
|
||||
|
||||
@@ -7,11 +7,11 @@ export default function useWidgetAPI(widget, ...options) {
|
||||
if (options && options[1]?.refreshInterval) {
|
||||
config.refreshInterval = options[1].refreshInterval;
|
||||
}
|
||||
let url = formatProxyUrl(widget, ...options)
|
||||
let url = formatProxyUrl(widget, ...options);
|
||||
if (options[0] === "") {
|
||||
url = null
|
||||
url = null;
|
||||
}
|
||||
const { data, error, mutate } = useSWR(url, config);
|
||||
// make the data error the top-level error
|
||||
return { data, error: data?.error ?? error, mutate }
|
||||
return { data, error: data?.error ?? error, mutate };
|
||||
}
|
||||
|
||||
@@ -2,34 +2,38 @@
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
export default function validateWidgetData(widget, endpoint, data) {
|
||||
let valid = true;
|
||||
let dataParsed = data;
|
||||
let error;
|
||||
let mapping;
|
||||
if (Buffer.isBuffer(data)) {
|
||||
try {
|
||||
dataParsed = JSON.parse(data);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
valid = false;
|
||||
}
|
||||
let valid = true;
|
||||
let dataParsed = data;
|
||||
let error;
|
||||
let mapping;
|
||||
if (Buffer.isBuffer(data)) {
|
||||
try {
|
||||
dataParsed = JSON.parse(data);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataParsed && Object.entries(dataParsed).length) {
|
||||
const mappings = widgets[widget.type]?.mappings;
|
||||
if (mappings) {
|
||||
mapping = Object.values(mappings).find(m => m.endpoint === endpoint);
|
||||
mapping?.validate?.forEach(key => {
|
||||
if (dataParsed[key] === undefined) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
if (dataParsed && Object.entries(dataParsed).length) {
|
||||
const mappings = widgets[widget.type]?.mappings;
|
||||
if (mappings) {
|
||||
mapping = Object.values(mappings).find((m) => m.endpoint === endpoint);
|
||||
mapping?.validate?.forEach((key) => {
|
||||
if (dataParsed[key] === undefined) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
console.warn(`Invalid data for widget '${widget.type}' endpoint '${endpoint}':\nExpected:${mapping?.validate}\nParse error: ${error ?? "none"}\nData: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
return valid;
|
||||
if (!valid) {
|
||||
console.warn(
|
||||
`Invalid data for widget '${widget.type}' endpoint '${endpoint}':\nExpected:${mapping?.validate}\nParse error: ${
|
||||
error ?? "none"
|
||||
}\nData: ${JSON.stringify(data)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user