chore(duplication): share service install helpers (#5495)

Share service install helpers; re-add SERVICE_VERSION_PATTERN regex to the shared schema (dropped in extraction, #5474) + tests rejecting malformed versions.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
This commit is contained in:
Jan Leon
2026-06-30 03:08:27 +02:00
committed by GitHub
parent e522df1302
commit ae93cfbee7
4 changed files with 206 additions and 73 deletions

View File

@@ -1,41 +1,6 @@
import { z } from "zod";
import { install, InstallResult } from "@/lib/services/installers/ninerouter";
import { InstallError, SERVICE_VERSION_PATTERN } from "@/lib/services/installers/utils";
import { createErrorResponse } from "@/lib/api/errorResponse";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
const BodySchema = z.object({
version: z
.string()
.regex(SERVICE_VERSION_PATTERN, "Invalid version: only letters, digits and . _ + - are allowed")
.optional()
.default("latest"),
});
import { install } from "@/lib/services/installers/ninerouter";
import { handleServiceInstall } from "@/app/api/services/_shared/installRoute";
export async function POST(request: Request): Promise<Response> {
let body: unknown;
try {
body = request.body === null ? {} : await request.json();
} catch {
return createErrorResponse({ status: 400, message: "Invalid JSON body" });
}
const parsed = BodySchema.safeParse(body);
if (!parsed.success) {
return createErrorResponse({ status: 400, message: parsed.error.message });
}
try {
const result: InstallResult = await install(parsed.data.version);
return Response.json({ ok: true, ...result });
} catch (err) {
if (err instanceof InstallError) {
return createErrorResponse({
status: err.httpStatus,
message: err.friendly,
});
}
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return createErrorResponse({ status: 500, message: msg });
}
return handleServiceInstall(request, install);
}

View File

@@ -0,0 +1,79 @@
import { z } from "zod";
import { createErrorResponse } from "@/lib/api/errorResponse";
import { InstallError, SERVICE_VERSION_PATTERN } from "@/lib/services/installers/utils";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
export type ServiceInstallResult = {
installedVersion: string;
installPath: string;
durationMs: number;
};
export type ServiceInstaller = (version: string) => Promise<ServiceInstallResult>;
const installBodySchema = z.object({
// Keep the version constrained by SERVICE_VERSION_PATTERN — the per-route schemas
// enforced this before the extraction (#5474); dropping it here would let strings
// like "../../malicious" reach the installer (#5495).
version: z
.string()
.regex(SERVICE_VERSION_PATTERN, "Invalid version: only letters, digits and . _ + - are allowed")
.optional()
.default("latest"),
});
export async function readServiceInstallVersion(request: Request): Promise<
| {
ok: true;
version: string;
}
| {
ok: false;
response: Response;
}
> {
let body: unknown;
try {
body = request.body === null ? {} : await request.json();
} catch {
return {
ok: false,
response: createErrorResponse({ status: 400, message: "Invalid JSON body" }),
};
}
const parsed = installBodySchema.safeParse(body);
if (!parsed.success) {
return {
ok: false,
response: createErrorResponse({ status: 400, message: parsed.error.message }),
};
}
return { ok: true, version: parsed.data.version };
}
export async function handleServiceInstall(
request: Request,
install: ServiceInstaller
): Promise<Response> {
const parsed = await readServiceInstallVersion(request);
if (!parsed.ok) {
return parsed.response;
}
try {
const result = await install(parsed.version);
return Response.json({ ok: true, ...result });
} catch (err) {
if (err instanceof InstallError) {
return createErrorResponse({
status: err.httpStatus,
message: err.friendly,
});
}
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return createErrorResponse({ status: 500, message: msg });
}
}

View File

@@ -1,38 +1,6 @@
import { z } from "zod";
import { install, InstallResult } from "@/lib/services/installers/cliproxy";
import { InstallError, SERVICE_VERSION_PATTERN } from "@/lib/services/installers/utils";
import { createErrorResponse } from "@/lib/api/errorResponse";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
const BodySchema = z.object({
version: z
.string()
.regex(SERVICE_VERSION_PATTERN, "Invalid version: only letters, digits and . _ + - are allowed")
.optional()
.default("latest"),
});
import { install } from "@/lib/services/installers/cliproxy";
import { handleServiceInstall } from "@/app/api/services/_shared/installRoute";
export async function POST(request: Request): Promise<Response> {
let body: unknown;
try {
body = request.body === null ? {} : await request.json();
} catch {
return createErrorResponse({ status: 400, message: "Invalid JSON body" });
}
const parsed = BodySchema.safeParse(body);
if (!parsed.success) {
return createErrorResponse({ status: 400, message: parsed.error.message });
}
try {
const result: InstallResult = await install(parsed.data.version);
return Response.json({ ok: true, ...result });
} catch (err) {
if (err instanceof InstallError) {
return createErrorResponse({ status: err.httpStatus, message: err.friendly });
}
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return createErrorResponse({ status: 500, message: msg });
}
return handleServiceInstall(request, install);
}

View File

@@ -0,0 +1,121 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import {
handleServiceInstall,
readServiceInstallVersion,
} from "../../../src/app/api/services/_shared/installRoute.ts";
import { InstallError } from "../../../src/lib/services/installers/utils.ts";
async function readJson(response: Response) {
return (await response.json()) as Record<string, unknown>;
}
test("readServiceInstallVersion defaults empty requests to latest", async () => {
const result = await readServiceInstallVersion(
new Request("http://localhost/api/services/example/install", { method: "POST" })
);
assert.deepEqual(result, { ok: true, version: "latest" });
});
test("readServiceInstallVersion returns the requested version", async () => {
const result = await readServiceInstallVersion(
new Request("http://localhost/api/services/example/install", {
method: "POST",
body: JSON.stringify({ version: "1.2.3" }),
headers: { "Content-Type": "application/json" },
})
);
assert.deepEqual(result, { ok: true, version: "1.2.3" });
});
test("readServiceInstallVersion rejects malformed versions (#5495 SERVICE_VERSION_PATTERN guard)", async () => {
const result = await readServiceInstallVersion(
new Request("http://localhost/api/services/example/install", {
method: "POST",
body: JSON.stringify({ version: "../../malicious" }),
headers: { "Content-Type": "application/json" },
})
);
assert.equal(result.ok, false);
if (result.ok) throw new Error("expected version validation failure");
assert.equal(result.response.status, 400);
});
test("handleServiceInstall never reaches the installer for a malformed version (#5495)", async () => {
const calls: string[] = [];
const response = await handleServiceInstall(
new Request("http://localhost/api/services/example/install", {
method: "POST",
body: JSON.stringify({ version: "v1; rm -rf /" }),
headers: { "Content-Type": "application/json" },
}),
async (version) => {
calls.push(version);
return { installedVersion: version, installPath: "/tmp/service", durationMs: 1 };
}
);
assert.deepEqual(calls, []);
assert.equal(response.status, 400);
});
test("readServiceInstallVersion preserves invalid JSON error shape", async () => {
const result = await readServiceInstallVersion(
new Request("http://localhost/api/services/example/install", {
method: "POST",
body: "not-json",
headers: { "Content-Type": "application/json" },
})
);
assert.equal(result.ok, false);
if (result.ok) throw new Error("expected parse failure");
assert.equal(result.response.status, 400);
const body = await readJson(result.response);
assert.equal((body.error as Record<string, unknown>).message, "Invalid JSON body");
});
test("handleServiceInstall wraps successful installer results", async () => {
const calls: string[] = [];
const response = await handleServiceInstall(
new Request("http://localhost/api/services/example/install", {
method: "POST",
body: JSON.stringify({ version: "2.0.0" }),
headers: { "Content-Type": "application/json" },
}),
async (version) => {
calls.push(version);
return {
installedVersion: version,
installPath: "/tmp/service",
durationMs: 42,
};
}
);
assert.deepEqual(calls, ["2.0.0"]);
assert.equal(response.status, 200);
assert.deepEqual(await readJson(response), {
ok: true,
installedVersion: "2.0.0",
installPath: "/tmp/service",
durationMs: 42,
});
});
test("handleServiceInstall maps InstallError to its friendly message and status", async () => {
const response = await handleServiceInstall(
new Request("http://localhost/api/services/example/install", { method: "POST" }),
async () => {
throw new InstallError("raw command failed", "Friendly install failure", 503);
}
);
assert.equal(response.status, 503);
const body = await readJson(response);
assert.equal((body.error as Record<string, unknown>).message, "Friendly install failure");
});