Files
homepage/src/pages/api/services/proxy.js
T
2026-09-01 20:25:13 -07:00

147 lines
4.9 KiB
JavaScript

import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import { formatApiCall } from "utils/proxy/api-helpers";
import genericProxyHandler from "utils/proxy/handlers/generic";
import calendarProxyHandler from "widgets/calendar/proxy";
import widgets from "widgets/widgets";
const logger = createLogger("servicesProxy");
function getSafeSegments(rawSegments, allowedSegments) {
if (typeof rawSegments !== "string" || !Array.isArray(allowedSegments)) return null;
let segments;
try {
segments = JSON.parse(rawSegments);
} catch {
return null;
}
if (!segments || typeof segments !== "object" || Array.isArray(segments)) return null;
const keys = Object.keys(segments);
if (keys.length !== allowedSegments.length || !keys.every((key) => allowedSegments.includes(key))) return null;
const safeSegments = {};
for (const key of allowedSegments) {
const value = segments[key];
if (
typeof value !== "string" ||
value.length === 0 ||
value.includes("%") ||
value.includes("/") ||
value.includes("\\") ||
value.includes("..")
) {
return null;
}
safeSegments[key] = encodeURIComponent(value);
}
return safeSegments;
}
export default async function handler(req, res) {
try {
const { service, group, index } = req.query;
const serviceWidget = await getServiceWidget(group, service, index);
let type = serviceWidget?.type;
// exceptions
if (type === "calendar") type = "ical";
else if (service === "unifi_console" && group === "unifi_console") type = "unifi_console";
const widget = widgets[type];
if (!widget) {
logger.debug("Unknown proxy service type: %s", type);
return res.status(403).json({ error: "Unknown proxy service type" });
}
const serviceProxyHandler = widget.proxyHandler || genericProxyHandler;
if (serviceProxyHandler instanceof Function) {
// quick return for no endpoint services, calendar is an exception
if (!req.query.endpoint || serviceProxyHandler === calendarProxyHandler) {
req.method = "GET";
req.body = undefined;
return await serviceProxyHandler(req, res);
}
// map opaque endpoints to their actual endpoint
if (widget?.mappings) {
const mapping = widget?.mappings?.[req.query.endpoint];
const mappingParams = mapping?.params;
const optionalParams = mapping?.optionalParams;
const map = mapping?.map;
const endpoint = mapping?.endpoint;
const endpointProxy = mapping?.proxyHandler || serviceProxyHandler;
if (mapping?.method && mapping.method !== req.method) {
logger.debug("Unsupported method: %s", req.method);
return res.status(403).json({ error: "Unsupported method" });
}
if (!endpoint) {
logger.debug("Unsupported service endpoint: %s", type);
return res.status(403).json({ error: "Unsupported service endpoint" });
}
req.method = mapping?.method || "GET";
req.body = mapping?.body;
req.query.endpoint = endpoint;
if (mapping.segments || req.query.segments) {
const segments = getSafeSegments(req.query.segments, mapping.segments);
if (!segments) {
logger.debug("Unsupported segments");
return res.status(403).json({ error: "Unsupported segment" });
}
req.query.endpoint = formatApiCall(endpoint, segments);
}
if (req.query.query && (mappingParams || optionalParams)) {
const queryParams = JSON.parse(req.query.query);
let filteredOptionalParams = [];
if (optionalParams) filteredOptionalParams = optionalParams.filter((p) => queryParams[p] !== undefined);
let params = [];
if (mappingParams) params = params.concat(mappingParams);
if (filteredOptionalParams) params = params.concat(filteredOptionalParams);
const query = new URLSearchParams(params.map((p) => [p, queryParams[p]]));
req.query.endpoint = `${req.query.endpoint}?${query}`;
}
if (mapping?.headers) {
req.extraHeaders = mapping.headers;
}
if (endpointProxy instanceof Function) {
return await endpointProxy(req, res, map);
}
return await serviceProxyHandler(req, res, map);
}
if (widget.allowedEndpoints instanceof RegExp) {
if (widget.allowedEndpoints.test(req.query.endpoint)) {
req.method = "GET";
req.body = undefined;
return await serviceProxyHandler(req, res);
}
}
logger.debug("Unmapped proxy request.");
return res.status(403).json({ error: "Unmapped proxy request." });
}
logger.debug("Unknown proxy service type: %s", type);
return res.status(403).json({ error: "Unknown proxy service type" });
} catch (e) {
if (e) logger.error(e);
return res.status(500).send({ error: "Unexpected error" });
}
}