fix(auth): isolate session races and financial caches

TD-001 and TD-002: serialize cookie writes, ignore stale responses and reset financial
queries on identity changes. Backend cookie lifecycle regression: ec5780d.
This commit is contained in:
2026-09-21 21:09:23 +03:00
parent 3c99debe88
commit a209d71589
4 changed files with 261 additions and 38 deletions
+14 -5
View File
@@ -8,6 +8,7 @@ const HTTP_UNAUTHORIZED = 401;
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:3000";
type AuthHandlers = {
generation?: () => number;
accessToken: () => string | null;
refresh: () => Promise<boolean>;
clear: () => void;
@@ -41,7 +42,10 @@ async function authenticatedFetch(request: Request): Promise<Response> {
const isAuthRequest = new URL(request.url).pathname.startsWith(
"/api/v1/auth/",
);
const token = auth?.accessToken();
const session = auth;
const generation = session?.generation?.();
const isCurrent = () => generation === auth?.generation?.();
const token = session?.accessToken();
const withToken = (source: Request, value: string) => {
const headers = new Headers(source.headers);
headers.set("Authorization", `Bearer ${value}`);
@@ -51,6 +55,8 @@ async function authenticatedFetch(request: Request): Promise<Response> {
token && !isAuthRequest ? withToken(request, token) : request;
const retryRequest = authorized.clone();
const response = await fetch(authorized);
if (!isAuthRequest && !isCurrent())
throw new DOMException("Session changed", "AbortError");
if (
response.status !== HTTP_UNAUTHORIZED ||
isAuthRequest ||
@@ -63,14 +69,17 @@ async function authenticatedFetch(request: Request): Promise<Response> {
)?.code !== "UNAUTHORIZED"
)
return response;
if (!isCurrent()) throw new DOMException("Session changed", "AbortError");
if (!(await auth.refresh())) {
auth.clear();
if (isCurrent()) auth.clear();
return response;
}
if (!isCurrent()) throw new DOMException("Session changed", "AbortError");
const refreshedToken = auth.accessToken();
return refreshedToken
? fetch(withToken(retryRequest, refreshedToken))
: response;
if (!refreshedToken) return response;
const retried = await fetch(withToken(retryRequest, refreshedToken));
if (!isCurrent()) throw new DOMException("Session changed", "AbortError");
return retried;
}
const apiClient = createClient<paths>({