import { useState, useEffect, useContext, createContext } from "react"; import { useLocation, Redirect } from "react-router-dom"; import { authenticatedEndpoint, BASE_URL, unauthenticatedEndpoint, } from "./util"; import LoadingPage from "./pages/Loading"; export interface OAuthProviders { providers: string[]; } interface LoginResponse { user_uuid: string; key: string; expires: number; error?: string; picture_url?: string; } export interface AuthContext { login: (username: string, password: string) => Promise; oauthLogin: (provider: string) => Promise; logout: () => Promise; getAuthKey: () => Promise; getUserUuid: () => string; getPictureUrl: () => string; handleLoginResponse: (json: LoginResponse) => any; } const authContext = createContext(null); export function ProvideAuth({ children }: { children: any }) { const auth = useProvideAuth(); return {children}; } export function HandleOAuthLogin() { const location = useLocation(); const auth = useAuth(); const [result, setResult] = useState(null); useEffect(async () => { try { let result = await fetch( unauthenticatedEndpoint(`auth/login/oauth/complete${location.search}`) ); let json = await result.json(); auth?.handleLoginResponse(json); } catch (err: any) { setResult( ); } }); return result ?? ; } export const useAuth = (): AuthContext | null => { return useContext(authContext); }; function useProvideAuth(): AuthContext { const [auth, setAuth] = useState(() => { let authStorage = getAuthStorage(); return [ authStorage.userUuid, authStorage.authKey, authStorage.expires, authStorage.pictureUrl, ]; }); useEffect(() => { localStorage.setItem( "charteredAuthentication", JSON.stringify({ userUuid: auth?.[0], authKey: auth?.[1], expires: auth?.[2], pictureUrl: auth?.[3], }) ); }, [auth]); const handleLoginResponse = (response: LoginResponse) => { if (response.error) { throw new Error(response.error); } setAuth([ response.user_uuid, response.key, new Date(response.expires), response.picture_url, ]); }; const login = async (username: string, password: string) => { let res = await fetch(unauthenticatedEndpoint("auth/login/password"), { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": window.navigator.userAgent, }, body: JSON.stringify({ username, password }), }); let json = await res.json(); handleLoginResponse(json); }; const oauthLogin = async (provider: string) => { let res = await fetch( unauthenticatedEndpoint(`auth/login/oauth/${provider}/begin`), { method: "GET", headers: { "Content-Type": "application/json", "User-Agent": window.navigator.userAgent, }, } ); let json = await res.json(); if (json.error) { throw new Error(json.error); } window.location.href = json.redirect_url; }; const getAuthKey = () => { if (auth?.[2] > new Date()) { return auth?.[1]; } else if (auth) { return null; } }; const logout = async () => { if (auth === null) { return; } try { await fetch(`${BASE_URL}/a/${getAuthKey()}/web/v1/auth/logout`, { method: "GET", headers: { "Content-Type": "application/json", "User-Agent": window.navigator.userAgent, }, }); } catch (e) { console.error("Failed to fully log user out of session", e); } finally { setAuth(null); } }; const getUserUuid = () => { if (auth?.[2] > new Date()) { return auth?.[0]; } else if (auth) { return null; } }; const getPictureUrl = () => { if (auth?.[2] > new Date()) { return auth?.[3]; } else if (auth) { return null; } }; return { login, logout, getAuthKey, getUserUuid, getPictureUrl, oauthLogin, handleLoginResponse, }; } function getAuthStorage() { const saved = localStorage.getItem("charteredAuthentication"); const initial = saved ? JSON.parse(saved) : {}; return { userUuid: initial?.userUuid || null, authKey: initial?.authKey || null, expires: initial?.expires ? new Date(initial.expires) : null, pictureUrl: initial?.pictureUrl, }; }