Session validation
Build and Push Lunchtime Images (Kaniko) / build-and-push (push) Successful in 1m16s

This commit is contained in:
Simon Gruber
2026-03-29 16:40:17 +02:00
parent df86f98065
commit ab50d2ecfe
3 changed files with 46 additions and 1 deletions
+23
View File
@@ -25,6 +25,7 @@ import {
clearUserId,
ensureUserId,
hasStoredUserId,
USER_SESSION_INVALIDATED_EVENT,
updateUserId,
} from "./lib/userIdentity";
import { THEME_MODE_KEY } from "./lib/constants";
@@ -363,6 +364,28 @@ export default function App() {
};
}, []);
useEffect(() => {
const handleSessionInvalidated = () => {
setUserId("");
setUserEmail("");
setIsOnboardingOpen(true);
navigateTo("/");
message.warning("Your session is no longer valid. Please sign in again.");
};
window.addEventListener(
USER_SESSION_INVALIDATED_EVENT,
handleSessionInvalidated,
);
return () => {
window.removeEventListener(
USER_SESSION_INVALIDATED_EVENT,
handleSessionInvalidated,
);
};
}, []);
const themeConfig = useMemo(
() => ({
algorithm: themeMode === "dark" ? darkAlgorithm : defaultAlgorithm,
+14 -1
View File
@@ -1,4 +1,4 @@
import { ensureUserId } from "./userIdentity";
import { ensureUserId, invalidateUserSession } from "./userIdentity";
import type { ApiError } from "./types";
const API_BASE = "/api";
@@ -17,6 +17,15 @@ export function isNotFoundError(error: unknown) {
return !!(maybeError && (maybeError.isNotFound || maybeError.status === 404));
}
function shouldClearStoredUserId(status: number, detail: unknown) {
if (status !== 401) {
return false;
}
const normalizedDetail = String(detail || "").trim().toLowerCase();
return normalizedDetail === "invalid user id" || normalizedDetail === "missing user id";
}
export async function apiFetch<T = any>(url: string, options: RequestInit = {}): Promise<T> {
const fullUrl = url.startsWith("/api") ? API_BASE + url.slice(4) : url;
const userId = ensureUserId();
@@ -45,6 +54,10 @@ export async function apiFetch<T = any>(url: string, options: RequestInit = {}):
if (!response.ok) {
const payload = await response.json().catch(() => ({} as { detail?: string }));
if (shouldClearStoredUserId(response.status, payload?.detail)) {
invalidateUserSession();
}
throw createApiError(payload.detail || `Request failed (${response.status})`, {
status: response.status,
});
+9
View File
@@ -2,6 +2,7 @@ import { USER_TOKEN_KEY } from "./constants";
import { getStoredValue, removeStoredValue, setStoredValue } from "./storage";
const emailByUserId = new Map<string, string>();
export const USER_SESSION_INVALIDATED_EVENT = "lunchtime:user-session-invalidated";
function generateGuid() {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -76,3 +77,11 @@ export function regenerateUserId() {
export function clearUserId() {
removeStoredValue(USER_TOKEN_KEY);
}
export function invalidateUserSession() {
clearUserId();
if (typeof window !== "undefined") {
window.dispatchEvent(new Event(USER_SESSION_INVALIDATED_EVENT));
}
}