diff --git a/decnet_web/src/App.tsx b/decnet_web/src/App.tsx index 8748ef2..5ff438c 100644 --- a/decnet_web/src/App.tsx +++ b/decnet_web/src/App.tsx @@ -9,15 +9,30 @@ import Attackers from './components/Attackers'; import Config from './components/Config'; import Bounty from './components/Bounty'; +function isTokenValid(token: string): boolean { + try { + const payload = JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))); + return typeof payload.exp === 'number' && payload.exp * 1000 > Date.now(); + } catch { + return false; + } +} + +function getValidToken(): string | null { + const stored = localStorage.getItem('token'); + if (stored && isTokenValid(stored)) return stored; + if (stored) localStorage.removeItem('token'); + return null; +} + function App() { - const [token, setToken] = useState(localStorage.getItem('token')); + const [token, setToken] = useState(getValidToken); const [searchQuery, setSearchQuery] = useState(''); useEffect(() => { - const savedToken = localStorage.getItem('token'); - if (savedToken) { - setToken(savedToken); - } + const onAuthLogout = () => setToken(null); + window.addEventListener('auth:logout', onAuthLogout); + return () => window.removeEventListener('auth:logout', onAuthLogout); }, []); const handleLogin = (newToken: string) => { diff --git a/decnet_web/src/utils/api.ts b/decnet_web/src/utils/api.ts index 315653a..bc987a5 100644 --- a/decnet_web/src/utils/api.ts +++ b/decnet_web/src/utils/api.ts @@ -12,4 +12,15 @@ api.interceptors.request.use((config) => { return config; }); +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('token'); + window.dispatchEvent(new Event('auth:logout')); + } + return Promise.reject(error); + } +); + export default api;