fix(auth): end the session when a refetch of the account says 401

A failed refetch keeps the last good payload in the query cache, so a
session that expired while the app was open still read as signed in:
the shell kept its pages, and every save failed with "sign in again".
A 401 or 403 now ends the session even with a cached account; a network
failure or a 5xx still holds the last known state.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 21:37:25 +01:00
1 parent a03782f93f
commit b89599d66a
2 files changed
+31 -4

No files matched your search

@@ -136,6 +136,30 @@ describe("useAuth — last-known state on a non-verdict failure", () => {
});
});
describe("useAuth — a verdict on a refetch", () => {
it("ends the session on a 401 although the cache still holds the account", () => {
store.set("healthlog-was-authenticated", "1");
queryState.data = { id: "u1", username: "someone" };
queryState.isError = true;
queryState.error = new ApiError("unauthorized", 401);
const auth = useAuth();
expect(auth.isAuthenticated).toBe(false);
expect(auth.user).toBeNull();
expect(auth.isAuthUnknown).toBe(false);
});
it("keeps the cached account through a network failure on a refetch", () => {
queryState.data = { id: "u1", username: "someone" };
queryState.isError = true;
queryState.error = new TypeError("Failed to fetch");
const auth = useAuth();
expect(auth.isAuthenticated).toBe(true);
expect(auth.user).toEqual({ id: "u1", username: "someone" });
});
});
describe("session-end marker lifecycle", () => {
it("clearCachesForSessionEnd drops the marker so the next cold offline launch lands on the login gate", () => {
store.set("healthlog-was-authenticated", "1");
+7 -4
View File
@@ -536,11 +536,14 @@ export function useAuth() {
// /auth/login and wiped every offline cache (data 7→0 entries, pages 1→0
// in the audit measurement), making the whole offline story unreachable.
const verdictUnknown = query.isError && isAuthVerdictUnknown(query.error);
const isAuthenticated =
!!query.data || (verdictUnknown && readWasAuthenticated());
// A 401/403 on a refetch ends the session although the cache still holds
// the last good payload: React Query keeps `data` when a refetch fails.
const sessionEnded = query.isError && !verdictUnknown;
const user = sessionEnded ? null : (query.data ?? null);
const isAuthenticated = !!user || (verdictUnknown && readWasAuthenticated());
return {
user: query.data ?? null,
user,
isLoading: query.isLoading,
isAuthenticated,
/**
@@ -550,7 +553,7 @@ export function useAuth() {
* wipe: a wipe without a 401/403 verdict would destroy the offline
* caches on every network blip.
*/
isAuthUnknown: !query.data && verdictUnknown,
isAuthUnknown: !user && verdictUnknown,
error: query.error,
refetch: query.refetch,
};