mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-08-19 13:33:23 +00:00
fix(kimi): harden peer-rotation adoption per review
Address review feedback: keep KimiRefreshAuthError internal to the module, and require an access token on the adopted credential so a partially written file cannot mask the real refresh failure.
This commit is contained in:
parent
1ea0c960a7
commit
c2e478691a
2 changed files with 44 additions and 2 deletions
|
|
@ -54,7 +54,7 @@ type KimiConfiguredProvider = {
|
|||
sourceFile: string;
|
||||
};
|
||||
|
||||
export class KimiRefreshAuthError extends Error {
|
||||
class KimiRefreshAuthError extends Error {
|
||||
readonly status: number;
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
|
|
@ -224,7 +224,7 @@ function adoptPeerRotatedKimiAuth(auth: KimiTokenSet, error: unknown): KimiToken
|
|||
// is stale and the server rejects it, but the file on disk already holds
|
||||
// the newer credential. Adopt it instead of failing the request.
|
||||
const latest = readKimiAuthFromFile(auth.sourceFile, auth.oauthHost);
|
||||
if (latest?.refreshToken && latest.refreshToken !== auth.refreshToken) {
|
||||
if (latest?.refreshToken && latest.accessToken && latest.refreshToken !== auth.refreshToken) {
|
||||
return latest;
|
||||
}
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -221,6 +221,48 @@ oauth = { storage = "file", key = "oauth/kimi-code", oauth_host = "http://127.0.
|
|||
});
|
||||
});
|
||||
|
||||
test("Kimi CLI OAuth does not adopt a rotated credential without an access token", async (t) => {
|
||||
await withKimiHome(async (kimiHome) => {
|
||||
writeFileSync(path.join(kimiHome, "config.toml"), `
|
||||
[providers."managed:kimi-code"]
|
||||
type = "kimi"
|
||||
base_url = "https://api.kimi.com/coding/v1"
|
||||
oauth = { storage = "file", key = "oauth/kimi-code", oauth_host = "http://127.0.0.1" }
|
||||
`);
|
||||
const credentialsDir = path.join(kimiHome, "credentials");
|
||||
mkdirSync(credentialsDir, { recursive: true });
|
||||
const credentialFile = path.join(credentialsDir, "kimi-code.json");
|
||||
writeFileSync(credentialFile, JSON.stringify({
|
||||
access_token: "expired-token",
|
||||
expires_at: 1,
|
||||
expires_in: 3600,
|
||||
refresh_token: "stale-refresh-token"
|
||||
}));
|
||||
|
||||
const previousFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => {
|
||||
// Partial write: a new refresh token landed without an access token.
|
||||
writeFileSync(credentialFile, JSON.stringify({
|
||||
expires_at: 1,
|
||||
expires_in: 7200,
|
||||
refresh_token: "peer-refresh-token"
|
||||
}));
|
||||
return new Response(JSON.stringify({ error: "invalid_grant" }), {
|
||||
headers: { "content-type": "application/json" },
|
||||
status: 401
|
||||
});
|
||||
};
|
||||
t.after(() => {
|
||||
globalThis.fetch = previousFetch;
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => resolveKimiAuth({ key: "oauth/kimi-code", oauthHost: "http://127.0.0.1" }),
|
||||
/HTTP 401/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function withKimiHome(run) {
|
||||
const kimiHome = mkdtempSync(path.join(os.tmpdir(), "ccr-kimi-provider-"));
|
||||
const previous = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue