104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
import { useEffect, useState } from "preact/hooks";
|
|||
|
|
import { getConfiguredToken, setAuthToken, verifySession } from "@/lib/api";
|
||
|
|
|
||
|
|
const TOKEN_STORAGE_KEY = "sybil_admin_token";
|
||
|
|
|
||
|
|
export type AuthMode = "open" | "token";
|
||
|
|
|
||
|
|
function readStoredToken() {
|
||
|
|
return localStorage.getItem(TOKEN_STORAGE_KEY)?.trim() || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function persistToken(token: string | null) {
|
||
|
|
if (token) {
|
||
|
|
localStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||
|
|
}
|
||
|
|
|
||
|
|
function normalizeAuthError(message: string) {
|
||
|
|
if (message.includes("missing bearer token") || message.includes("invalid bearer token")) {
|
||
|
|
return "Authentication failed. Enter the ADMIN_TOKEN configured in server/.env.";
|
||
|
|
}
|
||
|
|
return message;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useSessionAuth() {
|
||
|
|
const initialToken = readStoredToken() ?? getConfiguredToken() ?? "";
|
||
|
|
|
||
|
|
const [authTokenInput, setAuthTokenInput] = useState(initialToken);
|
||
|
|
const [isCheckingSession, setIsCheckingSession] = useState(true);
|
||
|
|
const [isSigningIn, setIsSigningIn] = useState(false);
|
||
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||
|
|
const [authMode, setAuthMode] = useState<AuthMode | null>(null);
|
||
|
|
const [authError, setAuthError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
const completeSessionCheck = async (tokenCandidate: string | null) => {
|
||
|
|
setAuthToken(tokenCandidate);
|
||
|
|
const session = await verifySession();
|
||
|
|
setIsAuthenticated(true);
|
||
|
|
setAuthMode(session.mode);
|
||
|
|
setAuthError(null);
|
||
|
|
persistToken(tokenCandidate);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleAuthFailure = (message: string) => {
|
||
|
|
setIsAuthenticated(false);
|
||
|
|
setAuthMode(null);
|
||
|
|
setAuthError(normalizeAuthError(message));
|
||
|
|
setAuthToken(null);
|
||
|
|
persistToken(null);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSignIn = async (tokenCandidate: string | null) => {
|
||
|
|
setIsSigningIn(true);
|
||
|
|
setAuthError(null);
|
||
|
|
try {
|
||
|
|
await completeSessionCheck(tokenCandidate);
|
||
|
|
} catch (err) {
|
||
|
|
const message = err instanceof Error ? err.message : String(err);
|
||
|
|
setAuthError(normalizeAuthError(message));
|
||
|
|
setIsAuthenticated(false);
|
||
|
|
setAuthMode(null);
|
||
|
|
} finally {
|
||
|
|
setIsSigningIn(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const logout = () => {
|
||
|
|
setAuthToken(null);
|
||
|
|
persistToken(null);
|
||
|
|
setIsAuthenticated(false);
|
||
|
|
setAuthMode(null);
|
||
|
|
setAuthError(null);
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const token = readStoredToken() ?? getConfiguredToken();
|
||
|
|
void (async () => {
|
||
|
|
try {
|
||
|
|
await completeSessionCheck(token);
|
||
|
|
} catch (err) {
|
||
|
|
const message = err instanceof Error ? err.message : String(err);
|
||
|
|
handleAuthFailure(message);
|
||
|
|
} finally {
|
||
|
|
setIsCheckingSession(false);
|
||
|
|
}
|
||
|
|
})();
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return {
|
||
|
|
authTokenInput,
|
||
|
|
setAuthTokenInput,
|
||
|
|
isCheckingSession,
|
||
|
|
isSigningIn,
|
||
|
|
isAuthenticated,
|
||
|
|
authMode,
|
||
|
|
authError,
|
||
|
|
handleAuthFailure,
|
||
|
|
handleSignIn,
|
||
|
|
logout,
|
||
|
|
};
|
||
|
|
}
|