Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09c720f18c | ||
|
|
3208704053 | ||
|
|
3da3c96edd | ||
|
|
858999119c | ||
|
|
a181e30eef |
No files matched your search
@@ -3,6 +3,7 @@
|
||||
src/main/resources/material-dashboard/
|
||||
|
||||
**/.idea*
|
||||
.vscode
|
||||
*.iml
|
||||
.yalc*
|
||||
yalc.lock
|
||||
|
||||
+3
-2
@@ -48,7 +48,7 @@ import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||
import ReportRun from "qqq/pages/processes/ReportRun";
|
||||
import EntityCreate from "qqq/pages/records/create/RecordCreate";
|
||||
import TableDeveloperView from "qqq/pages/records/developer/TableDeveloperView";
|
||||
import {resolveAssetUrl} from "qqq/utils/PathUtils";
|
||||
import {detectBasePath, resolveAssetUrl} from "qqq/utils/PathUtils";
|
||||
import EntityEdit from "qqq/pages/records/edit/RecordEdit";
|
||||
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
||||
@@ -558,7 +558,8 @@ export default function App({authenticationMetaData}: Props)
|
||||
console.log("Exception is a QException with status = 401. Clearing some of localStorage & cookies");
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
localStorage.removeItem("accessToken");
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
const basePath = detectBasePath();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: basePath});
|
||||
|
||||
doLogout();
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {useAuth0} from "@auth0/auth0-react";
|
||||
import React, {useEffect} from "react";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {SESSION_UUID_COOKIE_NAME} from "App";
|
||||
import {detectBasePath} from "qqq/utils/PathUtils";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@@ -39,7 +40,8 @@ function HandleAuthorizationError({errorMessage}: Props)
|
||||
useEffect(() =>
|
||||
{
|
||||
logout();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
const basePath = detectBasePath();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: basePath});
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import {QAuthenticationMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
import {SESSION_UUID_COOKIE_NAME} from "App";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import {detectBasePath} from "qqq/utils/PathUtils";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {Md5} from "ts-md5/dist/md5";
|
||||
|
||||
@@ -49,7 +50,8 @@ export default function useAnonymousAuthenticationModule({setIsFullyAuthenticate
|
||||
console.log("Generating random token...");
|
||||
setIsFullyAuthenticated(true);
|
||||
Client.setGotAuthenticationInAllControllers();
|
||||
setCookie(SESSION_UUID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
|
||||
const basePath = detectBasePath();
|
||||
setCookie(SESSION_UUID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: basePath});
|
||||
console.log("Token generation complete.");
|
||||
};
|
||||
|
||||
@@ -60,7 +62,8 @@ export default function useAnonymousAuthenticationModule({setIsFullyAuthenticate
|
||||
const logout = () =>
|
||||
{
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
const basePath = detectBasePath();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: basePath});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ 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 {detectBasePath} from "qqq/utils/PathUtils";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {useNavigate, useSearchParams} from "react-router-dom";
|
||||
|
||||
@@ -142,7 +143,8 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s
|
||||
console.log(`Error loading token: ${JSON.stringify(e)}`);
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
localStorage.removeItem("accessToken");
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
const basePath = detectBasePath();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: basePath});
|
||||
useAuth0Logout();
|
||||
return;
|
||||
}
|
||||
@@ -232,12 +234,14 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s
|
||||
return <App authenticationMetaData={authenticationMetaData} />
|
||||
}
|
||||
|
||||
const basePath = detectBasePath();
|
||||
const redirectUri = `${window.location.origin}${basePath}`;
|
||||
return (
|
||||
<Auth0ProviderWithRedirectCallback
|
||||
domain={domain}
|
||||
clientId={clientId}
|
||||
audience={audience}
|
||||
redirectUri={`${window.location.origin}/`}>
|
||||
redirectUri={redirectUri}>
|
||||
<MaterialUIControllerProvider>
|
||||
<ProtectedRoute component={WrappedApp} />
|
||||
</MaterialUIControllerProvider>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import {QAuthenticationMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
import {SESSION_UUID_COOKIE_NAME} from "App";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import {detectBasePath} from "qqq/utils/PathUtils";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {AuthContextProps, AuthProvider, useAuth} from "react-oidc-context";
|
||||
import {useNavigate, useSearchParams} from "react-router-dom";
|
||||
@@ -59,8 +60,10 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
{
|
||||
try
|
||||
{
|
||||
const basePath = detectBasePath();
|
||||
const preSigninRedirectPathnameKey = "oauth2.preSigninRedirect.pathname";
|
||||
if (window.location.pathname == "/token")
|
||||
const tokenPath = basePath === "/" ? "/token" : `${basePath}/token`;
|
||||
if (window.location.pathname === tokenPath)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if we're at a path of /token, get code & state params, look up values //
|
||||
@@ -87,7 +90,7 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
|
||||
const preSigninRedirectPathname = localStorage.getItem(preSigninRedirectPathnameKey);
|
||||
localStorage.removeItem(preSigninRedirectPathname);
|
||||
navigate(preSigninRedirectPathname ?? "/", {replace: true});
|
||||
navigate(preSigninRedirectPathname ?? basePath, {replace: true});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -143,7 +146,8 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
const logout = () =>
|
||||
{
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
const basePath = detectBasePath();
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: basePath});
|
||||
authOidc?.signoutRedirect();
|
||||
};
|
||||
|
||||
@@ -163,11 +167,13 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
);
|
||||
}
|
||||
|
||||
const basePath = detectBasePath();
|
||||
const tokenPath = basePath === "/" ? "/token" : `${basePath}/token`;
|
||||
const oidcConfig =
|
||||
{
|
||||
authority: authority,
|
||||
client_id: clientId,
|
||||
redirect_uri: `${window.location.origin}/token`,
|
||||
redirect_uri: `${window.location.origin}${tokenPath}`,
|
||||
response_type: "code",
|
||||
scope: "openid profile email offline_access",
|
||||
};
|
||||
|
||||
+77
-23
@@ -19,36 +19,58 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Normalizes a path to always be an absolute path starting with "/".
|
||||
* Handles edge cases like "./", "", or paths without leading slash.
|
||||
*
|
||||
* @param path The path to normalize
|
||||
* @returns An absolute path starting with "/"
|
||||
*/
|
||||
function normalizeBasePath(path: string): string
|
||||
{
|
||||
if (!path || path === "" || path === "./" || path === ".")
|
||||
{
|
||||
return "/";
|
||||
}
|
||||
|
||||
// Ensure it starts with "/"
|
||||
if (!path.startsWith("/"))
|
||||
{
|
||||
return "/" + path.replace(/^\.\//, "").replace(/\/$/, "");
|
||||
}
|
||||
|
||||
// Ensure it doesn't end with "/" (except for root)
|
||||
if (path !== "/" && path.endsWith("/"))
|
||||
{
|
||||
return path.slice(0, -1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the base path where the SPA is running.
|
||||
* Works for both root and sub-paths like /admin, /dashboard, etc.
|
||||
*
|
||||
* This function works correctly with `homepage: "."` in package.json because:
|
||||
* - Even though HTML may reference assets relatively (./static/js/main.js),
|
||||
* - The browser resolves these to absolute URLs when loading
|
||||
* - document.currentScript.src will always contain the full absolute URL
|
||||
*
|
||||
* Detection strategy (in order of reliability):
|
||||
* 1. Extract from current script location (most reliable)
|
||||
* 2. Extract from known path patterns in current pathname
|
||||
* 3. Default to / (root)
|
||||
* 1. Extract from current script location (most reliable - works even with relative paths in HTML)
|
||||
* 2. Extract from all script tags as fallback
|
||||
* 3. Extract from known path patterns in current pathname
|
||||
* 4. Default to / (root)
|
||||
*
|
||||
* @returns The detected base path (e.g., "/admin", "/", "/dashboard")
|
||||
* Always returns an absolute path starting with "/"
|
||||
*/
|
||||
export function detectBasePath(): string
|
||||
{
|
||||
// Strategy 1: Check current URL path first (most reliable for dev and prod)
|
||||
const currentPath = window.location.pathname;
|
||||
const parts = currentPath.split("/").filter(p => p);
|
||||
|
||||
// If first part is a known SPA path like "admin", "dashboard", etc., use it
|
||||
if (parts.length > 0)
|
||||
{
|
||||
const firstPart = parts[0];
|
||||
// You can add more known paths here as needed
|
||||
if (["admin", "dashboard", "app", "manager", "console"].includes(firstPart))
|
||||
{
|
||||
return "/" + firstPart;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Try to extract from the current script's source
|
||||
// When served from /admin/static/js/main.abc123.js, extract /admin
|
||||
// Strategy 1: Try to extract from the current script's source (most reliable)
|
||||
// Works even when homepage: "." uses relative paths because browser resolves them to absolute URLs
|
||||
// Example: HTML has "./static/js/main.js" but browser resolves to "http://localhost:8000/admin/static/js/main.js"
|
||||
if (document.currentScript && document.currentScript instanceof HTMLScriptElement)
|
||||
{
|
||||
const src = document.currentScript.src;
|
||||
@@ -57,12 +79,44 @@ export function detectBasePath(): string
|
||||
const match = src.match(/^https?:\/\/[^\/]+([\/\w-]*?)\/static\//);
|
||||
if (match && match[1])
|
||||
{
|
||||
return match[1];
|
||||
return normalizeBasePath(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Default to root
|
||||
return "/";
|
||||
// Strategy 2: Fallback - try to extract from any script tag with /static/ in the path
|
||||
// This handles cases where currentScript might not be available
|
||||
const scripts = document.querySelectorAll("script[src]");
|
||||
for (let i = 0; i < scripts.length; i++)
|
||||
{
|
||||
const script = scripts[i] as HTMLScriptElement;
|
||||
if (script.src && script.src.includes("/static/"))
|
||||
{
|
||||
const match = script.src.match(/^https?:\/\/[^\/]+([\/\w-]*?)\/static\//);
|
||||
if (match && match[1])
|
||||
{
|
||||
return normalizeBasePath(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Extract from current URL pathname
|
||||
// Check if first part of pathname matches known SPA paths
|
||||
const currentPath = window.location.pathname;
|
||||
const parts = currentPath.split("/").filter(p => p);
|
||||
|
||||
if (parts.length > 0)
|
||||
{
|
||||
const firstPart = parts[0];
|
||||
// You can add more known paths here as needed
|
||||
if (["admin", "dashboard", "app", "manager", "console"].includes(firstPart))
|
||||
{
|
||||
return normalizeBasePath("/" + firstPart);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 4: Default to root
|
||||
// This handles the case where SPA is served from root (/)
|
||||
return normalizeBasePath("/");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user