mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 01:51:21 +08:00
175 lines
5.6 KiB
TypeScript
175 lines
5.6 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import {
|
|
parseJwtClaims,
|
|
extractAccountIdFromClaims,
|
|
extractAccountId,
|
|
CodexAuthPlugin,
|
|
type IdTokenClaims,
|
|
} from "../../src/plugin/codex"
|
|
import type { PluginInput } from "@kilocode/plugin"
|
|
|
|
function createTestJwt(payload: object): string {
|
|
const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url")
|
|
const body = Buffer.from(JSON.stringify(payload)).toString("base64url")
|
|
return `${header}.${body}.sig`
|
|
}
|
|
|
|
describe("plugin.codex", () => {
|
|
test("identifies refresh requests as Kilo", async () => {
|
|
const original = globalThis.fetch
|
|
const seen: Request[] = []
|
|
let auth = {
|
|
type: "oauth" as const,
|
|
access: "old-access",
|
|
refresh: "old-refresh",
|
|
expires: 0,
|
|
}
|
|
const input = {
|
|
client: {
|
|
auth: {
|
|
set: async (req: { body: typeof auth }) => {
|
|
auth = req.body
|
|
},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
globalThis.fetch = Object.assign(
|
|
async (...args: Parameters<typeof globalThis.fetch>) => {
|
|
const req = new Request(...args)
|
|
seen.push(req)
|
|
if (req.url === "https://auth.openai.com/oauth/token") {
|
|
return Response.json({
|
|
id_token: "",
|
|
access_token: "next-access",
|
|
refresh_token: "next-refresh",
|
|
expires_in: 60,
|
|
})
|
|
}
|
|
return new Response("", { status: 200 })
|
|
},
|
|
{ preconnect: original.preconnect },
|
|
)
|
|
|
|
try {
|
|
const plugin = await CodexAuthPlugin(input)
|
|
const loaded = await plugin.auth!.loader!(async () => auth, {} as never)
|
|
await loaded.fetch("https://api.openai.com/v1/responses")
|
|
} finally {
|
|
globalThis.fetch = original
|
|
}
|
|
|
|
const refresh = seen[0]
|
|
expect(refresh.url).toBe("https://auth.openai.com/oauth/token")
|
|
expect(refresh.headers.get("user-agent")).toMatch(/^kilo\//)
|
|
expect(await refresh.text()).toContain("refresh_token=old-refresh")
|
|
})
|
|
|
|
describe("parseJwtClaims", () => {
|
|
test("parses valid JWT with claims", () => {
|
|
const payload = { email: "test@example.com", chatgpt_account_id: "acc-123" }
|
|
const jwt = createTestJwt(payload)
|
|
const claims = parseJwtClaims(jwt)
|
|
expect(claims).toEqual(payload)
|
|
})
|
|
|
|
test("returns undefined for JWT with less than 3 parts", () => {
|
|
expect(parseJwtClaims("invalid")).toBeUndefined()
|
|
expect(parseJwtClaims("only.two")).toBeUndefined()
|
|
})
|
|
|
|
test("returns undefined for invalid base64", () => {
|
|
expect(parseJwtClaims("a.!!!invalid!!!.b")).toBeUndefined()
|
|
})
|
|
|
|
test("returns undefined for invalid JSON payload", () => {
|
|
const header = Buffer.from("{}").toString("base64url")
|
|
const invalidJson = Buffer.from("not json").toString("base64url")
|
|
expect(parseJwtClaims(`${header}.${invalidJson}.sig`)).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("extractAccountIdFromClaims", () => {
|
|
test("extracts chatgpt_account_id from root", () => {
|
|
const claims: IdTokenClaims = { chatgpt_account_id: "acc-root" }
|
|
expect(extractAccountIdFromClaims(claims)).toBe("acc-root")
|
|
})
|
|
|
|
test("extracts chatgpt_account_id from nested https://api.openai.com/auth", () => {
|
|
const claims: IdTokenClaims = {
|
|
"https://api.openai.com/auth": { chatgpt_account_id: "acc-nested" },
|
|
}
|
|
expect(extractAccountIdFromClaims(claims)).toBe("acc-nested")
|
|
})
|
|
|
|
test("prefers root over nested", () => {
|
|
const claims: IdTokenClaims = {
|
|
chatgpt_account_id: "acc-root",
|
|
"https://api.openai.com/auth": { chatgpt_account_id: "acc-nested" },
|
|
}
|
|
expect(extractAccountIdFromClaims(claims)).toBe("acc-root")
|
|
})
|
|
|
|
test("extracts from organizations array as fallback", () => {
|
|
const claims: IdTokenClaims = {
|
|
organizations: [{ id: "org-123" }, { id: "org-456" }],
|
|
}
|
|
expect(extractAccountIdFromClaims(claims)).toBe("org-123")
|
|
})
|
|
|
|
test("returns undefined when no accountId found", () => {
|
|
const claims: IdTokenClaims = { email: "test@example.com" }
|
|
expect(extractAccountIdFromClaims(claims)).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("extractAccountId", () => {
|
|
test("extracts from id_token first", () => {
|
|
const idToken = createTestJwt({ chatgpt_account_id: "from-id-token" })
|
|
const accessToken = createTestJwt({ chatgpt_account_id: "from-access-token" })
|
|
expect(
|
|
extractAccountId({
|
|
id_token: idToken,
|
|
access_token: accessToken,
|
|
refresh_token: "rt",
|
|
}),
|
|
).toBe("from-id-token")
|
|
})
|
|
|
|
test("falls back to access_token when id_token has no accountId", () => {
|
|
const idToken = createTestJwt({ email: "test@example.com" })
|
|
const accessToken = createTestJwt({
|
|
"https://api.openai.com/auth": { chatgpt_account_id: "from-access" },
|
|
})
|
|
expect(
|
|
extractAccountId({
|
|
id_token: idToken,
|
|
access_token: accessToken,
|
|
refresh_token: "rt",
|
|
}),
|
|
).toBe("from-access")
|
|
})
|
|
|
|
test("returns undefined when no tokens have accountId", () => {
|
|
const token = createTestJwt({ email: "test@example.com" })
|
|
expect(
|
|
extractAccountId({
|
|
id_token: token,
|
|
access_token: token,
|
|
refresh_token: "rt",
|
|
}),
|
|
).toBeUndefined()
|
|
})
|
|
|
|
test("handles missing id_token", () => {
|
|
const accessToken = createTestJwt({ chatgpt_account_id: "acc-123" })
|
|
expect(
|
|
extractAccountId({
|
|
id_token: "",
|
|
access_token: accessToken,
|
|
refresh_token: "rt",
|
|
}),
|
|
).toBe("acc-123")
|
|
})
|
|
})
|
|
})
|