fix(auth): address Claude review on OAuth persist serialization
Serialize persist() in OAuthStorageBase so overlapping remote POSTs cannot reorder and clobber state. Regenerate launcher package-lock.json to drop stale zustand entries. Let parseOAuthPersistBlob accept parsed objects from res.json() without a redundant JSON round-trip.
This commit is contained in:
parent
3c65bdb4c6
commit
af174666d6
4 changed files with 31 additions and 993 deletions
988
clients/launcher/package-lock.json
generated
988
clients/launcher/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,16 @@ describe("parseOAuthPersistBlob", () => {
|
|||
expect(parseOAuthPersistBlob(JSON.stringify(snapshot))).toEqual(snapshot);
|
||||
});
|
||||
|
||||
it("accepts an already-parsed object without re-serializing", () => {
|
||||
const snapshot = {
|
||||
servers: {
|
||||
"http://example.com": { codeVerifier: "v1" },
|
||||
},
|
||||
idpSessions: {},
|
||||
};
|
||||
expect(parseOAuthPersistBlob(snapshot)).toEqual(snapshot);
|
||||
});
|
||||
|
||||
it("promotes legacy persist envelope state to the top level", () => {
|
||||
const legacy = {
|
||||
state: {
|
||||
|
|
|
|||
|
|
@ -36,14 +36,22 @@ function snapshotFromPayload(payload: Partial<OAuthPersistSnapshot>): OAuthPersi
|
|||
/**
|
||||
* Parse OAuth store JSON from disk, remote API, or sessionStorage.
|
||||
* Accepts plain `{ servers, idpSessions }` or legacy `{ state, version }`.
|
||||
* `raw` may be a JSON string or an already-parsed object (e.g. from `res.json()`).
|
||||
*/
|
||||
export function parseOAuthPersistBlob(
|
||||
raw: string | null,
|
||||
raw: string | null | unknown,
|
||||
): OAuthPersistSnapshot | null {
|
||||
if (!raw) {
|
||||
if (raw === null || raw === undefined) {
|
||||
return null;
|
||||
}
|
||||
const parsed = parseStore(raw);
|
||||
|
||||
const parsed =
|
||||
typeof raw === "string"
|
||||
? raw
|
||||
? parseStore(raw)
|
||||
: null
|
||||
: raw;
|
||||
|
||||
if (!isRecord(parsed)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -131,7 +139,7 @@ export function createRemoteOAuthPersistBackend(
|
|||
if (Object.keys(store as object).length === 0) {
|
||||
return null;
|
||||
}
|
||||
return parseOAuthPersistBlob(JSON.stringify(store));
|
||||
return parseOAuthPersistBlob(store);
|
||||
},
|
||||
async write(snapshot) {
|
||||
const headers: Record<string, string> = {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import type {
|
|||
export class OAuthStorageBase implements OAuthStorage {
|
||||
private loaded = false;
|
||||
private loadPromise: Promise<void> | undefined;
|
||||
/** Serializes persist writes so concurrent mutators cannot reorder POSTs. */
|
||||
private persistQueue: Promise<void> = Promise.resolve();
|
||||
private readonly memory: OAuthMemoryStore;
|
||||
private readonly backend: OAuthPersistBackend;
|
||||
|
||||
|
|
@ -55,7 +57,13 @@ export class OAuthStorageBase implements OAuthStorage {
|
|||
}
|
||||
|
||||
private async persist(): Promise<void> {
|
||||
await this.backend.write(this.memory.snapshot());
|
||||
const snapshot = this.memory.snapshot();
|
||||
const prior = this.persistQueue;
|
||||
const tracked = prior
|
||||
.catch(() => {})
|
||||
.then(() => this.backend.write(snapshot));
|
||||
this.persistQueue = tracked;
|
||||
await tracked;
|
||||
}
|
||||
|
||||
async getClientInformation(
|
||||
|
|
|
|||
Loading…
Reference in a new issue