feat: add safe manual workspace uploads

This commit is contained in:
Federico Jaramillo Martinez
2026-06-25 15:16:44 +02:00
parent f0b779b062
commit e46d9ecbf8
34 changed files with 2314 additions and 131 deletions
+21 -4
View File
@@ -2,7 +2,7 @@ import type { FastifyInstance, FastifyReply } from "fastify";
import type { WebSocket } from "ws";
import { FEDERATED_HTTP_ROUTES, FEDERATED_WEBSOCKET_ROUTES } from "../../shared/federatedRoutes.js";
import { bridgeSockets } from "../webSocketBridge.js";
import { RemoteMachineRequestError } from "./machineClient.js";
import { RemoteMachineRequestError, type MachineRequestOptions } from "./machineClient.js";
import { MachineService } from "./machineService.js";
export const REMOTE_HTTP_ROUTES = FEDERATED_HTTP_ROUTES;
@@ -23,7 +23,7 @@ export function registerMachineProxyRoutes(app: FastifyInstance, machines = new
app.route<{ Params: { machineId: string }; Body: unknown }>({
method: spec.method,
url: `/api/machines/:machineId${spec.path}`,
handler: (request, reply) => proxyHttpRequest(machines, request.params.machineId, request.method, request.url, request.body, reply),
handler: (request, reply) => proxyHttpRequest(machines, request.params.machineId, request.method, request.url, request.body, request.headers["content-type"], reply),
});
}
@@ -34,7 +34,7 @@ export function registerMachineProxyRoutes(app: FastifyInstance, machines = new
}
}
async function proxyHttpRequest(machines: MachineService, machineId: string, method: string, requestUrl: string, body: unknown, reply: FastifyReply): Promise<FastifyReply> {
async function proxyHttpRequest(machines: MachineService, machineId: string, method: string, requestUrl: string, body: unknown, contentType: string | string[] | undefined, reply: FastifyReply): Promise<FastifyReply> {
if (machineId === "local") {
return reply.code(501).send({ error: "Local machine route is not registered for this endpoint" });
}
@@ -45,7 +45,10 @@ async function proxyHttpRequest(machines: MachineService, machineId: string, met
}
try {
const upstream = await client.request(method, remoteApiPath(machineId, requestUrl), body);
const requestOptions = proxyRequestOptions(body, contentType);
const upstream = requestOptions === undefined
? await client.request(method, remoteApiPath(machineId, requestUrl), body)
: await client.request(method, remoteApiPath(machineId, requestUrl), body, requestOptions);
reply.code(upstream.statusCode);
applySafeHeaders(reply, upstream.headers);
if (upstream.body === undefined) return await reply.send();
@@ -81,6 +84,20 @@ function remoteApiPath(machineId: string, requestUrl: string): string {
return `/api${compatPath}`;
}
function proxyRequestOptions(body: unknown, contentType: string | string[] | undefined): MachineRequestOptions | undefined {
if (!isRawProxyBody(body)) return undefined;
const value = firstHeaderValue(contentType);
return value === undefined || value === "" ? undefined : { contentType: value };
}
function isRawProxyBody(body: unknown): boolean {
return typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
}
function firstHeaderValue(value: string | string[] | undefined): string | undefined {
return Array.isArray(value) ? value[0] : value;
}
function applySafeHeaders(reply: FastifyReply, headers: Record<string, string | string[] | undefined>): void {
for (const [name, value] of Object.entries(headers)) {
if (value === undefined) continue;