Fix refreshing causing you to go to the Processor (#7694)

This commit is contained in:
James Brunton
2026-08-27 23:45:10 +01:00
committed by GitHub
parent a48356a2d2
commit 849d616451
5 changed files with 45 additions and 6 deletions
@@ -113,6 +113,16 @@ class RequestUriUtilsTest {
assertTrue(RequestUriUtils.isFrontendRoute("", "/split-pdf"));
}
@Test
void testIsFrontendRoute_editorRouteOwnedByFrontend() {
// /editor (and its tool routes) is an SPA route: a direct-nav/refresh must
// serve index.html, not the auth filter's 302-to-/login. Regression test for
// the editor moving from / to /editor, whose refresh bounced processor users
// to the processor because the redirect dropped the return path.
assertTrue(RequestUriUtils.isFrontendRoute("", "/editor"));
assertTrue(RequestUriUtils.isFrontendRoute("/app", "/app/editor"));
}
@Test
void testIsFrontendRoute_filesRouteOwnedByFrontend() {
// /files and /files/<folder-uuid> are FileManagerView routes - they
@@ -357,12 +357,12 @@ public class SecurityConfiguration {
req -> {
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
// Check if it's a public auth endpoint or static
// resource
return RequestUriUtils.isStaticResource(
contextPath, uri)
|| RequestUriUtils.isPublicAuthEndpoint(
uri, contextPath);
uri, contextPath)
|| RequestUriUtils.isFrontendRoute(
contextPath, uri);
})
.permitAll()
.anyRequest()
@@ -234,7 +234,10 @@ describe("Login", () => {
);
};
afterEach(() => window.history.replaceState({}, "", "/"));
afterEach(() => {
window.history.replaceState({}, "", "/");
sessionStorage.clear();
});
it("returns to where the user came from", async () => {
signedIn();
@@ -247,6 +250,24 @@ describe("Login", () => {
});
});
// A full-page 401 redirect drops router state and Spring can strip ?from=,
// leaving the return path only in the sessionStorage stash. Without reading
// it here a processor user refreshing /editor falls through to the role
// router and lands on the processor.
it("returns to the stashed path when there is no ?from=", async () => {
signedIn();
sessionStorage.setItem("stirling_post_login_path", "/compress");
renderAtLogin("");
await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith("/compress", {
replace: true,
});
});
// Consumed, so a later sign-in can't reuse a stale path.
expect(sessionStorage.getItem("stirling_post_login_path")).toBeNull();
});
// Delegated to the shared isSafePostLoginRedirect, so the backslash form
// (browsers normalise "\" to "/") and auth routes are covered too.
it.each([
@@ -7,7 +7,10 @@ import {
} from "react-router-dom";
import { Button } from "@app/ui/Button";
import { isSafePostLoginRedirect } from "@app/auth";
import { setPostLoginRedirectPath } from "@app/auth/spring/springAuthClient";
import {
setPostLoginRedirectPath,
consumePostLoginRedirectPath,
} from "@app/auth/spring/springAuthClient";
import { useAuth } from "@app/auth/UseSession";
import { useAppConfig } from "@app/contexts/AppConfigContext";
import { useTranslation } from "react-i18next";
@@ -207,7 +210,8 @@ export default function Login() {
useEffect(() => {
if (loading) return;
if (!session) return;
const returnPath = resolveReturnPath();
const stashed = consumePostLoginRedirectPath();
const returnPath = resolveReturnPath() ?? stashed;
if (returnPath) {
navigate(returnPath, { replace: true });
return;
@@ -1,6 +1,7 @@
import { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from "axios";
import { withBasePath } from "@app/constants/app";
import { getBrowserId } from "@app/utils/browserIdentifier";
import { setPostLoginRedirectPath } from "@app/auth/spring/springAuthClient";
let isRefreshing = false;
let failedQueue: Array<{
@@ -93,6 +94,9 @@ async function refreshAuthToken(client: AxiosInstance): Promise<string> {
// Redirect to login
const loginPath = withBasePath("/login");
if (window.location.pathname !== loginPath) {
setPostLoginRedirectPath(
window.location.pathname + window.location.search,
);
console.log("[API Client] Redirecting to login page...");
window.location.href = loginPath;
}