import { withAuth } from "utils/auth"; 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; } 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" }); } } export default withAuth(handler);