From 70051735f63c0872b6955fe1dd5299cdf58a50b6 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 6 Mar 2026 12:31:58 -0600 Subject: [PATCH] Fix OpenClaw invite header normalization compatibility --- .../invite-accept-openclaw-defaults.test.ts | 32 ++++++++++ server/src/routes/access.ts | 59 ++++++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/server/src/__tests__/invite-accept-openclaw-defaults.test.ts b/server/src/__tests__/invite-accept-openclaw-defaults.test.ts index f206f2a8..64a3cd4e 100644 --- a/server/src/__tests__/invite-accept-openclaw-defaults.test.ts +++ b/server/src/__tests__/invite-accept-openclaw-defaults.test.ts @@ -110,6 +110,38 @@ describe("buildJoinDefaultsPayloadForAccept", () => { }); }); + it("accepts auth headers provided as tuple entries", () => { + const result = buildJoinDefaultsPayloadForAccept({ + adapterType: "openclaw", + defaultsPayload: { + headers: [["x-openclaw-auth", "gateway-token"]], + }, + }) as Record; + + expect(result).toMatchObject({ + headers: { + "x-openclaw-auth": "gateway-token", + }, + webhookAuthHeader: "Bearer gateway-token", + }); + }); + + it("accepts auth headers provided as name/value entries", () => { + const result = buildJoinDefaultsPayloadForAccept({ + adapterType: "openclaw", + defaultsPayload: { + headers: [{ name: "x-openclaw-auth", value: { authToken: "gateway-token" } }], + }, + }) as Record; + + expect(result).toMatchObject({ + headers: { + "x-openclaw-auth": "gateway-token", + }, + webhookAuthHeader: "Bearer gateway-token", + }); + }); + it("leaves non-openclaw payloads unchanged", () => { const defaultsPayload = { command: "echo hello" }; const result = buildJoinDefaultsPayloadForAccept({ diff --git a/server/src/routes/access.ts b/server/src/routes/access.ts index 2f2197c5..bcebe68b 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -134,7 +134,7 @@ function normalizeHeaderValue( ): string | null { const direct = nonEmptyTrimmedString(value); if (direct) return direct; - if (!isPlainObject(value) || depth >= 2) return null; + if (!isPlainObject(value) || depth >= 3) return null; const candidateKeys = [ "value", @@ -143,9 +143,16 @@ function normalizeHeaderValue( "apiKey", "api_key", "auth", + "authToken", + "auth_token", + "accessToken", + "access_token", "authorization", "bearer", "header", + "raw", + "text", + "string", ]; for (const key of candidateKeys) { if (!Object.prototype.hasOwnProperty.call(value, key)) continue; @@ -155,10 +162,56 @@ function normalizeHeaderValue( return null; } +function extractHeaderEntries(input: unknown): Array<[string, unknown]> { + if (isPlainObject(input)) { + return Object.entries(input); + } + if (!Array.isArray(input)) { + return []; + } + + const entries: Array<[string, unknown]> = []; + for (const item of input) { + if (Array.isArray(item)) { + const key = nonEmptyTrimmedString(item[0]); + if (!key) continue; + entries.push([key, item[1]]); + continue; + } + if (!isPlainObject(item)) continue; + + const mapped = item as Record; + const explicitKey = + nonEmptyTrimmedString(mapped.key) ?? + nonEmptyTrimmedString(mapped.name) ?? + nonEmptyTrimmedString(mapped.header); + if (explicitKey) { + const explicitValue = Object.prototype.hasOwnProperty.call(mapped, "value") + ? mapped.value + : Object.prototype.hasOwnProperty.call(mapped, "token") + ? mapped.token + : Object.prototype.hasOwnProperty.call(mapped, "secret") + ? mapped.secret + : mapped; + entries.push([explicitKey, explicitValue]); + continue; + } + + const singleEntry = Object.entries(mapped); + if (singleEntry.length === 1) { + entries.push(singleEntry[0] as [string, unknown]); + } + } + + return entries; +} + function normalizeHeaderMap(input: unknown): Record | undefined { - if (!isPlainObject(input)) return undefined; + const entries = extractHeaderEntries(input); + if (entries.length === 0) return undefined; + const out: Record = {}; - for (const [key, value] of Object.entries(input)) { + for (const [key, value] of entries) { const normalizedValue = normalizeHeaderValue(value); if (!normalizedValue) continue; const trimmedKey = key.trim();