From aede3bb5bab55cd89bb91e3188865d355cf60f11 Mon Sep 17 00:00:00 2001 From: Sam Firke Date: Thu, 16 Oct 2025 16:33:37 -0400 Subject: [PATCH] fix(auth): redirect anonymous attempts to view dashboard with next (#35345) --- superset-frontend/src/pages/Login/index.tsx | 28 ++++++- superset/views/core.py | 17 ++++- superset/views/error_handling.py | 17 +++-- superset/views/utils.py | 34 ++++++++- tests/integration_tests/dashboard_tests.py | 74 +++++++++++++++++-- .../security/security_rbac_tests.py | 8 +- .../dashboards/superset_factory_util.py | 2 + 7 files changed, 155 insertions(+), 25 deletions(-) diff --git a/superset-frontend/src/pages/Login/index.tsx b/superset-frontend/src/pages/Login/index.tsx index 9bb10dfd1d0..67552a316de 100644 --- a/superset-frontend/src/pages/Login/index.tsx +++ b/superset-frontend/src/pages/Login/index.tsx @@ -27,7 +27,7 @@ import { Typography, Icons, } from '@superset-ui/core/components'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { capitalize } from 'lodash/fp'; import { addDangerToast } from 'src/components/MessageToasts/actions'; import { useDispatch } from 'react-redux'; @@ -82,6 +82,26 @@ export default function Login() { const dispatch = useDispatch(); const bootstrapData = getBootstrapData(); + const nextUrl = useMemo(() => { + try { + const params = new URLSearchParams(window.location.search); + return params.get('next') || ''; + } catch (_error) { + return ''; + } + }, []); + + const loginEndpoint = useMemo( + () => (nextUrl ? `/login/?next=${encodeURIComponent(nextUrl)}` : '/login/'), + [nextUrl], + ); + + const buildProviderLoginUrl = (providerName: string) => { + const base = `/login/${providerName}`; + return nextUrl + ? `${base}${base.includes('?') ? '&' : '?'}next=${encodeURIComponent(nextUrl)}` + : base; + }; const authType: AuthType = bootstrapData.common.conf.AUTH_TYPE; const providers: Provider[] = bootstrapData.common.conf.AUTH_PROVIDERS; @@ -109,7 +129,7 @@ export default function Login() { sessionStorage.setItem('login_attempted', 'true'); // Use standard form submission for Flask-AppBuilder compatibility - SupersetClient.postForm('/login/', values, ''); + SupersetClient.postForm(loginEndpoint, values, ''); }; const getAuthIconElement = ( @@ -146,7 +166,7 @@ export default function Login() { {providers.map((provider: OIDProvider) => ( >