fix(auth): route clearClientInformation through clearAfterHydration too
Round-2 @claude review of #1592 caught a seventh member of the clear family that the first pass missed: clearClientInformation still wrote to the store synchronously, carrying the same pre-hydration clobber risk the clearAfterHydration helper was written for (a clear before hydration lands persists a near-empty blob, overwriting the whole on-disk oauth.json). Wrap its mutation the same way and add a clear-before-hydration test proving a sibling token (and thus every other server's blob) survives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3fTN8H3R8YV4yUGvZjYnX
This commit is contained in:
parent
8c10b8eb8e
commit
4e149719bd
2 changed files with 32 additions and 10 deletions
|
|
@ -172,6 +172,28 @@ describe("OAuthStorageBase — async hydration", () => {
|
||||||
expect(await storage.getCodeVerifier(SERVER)).toBe("keep-me");
|
expect(await storage.getCodeVerifier(SERVER)).toBe("keep-me");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("clearClientInformation before hydration is deferred and clears only client info", async () => {
|
||||||
|
const { storage, release } = makeAsyncBackedStorage({
|
||||||
|
[SERVER]: {
|
||||||
|
clientInformation: { client_id: "abc" },
|
||||||
|
clientRegistrationKind: "dynamic",
|
||||||
|
tokens: { access_token: "t", token_type: "Bearer" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
storage.clearClientInformation(SERVER);
|
||||||
|
release();
|
||||||
|
await storage.ready();
|
||||||
|
await Promise.resolve();
|
||||||
|
// Client info cleared, but the sibling token from the persisted blob (and
|
||||||
|
// every other server's blob, in the whole-blob adapter) survives — proving
|
||||||
|
// the pre-hydration clear did not clobber on-disk state.
|
||||||
|
expect(await storage.getClientInformation(SERVER)).toBeUndefined();
|
||||||
|
expect(await storage.getTokens(SERVER)).toEqual({
|
||||||
|
access_token: "t",
|
||||||
|
token_type: "Bearer",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("clear() after hydration does not schedule a deferred re-apply", async () => {
|
it("clear() after hydration does not schedule a deferred re-apply", async () => {
|
||||||
const { storage, release } = makeAsyncBackedStorage({
|
const { storage, release } = makeAsyncBackedStorage({
|
||||||
[SERVER]: { tokens: { access_token: "t", token_type: "Bearer" } },
|
[SERVER]: { tokens: { access_token: "t", token_type: "Bearer" } },
|
||||||
|
|
|
||||||
|
|
@ -172,16 +172,16 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
clearClientInformation(serverUrl: string, isPreregistered?: boolean): void {
|
clearClientInformation(serverUrl: string, isPreregistered?: boolean): void {
|
||||||
const updates: Partial<ServerOAuthState> = {};
|
this.clearAfterHydration(() => {
|
||||||
|
const updates: Partial<ServerOAuthState> = {};
|
||||||
if (isPreregistered) {
|
if (isPreregistered) {
|
||||||
updates.preregisteredClientInformation = undefined;
|
updates.preregisteredClientInformation = undefined;
|
||||||
} else {
|
} else {
|
||||||
updates.clientInformation = undefined;
|
updates.clientInformation = undefined;
|
||||||
updates.clientRegistrationKind = undefined;
|
updates.clientRegistrationKind = undefined;
|
||||||
}
|
}
|
||||||
|
this.store.getState().setServerState(serverUrl, updates);
|
||||||
this.store.getState().setServerState(serverUrl, updates);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTokens(serverUrl: string): Promise<OAuthTokens | undefined> {
|
async getTokens(serverUrl: string): Promise<OAuthTokens | undefined> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue