From fa43e5b0ddecdcd3440f401a80fb154627778609 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 6 Mar 2026 12:06:08 -0600 Subject: [PATCH] Accept OpenClaw auth headers in more payload formats --- .../invite-accept-openclaw-defaults.test.ts | 40 +++++++++++++++++++ server/src/routes/access.ts | 32 ++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/invite-accept-openclaw-defaults.test.ts b/server/src/__tests__/invite-accept-openclaw-defaults.test.ts index a8c70389..f206f2a8 100644 --- a/server/src/__tests__/invite-accept-openclaw-defaults.test.ts +++ b/server/src/__tests__/invite-accept-openclaw-defaults.test.ts @@ -70,6 +70,46 @@ describe("buildJoinDefaultsPayloadForAccept", () => { }); }); + it("accepts auth from agentDefaultsPayload.headers.x-openclaw-auth", () => { + const result = buildJoinDefaultsPayloadForAccept({ + adapterType: "openclaw", + defaultsPayload: { + url: "http://127.0.0.1:18789/v1/responses", + method: "POST", + headers: { + "x-openclaw-auth": "gateway-token", + }, + }, + }) as Record; + + expect(result).toMatchObject({ + headers: { + "x-openclaw-auth": "gateway-token", + }, + webhookAuthHeader: "Bearer gateway-token", + }); + }); + + it("accepts wrapped auth values in headers for compatibility", () => { + const result = buildJoinDefaultsPayloadForAccept({ + adapterType: "openclaw", + defaultsPayload: { + headers: { + "x-openclaw-auth": { + value: "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 4e1d9a22..2f2197c5 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -128,13 +128,41 @@ function normalizeHostname(value: string | null | undefined): string | null { return trimmed.toLowerCase(); } +function normalizeHeaderValue( + value: unknown, + depth: number = 0, +): string | null { + const direct = nonEmptyTrimmedString(value); + if (direct) return direct; + if (!isPlainObject(value) || depth >= 2) return null; + + const candidateKeys = [ + "value", + "token", + "secret", + "apiKey", + "api_key", + "auth", + "authorization", + "bearer", + "header", + ]; + for (const key of candidateKeys) { + if (!Object.prototype.hasOwnProperty.call(value, key)) continue; + const normalized = normalizeHeaderValue((value as Record)[key], depth + 1); + if (normalized) return normalized; + } + return null; +} + function normalizeHeaderMap(input: unknown): Record | undefined { if (!isPlainObject(input)) return undefined; const out: Record = {}; for (const [key, value] of Object.entries(input)) { - if (typeof value !== "string") continue; + const normalizedValue = normalizeHeaderValue(value); + if (!normalizedValue) continue; const trimmedKey = key.trim(); - const trimmedValue = value.trim(); + const trimmedValue = normalizedValue.trim(); if (!trimmedKey || !trimmedValue) continue; out[trimmedKey] = trimmedValue; }