M15: synchronize contracts and acceptance

This commit is contained in:
NuklearRabbit
2026-08-10 05:39:19 +02:00
parent 58fb515337
commit 2ee8b2d82b
38 changed files with 5091 additions and 1727 deletions
+20 -5
View File
@@ -1,5 +1,5 @@
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from "react";
import { api, onUnauthorized } from "../api/client";
import { api, ApiError, onUnauthorized } from "../api/client";
import type { CurrentUser, Role, SystemStatus } from "../api/types";
interface AuthState {
@@ -93,13 +93,28 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, []);
const logout = useCallback(async () => {
setUser(null);
cacheUser(null);
try {
await api.post("/api/v1/auth/logout");
// A logout response expires an HttpOnly cookie. Chromium can expose a very small
// race between resolving fetch() and applying that Set-Cookie header to an
// immediate top-level navigation. Confirm the server now rejects the session
// before allowing the router to continue; retrying logout is idempotent.
for (let attempt = 0; attempt < 3; attempt += 1) {
await api.post("/api/v1/auth/logout");
try {
await api.get<CurrentUser>("/api/v1/auth/session");
} catch (err) {
if (err instanceof ApiError && err.status === 401) return;
throw err;
}
}
} catch {
// Best effort: the cookie is cleared server-side when it works, and the client has
// already dropped its own state either way.
// still drops its own state when the request itself is unavailable.
} finally {
// Keep RequireAuth from redirecting to /login while the server-side invalidation
// is still in flight. Otherwise a very fast navigation can race ahead of logout.
setUser(null);
cacheUser(null);
}
}, []);