);
}
-})
+});
diff --git a/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx b/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx
new file mode 100644
index 0000000..9511ece
--- /dev/null
+++ b/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 .
+ */
+
+import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
+import {SESSION_UUID_COOKIE_NAME} from "App";
+import Client from "qqq/utils/qqq/Client";
+import {useCookies} from "react-cookie";
+import {Md5} from "ts-md5/dist/md5";
+
+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 anonymous authentication module
+ ***************************************************************************/
+export default function useAnonymousAuthenticationModule({setIsFullyAuthenticated, setLoggedInUser, setEarlyReturnForAuth}: Props)
+{
+ const [cookies, setCookie, removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ const setupSession = async () =>
+ {
+ console.log("Generating random token...");
+ setIsFullyAuthenticated(true);
+ qController.setGotAuthentication();
+ setCookie(SESSION_UUID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
+ console.log("Token generation complete.");
+ };
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ const logout = () =>
+ {
+ qController.clearAuthenticationMetaDataLocalStorage();
+ removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
+ };
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ const renderAppWrapper = (authenticationMetaData: QAuthenticationMetaData, children: JSX.Element): JSX.Element =>
+ {
+ return children;
+ };
+
+
+ return {
+ setupSession,
+ logout,
+ renderAppWrapper
+ };
+
+}
\ No newline at end of file
diff --git a/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx b/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx
new file mode 100644
index 0000000..aa1c256
--- /dev/null
+++ b/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx
@@ -0,0 +1,252 @@
+/*
+ * 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 .
+ */
+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
+
+
+
+ );
+ }
+ else
+ {
+ return (
+ // @ts-ignore
+
+ {children}
+
+ );
+ }
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ 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 (
+
Error: AUTH0 authenticationMetaData is missing baseUrl [{domain}] and/or clientId [{clientId}].
+ );
+ }
+
+ 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 and pass the authentication-
+ ** MetaData prop in, so a simple Component can be passed into ProtectedRoute
+ ***************************************************************************/
+ function WrappedApp()
+ {
+ return
+ }
+
+ return (
+
+
+
+
+
+ );
+ };
+
+
+ return {
+ setupSession,
+ logout,
+ renderAppWrapper
+ };
+
+}
\ No newline at end of file
diff --git a/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx b/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx
new file mode 100644
index 0000000..0bd58c7
--- /dev/null
+++ b/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx
@@ -0,0 +1,188 @@
+/*
+ * 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 .
+ */
+
+import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
+import {SESSION_UUID_COOKIE_NAME} from "App";
+import Client from "qqq/utils/qqq/Client";
+import {useCookies} from "react-cookie";
+import {AuthContextProps, AuthProvider, useAuth} from "react-oidc-context";
+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;
+ inOAuthContext: boolean;
+}
+
+/***************************************************************************
+ ** hook for working with the OAuth2 authentication module
+ ***************************************************************************/
+export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated, setLoggedInUser, setEarlyReturnForAuth, inOAuthContext}: Props)
+{
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // the useAuth hook should only be called if we're inside the element //
+ // so on the page that uses this hook to call renderAppWrapper, we aren't in that //
+ // element/context, thus, don't call that hook. //
+ ///////////////////////////////////////////////////////////////////////////////////////
+ const authOidc: AuthContextProps | null = inOAuthContext ? useAuth() : null;
+
+ const [cookies, removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]);
+ const [searchParams] = useSearchParams();
+ const navigate = useNavigate();
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ const setupSession = async () =>
+ {
+ try
+ {
+ const preSigninRedirectPathnameKey = "oauth2.preSigninRedirect.pathname";
+ if (window.location.pathname == "/token")
+ {
+ ///////////////////////////////////////////////////////////////////////////
+ // if we're at a path of /token, get code & state params, look up values //
+ // from that state in local storage, and make a post to the backend to //
+ // with these values - which will itself talk to the identity provider //
+ // to get an access token, and ultimately a session. //
+ ///////////////////////////////////////////////////////////////////////////
+ const code = searchParams.get("code");
+ const state = searchParams.get("state");
+ const oidcString = localStorage.getItem(`oidc.${state}`);
+ if (oidcString)
+ {
+ const oidcObject = JSON.parse(oidcString) as { [name: string]: any };
+ console.log(oidcObject);
+ const manageSessionRequestBody = {code: code, codeVerifier: oidcObject.code_verifier, redirectUri: oidcObject.redirect_uri};
+ const {uuid: newSessionUuid, values} = await qController.manageSession(null, null, manageSessionRequestBody);
+ console.log(`we have new session UUID: ${newSessionUuid}`);
+
+ setIsFullyAuthenticated(true);
+ qController.setGotAuthentication();
+
+ setLoggedInUser(values?.user);
+ console.log("Token load complete.");
+
+ const preSigninRedirectPathname = localStorage.getItem(preSigninRedirectPathnameKey);
+ localStorage.removeItem(preSigninRedirectPathname);
+ navigate(preSigninRedirectPathname ?? "/", {replace: true});
+ }
+ else
+ {
+ ////////////////////////////////////////////
+ // if unrecognized state, render an error //
+ ////////////////////////////////////////////
+ setEarlyReturnForAuth(
Login error: Unrecognized state. Refresh to try again.
);
+ }
+ }
+ else
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // if we have a sessionUUID cookie, try to validate it with the backend //
+ //////////////////////////////////////////////////////////////////////////
+ const sessionUuid = cookies[SESSION_UUID_COOKIE_NAME];
+ if (sessionUuid)
+ {
+ console.log(`we have session UUID: ${sessionUuid} - validating it...`);
+ const {values} = await qController.manageSession(null, sessionUuid, null);
+
+ setIsFullyAuthenticated(true);
+ qController.setGotAuthentication();
+
+ setLoggedInUser(values?.user);
+ console.log("Token load complete.");
+ }
+ else
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // else no cookie, and not a token url, we need to redirect to the provider's login page //
+ // capture the path the user was trying to access in local storage, to redirect back to later. //
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ console.log("Loading token from OAuth2 provider...");
+ console.log(authOidc);
+ localStorage.setItem(preSigninRedirectPathnameKey, window.location.pathname);
+ setEarlyReturnForAuth(