fix(auth): fall through to env/fallback when OAuth credential has no registered provider (#2097)
Fixes #2083 When an OpenRouter API key is stored in auth.json as type:"oauth" (instead of type:"api_key"), getApiKey() calls getOAuthProvider("openrouter") which returns undefined — OpenRouter is not a registered OAuth provider. Previously, resolveCredentialApiKey returned undefined and getApiKey returned that directly, never reaching the env-var or fallback-resolver paths. Now, when resolveCredentialApiKey returns undefined, getApiKey falls through to OPENROUTER_API_KEY env var and the fallback resolver instead of silently failing with "Authentication failed." Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f4ee51017a
commit
297845f10c
2 changed files with 73 additions and 2 deletions
|
|
@ -263,6 +263,74 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────
|
||||
|
||||
describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => {
|
||||
it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async () => {
|
||||
// Simulates the bug: OpenRouter credential stored as type:"oauth"
|
||||
// but OpenRouter is not a registered OAuth provider.
|
||||
const storage = inMemory({
|
||||
openrouter: {
|
||||
type: "oauth",
|
||||
access_token: "sk-or-v1-fake",
|
||||
refresh_token: "rt-fake",
|
||||
expires: Date.now() + 3_600_000,
|
||||
},
|
||||
});
|
||||
|
||||
// Before the fix, getApiKey returns undefined because
|
||||
// resolveCredentialApiKey calls getOAuthProvider("openrouter") → null → undefined.
|
||||
// The key in the oauth credential is never extracted.
|
||||
const key = await storage.getApiKey("openrouter");
|
||||
// After the fix, the oauth credential with an unrecognised provider
|
||||
// should be skipped, and getApiKey should fall through to env / fallback.
|
||||
assert.equal(key, undefined);
|
||||
});
|
||||
|
||||
it("falls through to env var when openrouter has type:oauth credential", async () => {
|
||||
const storage = inMemory({
|
||||
openrouter: {
|
||||
type: "oauth",
|
||||
access_token: "sk-or-v1-fake",
|
||||
refresh_token: "rt-fake",
|
||||
expires: Date.now() + 3_600_000,
|
||||
},
|
||||
});
|
||||
|
||||
// Simulate OPENROUTER_API_KEY being set via env
|
||||
const origEnv = process.env.OPENROUTER_API_KEY;
|
||||
try {
|
||||
process.env.OPENROUTER_API_KEY = "sk-or-v1-env-key";
|
||||
const key = await storage.getApiKey("openrouter");
|
||||
assert.equal(key, "sk-or-v1-env-key");
|
||||
} finally {
|
||||
if (origEnv === undefined) {
|
||||
delete process.env.OPENROUTER_API_KEY;
|
||||
} else {
|
||||
process.env.OPENROUTER_API_KEY = origEnv;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
|
||||
const storage = inMemory({
|
||||
openrouter: {
|
||||
type: "oauth",
|
||||
access_token: "sk-or-v1-fake",
|
||||
refresh_token: "rt-fake",
|
||||
expires: Date.now() + 3_600_000,
|
||||
},
|
||||
});
|
||||
|
||||
storage.setFallbackResolver((provider) =>
|
||||
provider === "openrouter" ? "sk-or-v1-fallback" : undefined,
|
||||
);
|
||||
|
||||
const key = await storage.getApiKey("openrouter");
|
||||
assert.equal(key, "sk-or-v1-fallback");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getAll truncation ────────────────────────────────────────────────────────
|
||||
|
||||
describe("AuthStorage — getAll()", () => {
|
||||
|
|
|
|||
|
|
@ -756,9 +756,12 @@ export class AuthStorage {
|
|||
if (credentials.length > 0) {
|
||||
const index = this.selectCredentialIndex(providerId, credentials, sessionId);
|
||||
if (index >= 0) {
|
||||
return this.resolveCredentialApiKey(providerId, credentials[index]);
|
||||
const resolved = await this.resolveCredentialApiKey(providerId, credentials[index]);
|
||||
if (resolved) return resolved;
|
||||
// Credential unresolvable (e.g. type:"oauth" for a non-OAuth provider) —
|
||||
// fall through to env / fallback instead of returning undefined (#2083)
|
||||
}
|
||||
// All credentials backed off - fall through to env/fallback
|
||||
// All credentials backed off or unresolvable - fall through to env/fallback
|
||||
}
|
||||
|
||||
// Fall back to environment variable
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue