mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
Merge branch 'fix/redirect-error' into 'development'
[HOTFIX/FE] Fixing redirect issues See merge request mbugroup/lti-web-client!82
This commit is contained in:
@@ -1,54 +1,46 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ReactNode, useEffect } from 'react';
|
import { ReactNode, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import useSWR from 'swr';
|
||||||
import useSWRImmutable from 'swr/immutable';
|
|
||||||
|
|
||||||
import { useAuth } from '@/services/hooks/useAuth';
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general';
|
import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import { redirectToSSO } from '@/lib/auth-helper';
|
||||||
|
|
||||||
interface RequireAuthProps {
|
interface RequireAuthProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RequireAuth = ({ children }: RequireAuthProps) => {
|
const RequireAuth = ({ children }: RequireAuthProps) => {
|
||||||
const router = useRouter();
|
|
||||||
const { setUser, setIsLoadingUser } = useAuth();
|
const { setUser, setIsLoadingUser } = useAuth();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: userResponse,
|
data: userResponse,
|
||||||
isLoading: isLoadingUserResponse,
|
isLoading: isLoadingUserResponse,
|
||||||
error: userErrorResponse,
|
error: userErrorResponse,
|
||||||
} = useSWRImmutable<
|
} = useSWR<
|
||||||
GetMeResponse & { ok?: boolean },
|
GetMeResponse & { ok?: boolean },
|
||||||
AxiosError<BaseApiResponse>,
|
AxiosError<BaseApiResponse>,
|
||||||
SWRHttpKey
|
SWRHttpKey
|
||||||
>('/sso/userinfo', httpClientFetcher, {
|
>('/sso/userinfo', httpClientFetcher, {
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
revalidateOnFocus: false,
|
|
||||||
revalidateOnReconnect: false,
|
|
||||||
refreshInterval: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsLoadingUser(isLoadingUserResponse);
|
|
||||||
}, [isLoadingUserResponse, setIsLoadingUser]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(userResponse)) {
|
if (isResponseSuccess(userResponse)) {
|
||||||
setUser(userResponse.data);
|
setUser(userResponse.data);
|
||||||
} else if (
|
|
||||||
isResponseError(userErrorResponse?.response?.data) &&
|
|
||||||
typeof window !== 'undefined'
|
|
||||||
) {
|
|
||||||
router.replace(
|
|
||||||
`${process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string}?redirect_url=${window.location.href}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [userResponse, userErrorResponse, setIsLoadingUser, setUser]);
|
}, [userResponse, setUser]);
|
||||||
|
|
||||||
|
// Explicitly handle 401 redirect from the component level
|
||||||
|
useEffect(() => {
|
||||||
|
if (userErrorResponse?.response?.status === 401) {
|
||||||
|
redirectToSSO();
|
||||||
|
}
|
||||||
|
}, [userErrorResponse]);
|
||||||
|
|
||||||
if (isLoadingUserResponse && !userResponse && !userErrorResponse) {
|
if (isLoadingUserResponse && !userResponse && !userErrorResponse) {
|
||||||
return (
|
return (
|
||||||
@@ -58,6 +50,24 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userErrorResponse) {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
|
||||||
|
<h2 className='text-2xl font-bold text-error'>Authentication Failed</h2>
|
||||||
|
<p className='text-gray-600'>
|
||||||
|
Please try refreshing the page or contact support if the problem
|
||||||
|
persists.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='btn btn-primary'
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return <>{isResponseSuccess(userResponse) && children}</>;
|
return <>{isResponseSuccess(userResponse) && children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Redirects the user to the SSO login page with loop protection.
|
||||||
|
*
|
||||||
|
* This function checks a session storage timestamp to ensure that redirects
|
||||||
|
* do not happen too frequently (blocking infinite redirect loops).
|
||||||
|
*/
|
||||||
|
export const redirectToSSO = () => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const lastRedirect = sessionStorage.getItem('auth_redirect_timestamp');
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Loop protection: allow redirect only if last one was > 2 seconds ago
|
||||||
|
// or if no redirect has happened yet.
|
||||||
|
if (!lastRedirect || now - parseInt(lastRedirect, 10) > 2000) {
|
||||||
|
sessionStorage.setItem('auth_redirect_timestamp', now.toString());
|
||||||
|
// const ssoLoginUrl = `${process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string}?redirect_url=${window.location.href}`;
|
||||||
|
|
||||||
|
const ltiSsoStart = `${process.env.NEXT_PUBLIC_API_BASE_URL as string}/sso/start?client_id=${process.env.NEXT_PUBLIC_CLIENT_ID as string}&redirect_url=${window.location.href}`;
|
||||||
|
const ssoLoginUrl = `${process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string}?redirect_url=${ltiSsoStart}`;
|
||||||
|
window.location.href = ssoLoginUrl;
|
||||||
|
} else {
|
||||||
|
console.error('Redirect loop detected. Aborting redirect.');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,6 +2,8 @@ import axios from 'axios';
|
|||||||
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
import { RequestOptions } from '@/services/http/base';
|
import { RequestOptions } from '@/services/http/base';
|
||||||
|
|
||||||
|
import { redirectToSSO } from '@/lib/auth-helper';
|
||||||
|
|
||||||
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
|
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
|
||||||
const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 10_000 });
|
const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 10_000 });
|
||||||
|
|
||||||
@@ -9,8 +11,7 @@ axiosClient.interceptors.response.use(
|
|||||||
(response) => response,
|
(response) => response,
|
||||||
(error: AxiosError) => {
|
(error: AxiosError) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
const ssoLoginUrl = `${process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string}?redirect_url=${window.location.href}`;
|
redirectToSSO();
|
||||||
window.location.href = ssoLoginUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user