feat: implement AuthPageGuard and GlobalCredentialChecker to enforce session management and token validation
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
import { AuthPageGuard } from '../../core/lib/auth-guard';
|
||||||
|
|
||||||
const LoginPage = lazy(() => import('./login'));
|
const LoginPage = lazy(() => import('./login'));
|
||||||
|
|
||||||
export default function AuthModule() {
|
export default function AuthModule() {
|
||||||
return (
|
return (
|
||||||
|
<AuthPageGuard>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/" element={<Navigate to="/auth/login" replace={true} />} />
|
<Route path="/" element={<Navigate to="/auth/login" replace={true} />} />
|
||||||
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
|
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</AuthPageGuard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import ModuleLayout from './layouts/module.layout';
|
import ModuleLayout from './layouts/module.layout';
|
||||||
|
import { GlobalCredentialChecker } from '../../core/lib/auth-guard';
|
||||||
|
|
||||||
const ExampleModule = lazy(() => import('./modules/example'));
|
const ExampleModule = lazy(() => import('./modules/example'));
|
||||||
const SystemSetting = lazy(() => import('./modules/system/setting'));
|
const SystemSetting = lazy(() => import('./modules/system/setting'));
|
||||||
@@ -9,6 +10,7 @@ const SystemNotification = lazy(() => import('./modules/system/notification'));
|
|||||||
|
|
||||||
export default function AppModule() {
|
export default function AppModule() {
|
||||||
return (
|
return (
|
||||||
|
<GlobalCredentialChecker>
|
||||||
<ModuleLayout>
|
<ModuleLayout>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/example/*" element={<ExampleModule />} />
|
<Route path="/example/*" element={<ExampleModule />} />
|
||||||
@@ -19,5 +21,6 @@ export default function AppModule() {
|
|||||||
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
|
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</ModuleLayout>
|
</ModuleLayout>
|
||||||
|
</GlobalCredentialChecker>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { useEffect, useState, ReactNode } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { appDatabase, AppDatabaseKey } from '../storage/local';
|
||||||
|
import { terminateAuthSession } from './auth.helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic JWT decoder to check expiration.
|
||||||
|
*/
|
||||||
|
function isTokenExpired(token: string): boolean {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||||
|
if (payload.exp) {
|
||||||
|
return payload.exp * 1000 < Date.now();
|
||||||
|
}
|
||||||
|
return false; // If no exp, assume valid
|
||||||
|
} catch (e) {
|
||||||
|
return true; // Invalid token format -> treat as expired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Auth Page (Login/Register).
|
||||||
|
* - If valid token exists -> Redirect to main application.
|
||||||
|
* - If expired token exists -> Clear session, stay on auth page.
|
||||||
|
*/
|
||||||
|
export function AuthPageGuard({ children }: { children: ReactNode }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isChecking, setIsChecking] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function checkCredential() {
|
||||||
|
try {
|
||||||
|
const token = await appDatabase.getItem<string>(AppDatabaseKey.ACCESS_TOKEN);
|
||||||
|
if (token) {
|
||||||
|
if (!isTokenExpired(token)) {
|
||||||
|
// Valid token exists, redirect to dashboard/main app
|
||||||
|
navigate('/app', { replace: true });
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Token exists but is expired. Terminate session without redirecting (we are already in Auth).
|
||||||
|
await terminateAuthSession({ preserveRedirect: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsChecking(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check credentials in AuthPageGuard:', error);
|
||||||
|
setIsChecking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCredential();
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
if (isChecking) return null;
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Root/Main App.
|
||||||
|
* - Monitors token status.
|
||||||
|
* - If expired -> Immediately terminate session and redirect to login.
|
||||||
|
*/
|
||||||
|
export function GlobalCredentialChecker({ children }: { children: ReactNode }) {
|
||||||
|
useEffect(() => {
|
||||||
|
let intervalId: any;
|
||||||
|
|
||||||
|
async function checkCredential() {
|
||||||
|
try {
|
||||||
|
const token = await appDatabase.getItem<string>(AppDatabaseKey.ACCESS_TOKEN);
|
||||||
|
if (!token || isTokenExpired(token)) {
|
||||||
|
// Token is missing or expired -> clear session and redirect to login
|
||||||
|
await terminateAuthSession({ preserveRedirect: true });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check credentials in GlobalCredentialChecker:', error);
|
||||||
|
await terminateAuthSession({ preserveRedirect: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediate check on mount
|
||||||
|
checkCredential();
|
||||||
|
|
||||||
|
// Periodic check every 1 minute
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
intervalId = setInterval(checkCredential, 60000);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user