Files
whisper_web/src/lib/useAuth.ts
T

68 lines
2.3 KiB
TypeScript

import { useCallback, useEffect, useState } from "react";
import {
fetchAuthStatus,
login as loginRequest,
logout as logoutRequest,
register as registerRequest,
type AuthUser,
} from "./authClient";
export interface AuthState {
/**
* Whether this deployment offers login at all (runtime flag, D-015).
* Starts false — the auth UI stays hidden until the /me probe confirms the
* endpoints exist, so a disabled deployment never flashes a Sign in button.
*/
enabled: boolean;
/** The signed-in user, or null while anonymous (the default). */
user: AuthUser | null;
/** Rejects with a UI-safe Error message on failure (e.g. 401). */
login: (username: string, password: string) => Promise<void>;
/** Rejects with a UI-safe Error message on failure (400/409). */
register: (username: string, password: string) => Promise<void>;
/** Always resolves; local state is cleared even if the server is down. */
logout: () => Promise<void>;
}
/**
* Session state for the M4 auth UI. The session cookie is HttpOnly, so the
* only source of truth is GET /api/auth/me, probed once on mount.
* `fetchAuthStatus` never rejects — 401 resolves to anonymous, and 404
* (AUTH_ENABLED=false, D-015) or network failure (offline, server down)
* resolve to `enabled: false`, keeping the anonymous-first invariant:
* a failing /me must not surface any error.
*/
export function useAuth(): AuthState {
const [enabled, setEnabled] = useState(false);
const [user, setUser] = useState<AuthUser | null>(null);
useEffect(() => {
let cancelled = false;
fetchAuthStatus().then((status) => {
// Guard against StrictMode double-mounts / fast unmounts.
if (cancelled) return;
setEnabled(status.enabled);
if (status.user) setUser(status.user);
});
return () => {
cancelled = true;
};
}, []);
const login = useCallback(async (username: string, password: string) => {
setUser(await loginRequest(username, password));
}, []);
const register = useCallback(async (username: string, password: string) => {
// Register signs the user in immediately (the 201 sets the cookie).
setUser(await registerRequest(username, password));
}, []);
const logout = useCallback(async () => {
await logoutRequest();
setUser(null);
}, []);
return { enabled, user, login, register, logout };
}