fix: encode headscale rename path segments

(cherry picked from commit 623e7c03f1)
This commit is contained in:
Aarnav Tale
2026-05-14 13:03:53 -04:00
parent c6b6cbc122
commit 1e0ff7ead6
3 changed files with 76 additions and 2 deletions
+5 -1
View File
@@ -145,7 +145,11 @@ export default defineApiEndpoints<NodeEndpoints>((client, apiKey) => ({
},
renameNode: async (nodeId, newName) => {
await client.apiFetch<void>("POST", `v1/node/${nodeId}/rename/${newName}`, apiKey);
await client.apiFetch<void>(
"POST",
`v1/node/${nodeId}/rename/${encodeURIComponent(newName)}`,
apiKey,
);
},
setNodeTags: async (nodeId, tags) => {
+5 -1
View File
@@ -78,6 +78,10 @@ export default defineApiEndpoints<UserEndpoints>((client, apiKey) => ({
},
renameUser: async (oldId, newName) => {
await client.apiFetch<void>("POST", `v1/user/${oldId}/rename/${newName}`, apiKey);
await client.apiFetch<void>(
"POST",
`v1/user/${oldId}/rename/${encodeURIComponent(newName)}`,
apiKey,
);
},
}));
+66
View File
@@ -0,0 +1,66 @@
import { describe, expect, test } from "vitest";
import type { HeadscaleApiInterface } from "~/server/headscale/api";
import nodeEndpoints from "~/server/headscale/api/endpoints/nodes";
import userEndpoints from "~/server/headscale/api/endpoints/users";
type ApiFetchArgs = Parameters<HeadscaleApiInterface["clientHelpers"]["apiFetch"]>;
function createApiClientRecorder() {
const calls: Array<{
method: ApiFetchArgs[0];
apiPath: ApiFetchArgs[1];
apiKey: ApiFetchArgs[2];
bodyOrQuery: ApiFetchArgs[3];
}> = [];
const client = {
isAtleast: () => false,
rawFetch: async () => {
throw new Error("rawFetch should not be called");
},
apiFetch: async <T>(
method: ApiFetchArgs[0],
apiPath: ApiFetchArgs[1],
apiKey: ApiFetchArgs[2],
bodyOrQuery?: ApiFetchArgs[3],
) => {
calls.push({ method, apiPath, apiKey, bodyOrQuery });
return undefined as T;
},
} as HeadscaleApiInterface["clientHelpers"];
return { calls, client };
}
describe("Headscale API path encoding", () => {
test("encodes node rename names as a single URL segment", async () => {
const { calls, client } = createApiClientRecorder();
await nodeEndpoints(client, "api-key").renameNode("2", "../../1/expire");
expect(calls).toEqual([
{
method: "POST",
apiPath: "v1/node/2/rename/..%2F..%2F1%2Fexpire",
apiKey: "api-key",
bodyOrQuery: undefined,
},
]);
});
test("encodes user rename names as a single URL segment", async () => {
const { calls, client } = createApiClientRecorder();
await userEndpoints(client, "api-key").renameUser("3", "../../../user/4/rename/pwned-user@");
expect(calls).toEqual([
{
method: "POST",
apiPath: "v1/user/3/rename/..%2F..%2F..%2Fuser%2F4%2Frename%2Fpwned-user%40",
apiKey: "api-key",
bodyOrQuery: undefined,
},
]);
});
});