mirror of
https://github.com/inventree/InvenTree.git
synced 2026-11-14 20:42:42 +00:00
fix(frontend): preserve redirect target through SSO login (#13270)
The redirect target set by ProtectedRoute travels in react-router state, which does not survive the full page navigation through the identity provider. After the SSO round trip users always landed on /home. Forward the target through the flow as a 'next' query param on the allauth callback URL instead, and recover it on the /logged-in page. Falls back to /home when no valid in-app path is present. Co-authored-by: Matthias Mair <code@mjmair.com> Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
3 files changed
+33
-8
No files matched your search
@@ -16,6 +16,7 @@ import {
|
||||
|
||||
import type { AuthProvider } from '@lib/types/Auth';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { ProviderLogin } from '../../functions/auth';
|
||||
|
||||
import type { JSX } from 'react';
|
||||
@@ -39,6 +40,8 @@ function getBrandIcon(provider: AuthProvider) {
|
||||
}
|
||||
|
||||
export function SsoButton({ provider }: Readonly<{ provider: AuthProvider }>) {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={t`You will be redirected to the provider for further actions.`}
|
||||
@@ -46,7 +49,7 @@ export function SsoButton({ provider }: Readonly<{ provider: AuthProvider }>) {
|
||||
<Button
|
||||
leftSection={getBrandIcon(provider)}
|
||||
radius='xl'
|
||||
onClick={() => ProviderLogin(provider)}
|
||||
onClick={() => void ProviderLogin(provider, 'login', location?.state)}
|
||||
>
|
||||
{provider.name}
|
||||
</Button>
|
||||
|
||||
@@ -558,9 +558,23 @@ export function clearCsrfCookie() {
|
||||
|
||||
export async function ProviderLogin(
|
||||
provider: AuthProvider,
|
||||
process: 'login' | 'connect' = 'login'
|
||||
process: 'login' | 'connect' = 'login',
|
||||
redirect?: any
|
||||
) {
|
||||
await ensureCsrf();
|
||||
|
||||
// The round-trip through the identity provider is a full page navigation,
|
||||
// so the router state carrying the originally requested URL is lost on
|
||||
// the way back. Forward it through the flow instead, appended to the
|
||||
// callback URL allauth redirects the browser back to - LoggedIn.tsx
|
||||
// picks the 'next' parameter back up once the login completes.
|
||||
const next = `${redirect?.redirectUrl ?? ''}${redirect?.queryParams ?? ''}`;
|
||||
const callback_url =
|
||||
generateUrl(`/${getBaseUrl()}/logged-in`, window.location.origin) +
|
||||
(process === 'login' && next.startsWith('/') && !next.startsWith('//')
|
||||
? `?next=${encodeURIComponent(next)}`
|
||||
: '');
|
||||
|
||||
post(generateUrl(apiUrl(ApiEndpoints.auth_provider_redirect)), {
|
||||
provider: provider.id,
|
||||
// Return to wherever this page is actually being served from, not the
|
||||
@@ -570,10 +584,7 @@ export async function ProviderLogin(
|
||||
// back in server-side by Django's compatibility redirect for bare
|
||||
// (non-API-host) paths, which only exists on the Django side - so add
|
||||
// it explicitly rather than relying on that.
|
||||
callback_url: generateUrl(
|
||||
`/${getBaseUrl()}/logged-in`,
|
||||
window.location.origin
|
||||
),
|
||||
callback_url,
|
||||
process: process,
|
||||
csrfmiddlewaretoken: getCsrfCookie()
|
||||
});
|
||||
|
||||
@@ -27,6 +27,15 @@ function ssoErrorMessage(error: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Recovers the redirect target from the 'next' query param ProviderLogin
|
||||
// appends to the SSO callback URL - router state does not survive the full
|
||||
// page load back from the identity provider.
|
||||
function redirectFromNextParam(next: string | null) {
|
||||
return next?.startsWith('/') && !next.startsWith('//')
|
||||
? { redirectUrl: next }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export default function Logged_In() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -34,6 +43,8 @@ export default function Logged_In() {
|
||||
|
||||
useEffect(() => {
|
||||
const error = searchParams.get('error');
|
||||
const redirect =
|
||||
location?.state ?? redirectFromNextParam(searchParams.get('next'));
|
||||
|
||||
if (error) {
|
||||
showLoginNotification({
|
||||
@@ -41,11 +52,11 @@ export default function Logged_In() {
|
||||
message: ssoErrorMessage(error),
|
||||
success: false
|
||||
});
|
||||
navigate('/login', { state: location?.state });
|
||||
navigate('/login', { state: redirect });
|
||||
return;
|
||||
}
|
||||
|
||||
checkLoginState(navigate, location?.state);
|
||||
void checkLoginState(navigate, redirect);
|
||||
}, [navigate]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user