fix(ai): applied broker refresh result to suspect credential snapshot
- Updated markCredentialSuspect to apply the credential entry returned from refreshCredential and throw when the broker returns a non-OAuth credential. - Added #applyCredentialEntry to replace or append a credential in the in-memory snapshot for immediate state updates. - Added a test covering suspect-credential refresh snapshot updates and token refresh invocation.
This commit is contained in:
@@ -191,8 +191,14 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
||||
}
|
||||
|
||||
async markCredentialSuspect(credentialId: number, opts: { signal?: AbortSignal } = {}): Promise<void> {
|
||||
await this.#client.refreshCredential(credentialId, opts.signal);
|
||||
await this.waitForFreshSnapshot(MAX_WAIT_MS, opts);
|
||||
const { entry } = await this.#client.refreshCredential(credentialId, opts.signal);
|
||||
if (entry.credential.type !== "oauth") {
|
||||
throw new Error(`Broker returned non-OAuth credential for id=${credentialId}`);
|
||||
}
|
||||
this.#applyCredentialEntry(entry);
|
||||
void this.refreshSnapshot().catch(error => {
|
||||
logger.debug("auth-broker snapshot refresh after suspect credential refresh failed", { error: String(error) });
|
||||
});
|
||||
}
|
||||
|
||||
replaceAuthCredentialsForProvider(_provider: string, _credentials: AuthCredential[]): StoredAuthCredential[] {
|
||||
@@ -295,6 +301,17 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
||||
const incoming = entries.map(entry => ({ ...entry, rotatesInMs: null }));
|
||||
this.#snapshot = { ...this.#snapshot, credentials: [...others, ...incoming] };
|
||||
}
|
||||
#applyCredentialEntry(entry: AuthCredentialSnapshotEntry): void {
|
||||
const incoming = { ...entry, rotatesInMs: null };
|
||||
const index = this.#snapshot.credentials.findIndex(candidate => candidate.id === entry.id);
|
||||
if (index === -1) {
|
||||
this.#snapshot = { ...this.#snapshot, credentials: [...this.#snapshot.credentials, incoming] };
|
||||
return;
|
||||
}
|
||||
const credentials = [...this.#snapshot.credentials];
|
||||
credentials[index] = incoming;
|
||||
this.#snapshot = { ...this.#snapshot, credentials };
|
||||
}
|
||||
|
||||
#removeProviderEntries(provider: string): void {
|
||||
const next = this.#snapshot.credentials.filter(entry => entry.provider !== provider);
|
||||
|
||||
@@ -105,6 +105,39 @@ describe("RemoteAuthCredentialStore + AuthStorage integration", () => {
|
||||
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
||||
clientStorage.close();
|
||||
});
|
||||
test("suspect credential refresh updates the client snapshot from the broker response", async () => {
|
||||
const rotated = {
|
||||
access: "server-access-after-401",
|
||||
refresh: "server-refresh-after-401",
|
||||
expires: Date.now() + 120_000,
|
||||
accountId: "account-1",
|
||||
email: "a@example.com",
|
||||
};
|
||||
const refreshSpy = vi.spyOn(oauthUtils, "refreshOAuthToken").mockResolvedValue(rotated);
|
||||
|
||||
const brokerClient = new AuthBrokerClient({ url: handle!.url, token });
|
||||
const initialResult = await brokerClient.fetchSnapshot();
|
||||
if (initialResult.status !== 200) throw new Error("expected snapshot");
|
||||
const initialEntry = initialResult.snapshot.credentials[0];
|
||||
if (!initialEntry) throw new Error("expected credential");
|
||||
|
||||
const remoteStore = new RemoteAuthCredentialStore({
|
||||
client: brokerClient,
|
||||
initialSnapshot: initialResult.snapshot,
|
||||
});
|
||||
|
||||
await remoteStore.markCredentialSuspect(initialEntry.id);
|
||||
const rows = remoteStore.listAuthCredentials("anthropic");
|
||||
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]?.credential.type).toBe("oauth");
|
||||
if (rows[0]?.credential.type === "oauth") {
|
||||
expect(rows[0].credential.access).toBe("server-access-after-401");
|
||||
expect(rows[0].credential.refresh).toBe(REMOTE_REFRESH_SENTINEL);
|
||||
}
|
||||
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
||||
remoteStore.close();
|
||||
});
|
||||
|
||||
test("RemoteAuthCredentialStore rejects writes from the client", () => {
|
||||
const remoteStore = new RemoteAuthCredentialStore({
|
||||
|
||||
Reference in New Issue
Block a user