From 30dbabd73d234be7b52f7187bb7adc590d8273e8 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 12 Aug 2025 15:27:03 -0700 Subject: [PATCH] Add internal proxy for gerbil endpoints --- server/routers/gerbil/index.ts | 3 +- server/routers/gerbil/proxy.ts | 101 +++++++++++++++++++++++++++++++++ server/routers/internal.ts | 16 ++++-- 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 server/routers/gerbil/proxy.ts diff --git a/server/routers/gerbil/index.ts b/server/routers/gerbil/index.ts index 4a4f3b60..7cf4dfaa 100644 --- a/server/routers/gerbil/index.ts +++ b/server/routers/gerbil/index.ts @@ -1,4 +1,5 @@ export * from "./getConfig"; export * from "./receiveBandwidth"; export * from "./updateHolePunch"; -export * from "./getAllRelays"; \ No newline at end of file +export * from "./getAllRelays"; +export { default as proxyRouter } from "./proxy"; \ No newline at end of file diff --git a/server/routers/gerbil/proxy.ts b/server/routers/gerbil/proxy.ts new file mode 100644 index 00000000..9a6eb98e --- /dev/null +++ b/server/routers/gerbil/proxy.ts @@ -0,0 +1,101 @@ +import { Request, Response, NextFunction } from "express"; +import { Router } from "express"; +import axios from "axios"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import config from "@server/lib/config"; + +const proxyRouter = Router(); + +/** + * Proxy function that forwards requests to the remote cloud server + */ +async function proxyToRemote( + req: Request, + res: Response, + next: NextFunction, + endpoint: string +): Promise { + try { + const remoteConfig = config.getRawConfig().hybrid; + + if (!remoteConfig?.endpoint) { + logger.error("Remote endpoint not configured in hybrid.endpoint config"); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Remote endpoint not configured" + ) + ); + } + + const remoteUrl = `${remoteConfig.endpoint.replace(/\/$/, '')}/api/v1/gerbil/${endpoint}`; + + logger.debug(`Proxying request to remote server: ${remoteUrl}`); + + // Forward the request to the remote server + const response = await axios({ + method: req.method as any, + url: remoteUrl, + data: req.body, + headers: { + 'Content-Type': 'application/json', + }, + params: req.query, + timeout: 30000, // 30 second timeout + validateStatus: () => true // Don't throw on non-2xx status codes + }); + + // Forward the response status and data + return res.status(response.status).json(response.data); + + } catch (error) { + logger.error("Error proxying request to remote server:", error); + + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') { + return next( + createHttpError( + HttpCode.SERVICE_UNAVAILABLE, + "Remote server is unavailable" + ) + ); + } + if (error.code === 'ECONNABORTED') { + return next( + createHttpError( + HttpCode.REQUEST_TIMEOUT, + "Request to remote server timed out" + ) + ); + } + } + + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Error communicating with remote server" + ) + ); + } +} + +// Proxy endpoints for each gerbil route +proxyRouter.post("/get-config", (req, res, next) => + proxyToRemote(req, res, next, "get-config") +); + +proxyRouter.post("/receive-bandwidth", (req, res, next) => + proxyToRemote(req, res, next, "receive-bandwidth") +); + +proxyRouter.post("/update-hole-punch", (req, res, next) => + proxyToRemote(req, res, next, "update-hole-punch") +); + +proxyRouter.post("/get-all-relays", (req, res, next) => + proxyToRemote(req, res, next, "get-all-relays") +); + +export default proxyRouter; diff --git a/server/routers/internal.ts b/server/routers/internal.ts index 118c8ae3..3fa32d7c 100644 --- a/server/routers/internal.ts +++ b/server/routers/internal.ts @@ -7,6 +7,8 @@ import * as auth from "@server/routers/auth"; import * as supporterKey from "@server/routers/supporterKey"; import * as license from "@server/routers/license"; import * as idp from "@server/routers/idp"; +import proxyRouter from "@server/routers/gerbil/proxy"; +import config from "@server/lib/config"; import HttpCode from "@server/types/HttpCode"; import { verifyResourceAccess, @@ -49,10 +51,16 @@ internalRouter.get("/idp/:idpId", idp.getIdp); const gerbilRouter = Router(); internalRouter.use("/gerbil", gerbilRouter); -gerbilRouter.post("/get-config", gerbil.getConfig); -gerbilRouter.post("/receive-bandwidth", gerbil.receiveBandwidth); -gerbilRouter.post("/update-hole-punch", gerbil.updateHolePunch); -gerbilRouter.post("/get-all-relays", gerbil.getAllRelays); +if (config.getRawConfig().hybrid) { + // Use proxy router to forward requests to remote cloud server + gerbilRouter.use("/", proxyRouter); +} else { + // Use local gerbil endpoints + gerbilRouter.post("/get-config", gerbil.getConfig); + gerbilRouter.post("/receive-bandwidth", gerbil.receiveBandwidth); + gerbilRouter.post("/update-hole-punch", gerbil.updateHolePunch); + gerbilRouter.post("/get-all-relays", gerbil.getAllRelays); +} // Badger routes const badgerRouter = Router();