diff --git a/bun.lock b/bun.lock index 6bf9f830c..1f81722f2 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,7 @@ "@corbits/codex-provider": "0.1.1", "@corbits/oauth-core": "0.2.0", "@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses", - "@corbits/xai-provider": "github:corbitsdev/corbits-xai-provider", + "@corbits/xai-provider": "0.1.1", "@intx/agent": "workspace:*", "@intx/authz": "workspace:*", "@intx/crypto": "0.3.0", @@ -265,7 +265,7 @@ "@corbits/workflows-stub": ["@corbits/workflows-stub@workspace:workflows/_stub"], - "@corbits/xai-provider": ["@corbits/xai-provider@github:corbitsdev/corbits-xai-provider#cadf9b9", { "dependencies": { "@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core", "@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses", "arktype": "2.2.3" }, "peerDependencies": { "@intx/inference": ">=0.3.0", "@intx/types": ">=0.3.0" } }, "corbitsdev-corbits-xai-provider-cadf9b9", "sha512-dVY4lQJWdCVPxwnK4pQNc3JgfgASaNj/GACKTnR22Y/s6CKSwlcZyGzZ0QEjIr21SZRk4+QP6ehOBf/OtHSJRw=="], + "@corbits/xai-provider": ["@corbits/xai-provider@0.1.1", "", { "dependencies": { "arktype": "^2.2.3" }, "peerDependencies": { "@corbits/oauth-core": "^0.2.0", "@corbits/openai-responses": "^0.2.0", "@intx/inference": "^0.4.0", "@intx/types": "^0.4.0" } }, "sha512-VbXdu5M300oQYQnP4Q8F0z6/Rm9kYTnIkBfo9svGlSus4rFRqEOArNhncKgFvu3Nwf9Qgooov3i8OU8gQCo/TQ=="], "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], diff --git a/package.json b/package.json index ad75e9939..718ef3fb2 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@corbits/codex-provider": "0.1.1", "@corbits/oauth-core": "0.2.0", "@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses", - "@corbits/xai-provider": "github:corbitsdev/corbits-xai-provider", + "@corbits/xai-provider": "0.1.1", "@intx/agent": "workspace:*", "@intx/authz": "workspace:*", "@intx/crypto": "0.3.0", diff --git a/src/auth/xai/auth-headers.test.ts b/src/auth/xai/auth-headers.test.ts new file mode 100644 index 000000000..8c9e3ca29 --- /dev/null +++ b/src/auth/xai/auth-headers.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from "bun:test"; + +import { xaiAuthHeadersForToken } from "./auth-headers.js"; + +function accessTokenWithSub(sub: string): string { + const segment = (obj: Record): string => + Buffer.from(JSON.stringify(obj), "utf8").toString("base64url"); + return `${segment({ alg: "none" })}.${segment({ sub })}.sig`; +} + +describe("xaiAuthHeadersForToken", () => { + test("lifts a clean JWT sub into x-grok-user-id", () => { + const headers = xaiAuthHeadersForToken({ + access: accessTokenWithSub("user-123"), + }); + expect(headers["x-grok-user-id"]).toBe("user-123"); + expect(headers.authorization).toBe( + `Bearer ${accessTokenWithSub("user-123")}`, + ); + }); + + test("omits x-grok-user-id when the JWT sub carries CR, LF, or NUL", () => { + for (const sub of ["user\r123", "user\n123", "user\u0000123"]) { + const headers = xaiAuthHeadersForToken({ + access: accessTokenWithSub(sub), + }); + expect("x-grok-user-id" in headers).toBe(false); + } + }); +});