File tree Expand file tree Collapse file tree
common/src/test/java/stirling/software/common/util
proprietary/src/main/java/stirling/software/proprietary/security/configuration
frontend/editor/src/proprietary Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -113,6 +113,16 @@ void testIsFrontendRoute_extensionlessPath() {
113113 assertTrue (RequestUriUtils .isFrontendRoute ("" , "/split-pdf" ));
114114 }
115115
116+ @ Test
117+ void testIsFrontendRoute_editorRouteOwnedByFrontend () {
118+ // /editor (and its tool routes) is an SPA route: a direct-nav/refresh must
119+ // serve index.html, not the auth filter's 302-to-/login. Regression test for
120+ // the editor moving from / to /editor, whose refresh bounced processor users
121+ // to the processor because the redirect dropped the return path.
122+ assertTrue (RequestUriUtils .isFrontendRoute ("" , "/editor" ));
123+ assertTrue (RequestUriUtils .isFrontendRoute ("/app" , "/app/editor" ));
124+ }
125+
116126 @ Test
117127 void testIsFrontendRoute_filesRouteOwnedByFrontend () {
118128 // /files and /files/<folder-uuid> are FileManagerView routes - they
Original file line number Diff line number Diff line change @@ -357,12 +357,12 @@ private SecurityFilterChain configureSecurity(
357357 req -> {
358358 String uri = req .getRequestURI ();
359359 String contextPath = req .getContextPath ();
360- // Check if it's a public auth endpoint or static
361- // resource
362360 return RequestUriUtils .isStaticResource (
363361 contextPath , uri )
364362 || RequestUriUtils .isPublicAuthEndpoint (
365- uri , contextPath );
363+ uri , contextPath )
364+ || RequestUriUtils .isFrontendRoute (
365+ contextPath , uri );
366366 })
367367 .permitAll ()
368368 .anyRequest ()
Original file line number Diff line number Diff line change @@ -234,7 +234,10 @@ describe("Login", () => {
234234 ) ;
235235 } ;
236236
237- afterEach ( ( ) => window . history . replaceState ( { } , "" , "/" ) ) ;
237+ afterEach ( ( ) => {
238+ window . history . replaceState ( { } , "" , "/" ) ;
239+ sessionStorage . clear ( ) ;
240+ } ) ;
238241
239242 it ( "returns to where the user came from" , async ( ) => {
240243 signedIn ( ) ;
@@ -247,6 +250,24 @@ describe("Login", () => {
247250 } ) ;
248251 } ) ;
249252
253+ // A full-page 401 redirect drops router state and Spring can strip ?from=,
254+ // leaving the return path only in the sessionStorage stash. Without reading
255+ // it here a processor user refreshing /editor falls through to the role
256+ // router and lands on the processor.
257+ it ( "returns to the stashed path when there is no ?from=" , async ( ) => {
258+ signedIn ( ) ;
259+ sessionStorage . setItem ( "stirling_post_login_path" , "/compress" ) ;
260+ renderAtLogin ( "" ) ;
261+
262+ await waitFor ( ( ) => {
263+ expect ( mockNavigate ) . toHaveBeenCalledWith ( "/compress" , {
264+ replace : true ,
265+ } ) ;
266+ } ) ;
267+ // Consumed, so a later sign-in can't reuse a stale path.
268+ expect ( sessionStorage . getItem ( "stirling_post_login_path" ) ) . toBeNull ( ) ;
269+ } ) ;
270+
250271 // Delegated to the shared isSafePostLoginRedirect, so the backslash form
251272 // (browsers normalise "\" to "/") and auth routes are covered too.
252273 it . each ( [
Original file line number Diff line number Diff line change @@ -7,7 +7,10 @@ import {
77} from "react-router-dom" ;
88import { Button } from "@app/ui/Button" ;
99import { isSafePostLoginRedirect } from "@app/auth" ;
10- import { setPostLoginRedirectPath } from "@app/auth/spring/springAuthClient" ;
10+ import {
11+ setPostLoginRedirectPath ,
12+ consumePostLoginRedirectPath ,
13+ } from "@app/auth/spring/springAuthClient" ;
1114import { useAuth } from "@app/auth/UseSession" ;
1215import { useAppConfig } from "@app/contexts/AppConfigContext" ;
1316import { useTranslation } from "react-i18next" ;
@@ -207,7 +210,8 @@ export default function Login() {
207210 useEffect ( ( ) => {
208211 if ( loading ) return ;
209212 if ( ! session ) return ;
210- const returnPath = resolveReturnPath ( ) ;
213+ const stashed = consumePostLoginRedirectPath ( ) ;
214+ const returnPath = resolveReturnPath ( ) ?? stashed ;
211215 if ( returnPath ) {
212216 navigate ( returnPath , { replace : true } ) ;
213217 return ;
Original file line number Diff line number Diff line change 11import { AxiosInstance , AxiosError , InternalAxiosRequestConfig } from "axios" ;
22import { withBasePath } from "@app/constants/app" ;
33import { getBrowserId } from "@app/utils/browserIdentifier" ;
4+ import { setPostLoginRedirectPath } from "@app/auth/spring/springAuthClient" ;
45
56let isRefreshing = false ;
67let failedQueue : Array < {
@@ -93,6 +94,9 @@ async function refreshAuthToken(client: AxiosInstance): Promise<string> {
9394 // Redirect to login
9495 const loginPath = withBasePath ( "/login" ) ;
9596 if ( window . location . pathname !== loginPath ) {
97+ setPostLoginRedirectPath (
98+ window . location . pathname + window . location . search ,
99+ ) ;
96100 console . log ( "[API Client] Redirecting to login page..." ) ;
97101 window . location . href = loginPath ;
98102 }
You can’t perform that action at this time.
0 commit comments