mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
eliminates warnings from oauth2 hook by conditionally using its useAuth hook.
252 lines
9.0 KiB
TypeScript
252 lines
9.0 KiB
TypeScript
/*
|
|
* QQQ - Low-code Application Framework for Engineers.
|
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
|
* contact@kingsrook.com
|
|
* https://github.com/Kingsrook/
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
import {Auth0Provider, useAuth0} from "@auth0/auth0-react";
|
|
import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
|
import App, {SESSION_UUID_COOKIE_NAME} from "App";
|
|
import HandleAuthorizationError from "HandleAuthorizationError";
|
|
import jwt_decode from "jwt-decode";
|
|
import ProtectedRoute from "qqq/authorization/auth0/ProtectedRoute";
|
|
import {MaterialUIControllerProvider} from "qqq/context";
|
|
import Client from "qqq/utils/qqq/Client";
|
|
import {useCookies} from "react-cookie";
|
|
import {useNavigate, useSearchParams} from "react-router-dom";
|
|
|
|
const qController = Client.getInstance();
|
|
|
|
interface Props
|
|
{
|
|
setIsFullyAuthenticated?: (is: boolean) => void;
|
|
setLoggedInUser?: (user: any) => void;
|
|
setEarlyReturnForAuth?: (element: JSX.Element | null) => void;
|
|
}
|
|
|
|
/***************************************************************************
|
|
** hook for working with the Auth0 authentication module
|
|
***************************************************************************/
|
|
export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, setLoggedInUser}: Props)
|
|
{
|
|
const {user: auth0User, getAccessTokenSilently: auth0GetAccessTokenSilently, logout: useAuth0Logout} = useAuth0();
|
|
|
|
const [cookies, removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]);
|
|
|
|
|
|
/***************************************************************************
|
|
**
|
|
***************************************************************************/
|
|
const shouldStoreNewToken = (newToken: string, oldToken: string): boolean =>
|
|
{
|
|
if (!cookies[SESSION_UUID_COOKIE_NAME])
|
|
{
|
|
console.log("No session uuid cookie - so we should store a new one.");
|
|
return (true);
|
|
}
|
|
|
|
if (!oldToken)
|
|
{
|
|
console.log("No accessToken in localStorage - so we should store a new one.");
|
|
return (true);
|
|
}
|
|
|
|
try
|
|
{
|
|
const oldJSON: any = jwt_decode(oldToken);
|
|
const newJSON: any = jwt_decode(newToken);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// if the old (local storage) token is expired, then we need to store the new one //
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
const oldExp = oldJSON["exp"];
|
|
if (oldExp * 1000 < (new Date().getTime()))
|
|
{
|
|
console.log("Access token in local storage was expired - so we should store a new one.");
|
|
return (true);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// remove the exp & iat values from what we compare - as they are always different from auth0 //
|
|
// note, this is only deleting them from what we compare, not from what we'd store. //
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
delete newJSON["exp"];
|
|
delete newJSON["iat"];
|
|
delete oldJSON["exp"];
|
|
delete oldJSON["iat"];
|
|
|
|
const different = JSON.stringify(newJSON) !== JSON.stringify(oldJSON);
|
|
if (different)
|
|
{
|
|
console.log("Latest access token from auth0 has changed vs localStorage - so we should store a new one.");
|
|
}
|
|
return (different);
|
|
}
|
|
catch (e)
|
|
{
|
|
console.log("Caught in shouldStoreNewToken: " + e);
|
|
}
|
|
|
|
return (true);
|
|
};
|
|
|
|
|
|
/***************************************************************************
|
|
**
|
|
***************************************************************************/
|
|
const setupSession = async () =>
|
|
{
|
|
try
|
|
{
|
|
console.log("Loading token from auth0...");
|
|
const accessToken = await auth0GetAccessTokenSilently();
|
|
|
|
const lsAccessToken = localStorage.getItem("accessToken");
|
|
if (shouldStoreNewToken(accessToken, lsAccessToken))
|
|
{
|
|
console.log("Sending accessToken to backend, requesting a sessionUUID...");
|
|
const {uuid: values} = await qController.manageSession(accessToken, null);
|
|
|
|
localStorage.setItem("accessToken", accessToken);
|
|
localStorage.setItem("sessionValues", JSON.stringify(values));
|
|
console.log("Got new sessionUUID from backend, and stored new accessToken");
|
|
}
|
|
else
|
|
{
|
|
console.log("Using existing sessionUUID cookie");
|
|
}
|
|
|
|
setIsFullyAuthenticated(true);
|
|
qController.setGotAuthentication();
|
|
|
|
setLoggedInUser(auth0User);
|
|
console.log("Token load complete.");
|
|
}
|
|
catch (e)
|
|
{
|
|
console.log(`Error loading token: ${JSON.stringify(e)}`);
|
|
qController.clearAuthenticationMetaDataLocalStorage();
|
|
localStorage.removeItem("accessToken");
|
|
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
|
useAuth0Logout();
|
|
return;
|
|
}
|
|
};
|
|
|
|
|
|
/***************************************************************************
|
|
**
|
|
***************************************************************************/
|
|
const logout = () =>
|
|
{
|
|
useAuth0Logout({returnTo: window.location.origin});
|
|
};
|
|
|
|
/***************************************************************************
|
|
**
|
|
***************************************************************************/
|
|
// @ts-ignore
|
|
function Auth0ProviderWithRedirectCallback({children, ...props})
|
|
{
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
|
|
// @ts-ignore
|
|
const onRedirectCallback = (appState) =>
|
|
{
|
|
navigate((appState && appState.returnTo) || window.location.pathname);
|
|
};
|
|
if (searchParams.get("error"))
|
|
{
|
|
return (
|
|
// @ts-ignore
|
|
<Auth0Provider {...props}>
|
|
<HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
|
|
</Auth0Provider>
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return (
|
|
// @ts-ignore
|
|
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
|
|
{children}
|
|
</Auth0Provider>
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
**
|
|
***************************************************************************/
|
|
const renderAppWrapper = (authenticationMetaData: QAuthenticationMetaData): JSX.Element =>
|
|
{
|
|
// @ts-ignore
|
|
let domain: string = authenticationMetaData.data.baseUrl;
|
|
|
|
// @ts-ignore
|
|
const clientId = authenticationMetaData.data.clientId;
|
|
|
|
// @ts-ignore
|
|
const audience = authenticationMetaData.data.audience;
|
|
|
|
if (!domain || !clientId)
|
|
{
|
|
return (
|
|
<div>Error: AUTH0 authenticationMetaData is missing baseUrl [{domain}] and/or clientId [{clientId}].</div>
|
|
);
|
|
}
|
|
|
|
if (domain.endsWith("/"))
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
// auth0 lib fails if we have a trailing slash. be a bit more graceful than that. //
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
domain = domain.replace(/\/$/, "");
|
|
}
|
|
|
|
/***************************************************************************
|
|
** simple Functional Component to wrap the <App> and pass the authentication-
|
|
** MetaData prop in, so a simple Component can be passed into ProtectedRoute
|
|
***************************************************************************/
|
|
function WrappedApp()
|
|
{
|
|
return <App authenticationMetaData={authenticationMetaData} />
|
|
}
|
|
|
|
return (
|
|
<Auth0ProviderWithRedirectCallback
|
|
domain={domain}
|
|
clientId={clientId}
|
|
audience={audience}
|
|
redirectUri={`${window.location.origin}/`}>
|
|
<MaterialUIControllerProvider>
|
|
<ProtectedRoute component={WrappedApp} />
|
|
</MaterialUIControllerProvider>
|
|
</Auth0ProviderWithRedirectCallback>
|
|
);
|
|
};
|
|
|
|
|
|
return {
|
|
setupSession,
|
|
logout,
|
|
renderAppWrapper
|
|
};
|
|
|
|
} |