mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 22:30:55 +00:00
162 lines
3.9 KiB
TypeScript
162 lines
3.9 KiB
TypeScript
import {
|
|
createContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
import { getMeRequest, loginRequest, verifyMfaRequest } from "./api";
|
|
import {
|
|
AUTH_TOKEN_COOKIE,
|
|
AUTH_USER_COOKIE,
|
|
clearSessionCookies,
|
|
getCookie,
|
|
setCookie,
|
|
} from "./cookies";
|
|
import { applyTokens } from "./http";
|
|
import {
|
|
startTokenRefreshScheduler,
|
|
stopTokenRefreshScheduler,
|
|
} from "./refreshScheduler";
|
|
import type { AuthTokens, AuthUser } from "./types";
|
|
|
|
interface LoginPayload {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
interface VerifyMfaPayload {
|
|
email: string;
|
|
otp: string;
|
|
}
|
|
|
|
export interface AuthContextValue {
|
|
user: AuthUser | null;
|
|
loading: boolean;
|
|
login: (payload: LoginPayload) => Promise<{ mfaRequired: boolean }>;
|
|
verifyMfa: (payload: VerifyMfaPayload) => Promise<void>;
|
|
logout: () => void;
|
|
}
|
|
|
|
export const AuthContext = createContext<AuthContextValue | null>(null);
|
|
|
|
const persistUser = (user: AuthUser | null) => {
|
|
if (user) {
|
|
setCookie(AUTH_USER_COOKIE, JSON.stringify(user));
|
|
return;
|
|
}
|
|
|
|
clearSessionCookies();
|
|
};
|
|
|
|
const bootstrapCachedUser = (): AuthUser | null => {
|
|
const serialized = getCookie(AUTH_USER_COOKIE);
|
|
if (!serialized) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(serialized) as AuthUser;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
const [user, setUser] = useState<AuthUser | null>(() => bootstrapCachedUser());
|
|
const [loading, setLoading] = useState(true);
|
|
const mfaEmailRef = useRef<string | null>(null);
|
|
|
|
const loadCurrentUser = async () => {
|
|
const currentUser = await getMeRequest();
|
|
setUser(currentUser);
|
|
persistUser(currentUser);
|
|
};
|
|
|
|
useEffect(() => {
|
|
["auth-token", "refresh-token"].forEach((name) => {
|
|
const value = getCookie(name);
|
|
if (value === "undefined" || value === "null" || value === "") {
|
|
clearSessionCookies();
|
|
}
|
|
});
|
|
|
|
const bootstrap = async () => {
|
|
const token = getCookie(AUTH_TOKEN_COOKIE);
|
|
if (!token) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await loadCurrentUser();
|
|
} catch {
|
|
clearSessionCookies();
|
|
setUser(null);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
void bootstrap();
|
|
}, []);
|
|
|
|
// Keep the server session alive while a user is logged in. Runs after
|
|
// login, MFA verification, and page-reload bootstrap alike.
|
|
useEffect(() => {
|
|
if (!user) {
|
|
stopTokenRefreshScheduler();
|
|
return;
|
|
}
|
|
|
|
startTokenRefreshScheduler();
|
|
return stopTokenRefreshScheduler;
|
|
}, [user]);
|
|
|
|
const value = useMemo<AuthContextValue>(
|
|
() => ({
|
|
user,
|
|
loading,
|
|
login: async ({ email, password }) => {
|
|
const result = await loginRequest({ email, password });
|
|
|
|
if (result.mfaRequired) {
|
|
mfaEmailRef.current = email;
|
|
return { mfaRequired: true };
|
|
}
|
|
|
|
const tokens = result as AuthTokens;
|
|
applyTokens(tokens);
|
|
await loadCurrentUser();
|
|
mfaEmailRef.current = null;
|
|
return { mfaRequired: false };
|
|
},
|
|
verifyMfa: async ({ email, otp }) => {
|
|
const identifier = mfaEmailRef.current ?? email;
|
|
const tokens = await verifyMfaRequest({ email: identifier, otp });
|
|
applyTokens(tokens);
|
|
await loadCurrentUser();
|
|
mfaEmailRef.current = null;
|
|
},
|
|
logout: () => {
|
|
const preservedTheme = window.localStorage.getItem("edr-theme");
|
|
|
|
clearSessionCookies();
|
|
window.localStorage.clear();
|
|
|
|
if (preservedTheme === "dark" || preservedTheme === "light") {
|
|
window.localStorage.setItem("edr-theme", preservedTheme);
|
|
}
|
|
|
|
setUser(null);
|
|
window.location.replace("/auth");
|
|
},
|
|
}),
|
|
[loading, user],
|
|
);
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
};
|