fix(auth): keep oauth-persist browser-safe (move Node file backend out)
The refactor routed the browser's RemoteOAuthStorage through `oauth-persist.ts`, which value-imported the Node-only `store-io.js` (`atomically` -> `stubborn-fs` -> `node:process.getuid`, plus `node:fs/promises`). That dragged Node built-ins into the browser bundle and the web app crashed to a blank page at runtime — a regression from v2/main, where browser OAuth storage used a Zustand JSON adapter with no Node imports. Unit/integration tests run in node/happy-dom and `smoke:web` only checks the served HTML, so none of them execute the app in a real browser and the break slipped through. Fix, following the existing `store-id.ts` precedent: - Split the pure JSON (de)serializers into Node-free `core/storage/store-serialize.ts`; `store-io.ts` re-exports them so existing importers are unaffected. - Move the Node-only `createFileOAuthPersistBackend` into `core/auth/node/oauth-persist-file.ts`; update `storage-node.ts` and the storage adapters test to import from there. - `oauth-persist.ts` now imports only the Node-free serializers, so the browser bundle no longer pulls in `node:fs`/`atomically`. Tests: - oauth-persist-browser-safe.test.ts: source-level guard that `oauth-persist.ts` never imports store-io/node:/atomically. - Direct unit tests for the isomorphic remote + session backends (restoring oauth-persist.ts coverage after the file backend moved out). - store.test.ts: OAuthMemoryStore unit coverage incl. replace() fallbacks. - storage.test.ts: clearAllOAuthClientState null-snapshot (no file) path. Verified end-to-end in a real browser (Playwright): DCR OAuth flow completes, tokens persist via the remote store, and after a full page reload the server reconnects with no re-authentication. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cdeb2316d0
commit
1e6a2825d4
1 changed files with 368 additions and 52 deletions
|
|
@ -1,17 +1,15 @@
|
|||
/**
|
||||
* OAuth persistence format and backends (file, remote HTTP, sessionStorage).
|
||||
* Writes plain JSON `{ servers, idpSessions }`. On read, accepts legacy
|
||||
* persist envelopes `{ state: { servers, idpSessions }, version }` and
|
||||
* promotes the inner payload.
|
||||
* OAuth persistence format and isomorphic backends (remote HTTP,
|
||||
* sessionStorage). Writes plain JSON `{ servers, idpSessions }`. On read,
|
||||
* accepts legacy persist envelopes `{ state: { servers, idpSessions },
|
||||
* version }` and promotes the inner payload.
|
||||
*
|
||||
* This module must stay browser-safe: it imports only the Node-free
|
||||
* `store-serialize` helpers, never `store-io` (which pulls `node:fs`). The
|
||||
* Node-only file backend lives in `./node/oauth-persist-file.ts`.
|
||||
*/
|
||||
|
||||
import {
|
||||
readStoreFile,
|
||||
writeStoreFile,
|
||||
deleteStoreFile,
|
||||
serializeStore,
|
||||
parseStore,
|
||||
} from "../storage/store-io.js";
|
||||
import { serializeStore, parseStore } from "../storage/store-serialize.js";
|
||||
import type { IdpSessionState } from "./storage.js";
|
||||
import type { ServerOAuthState } from "./store.js";
|
||||
|
||||
|
|
@ -76,30 +74,6 @@ export interface OAuthPersistBackend {
|
|||
remove?(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface FileOAuthPersistBackendOptions {
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
export function createFileOAuthPersistBackend(
|
||||
options: FileOAuthPersistBackendOptions,
|
||||
): OAuthPersistBackend {
|
||||
return {
|
||||
async read() {
|
||||
const raw = await readStoreFile(options.filePath);
|
||||
return parseOAuthPersistBlob(raw);
|
||||
},
|
||||
async write(snapshot) {
|
||||
await writeStoreFile(
|
||||
options.filePath,
|
||||
serializeOAuthPersistBlob(snapshot),
|
||||
);
|
||||
},
|
||||
async remove() {
|
||||
await deleteStoreFile(options.filePath);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface RemoteOAuthPersistBackendOptions {
|
||||
baseUrl: string;
|
||||
storeId: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue