M8: add operational authentication mode

This commit is contained in:
NuklearRabbit
2026-08-10 02:06:50 +02:00
parent 4cdf667dc1
commit 0bfcf71ff7
20 changed files with 345 additions and 24 deletions
+23 -6
View File
@@ -1,11 +1,13 @@
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from "react";
import { api, onUnauthorized } from "../api/client";
import type { CurrentUser, Role } from "../api/types";
import type { CurrentUser, Role, SystemStatus } from "../api/types";
interface AuthState {
user: CurrentUser | null;
loading: boolean;
demoMode: boolean;
loginAs: (role: Role) => Promise<void>;
loginWithPassword: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
}
@@ -32,11 +34,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// confirmed (or rejected) the session cookie.
const [user, setUser] = useState<CurrentUser | null>(() => readCachedUser());
const [loading, setLoading] = useState(true);
const [demoMode, setDemoMode] = useState(true);
useEffect(() => {
let cancelled = false;
api
.get<CurrentUser>("/api/v1/demo/session")
api.get<SystemStatus>("/api/v1/system/status")
.then((system) => setDemoMode(system.demo_mode))
.catch(() => setDemoMode(true))
.finally(() => api
.get<CurrentUser>("/api/v1/auth/session")
.then((confirmed) => {
if (cancelled) return;
setUser(confirmed);
@@ -49,7 +55,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
})
.finally(() => {
if (!cancelled) setLoading(false);
});
}));
return () => {
cancelled = true;
};
@@ -75,11 +81,22 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
}, []);
const loginWithPassword = useCallback(async (email: string, password: string) => {
setLoading(true);
try {
const loggedIn = await api.post<CurrentUser>("/api/v1/auth/login", { email, password });
setUser(loggedIn);
cacheUser(loggedIn);
} finally {
setLoading(false);
}
}, []);
const logout = useCallback(async () => {
setUser(null);
cacheUser(null);
try {
await api.post("/api/v1/demo/logout");
await api.post("/api/v1/auth/logout");
} catch {
// Best effort: the cookie is cleared server-side when it works, and the client has
// already dropped its own state either way.
@@ -87,7 +104,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, []);
return (
<AuthContext.Provider value={{ user, loading, loginAs, logout }}>{children}</AuthContext.Provider>
<AuthContext.Provider value={{ user, loading, demoMode, loginAs, loginWithPassword, logout }}>{children}</AuthContext.Provider>
);
}