diff --git a/Cargo.lock b/Cargo.lock index 4a395e1..5c41032 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2206,7 +2206,7 @@ dependencies = [ [[package]] name = "pm-agent-client" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "chrono", @@ -2223,7 +2223,7 @@ dependencies = [ [[package]] name = "pm-auth" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "argon2", @@ -2250,7 +2250,7 @@ dependencies = [ [[package]] name = "pm-ca" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "chrono", @@ -2273,7 +2273,7 @@ dependencies = [ [[package]] name = "pm-core" -version = "0.1.1" +version = "0.1.2" dependencies = [ "aes-gcm", "anyhow", @@ -2297,7 +2297,7 @@ dependencies = [ [[package]] name = "pm-reports" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "chrono", @@ -2318,7 +2318,7 @@ dependencies = [ [[package]] name = "pm-web" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "axum", @@ -2354,7 +2354,7 @@ dependencies = [ [[package]] name = "pm-worker" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "chrono", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5968fc0..fc8a58b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,6 +2,7 @@ import { Routes, Route, Navigate } from 'react-router-dom' import { CssBaseline, ThemeProvider } from '@mui/material' import { darkTheme } from './theme/theme' import { useAuthStore } from './store/authStore' +import { CircularProgress, Box } from '@mui/material' import AppLayout from './components/AppLayout' import LoginPage from './pages/LoginPage' import MfaSetupPage from './pages/MfaSetupPage' @@ -19,6 +20,16 @@ import SettingsPage from './pages/SettingsPage' function RequireAuth({ children }: { children: React.ReactNode }) { const isAuthenticated = useAuthStore((s) => s.isAuthenticated) + const isRestoring = useAuthStore((s) => s.isRestoring) + + if (isRestoring) { + return ( + + + + ) + } + return isAuthenticated ? <>{children} : } diff --git a/frontend/src/store/authStore.ts b/frontend/src/store/authStore.ts index 18fb275..d0812a3 100644 --- a/frontend/src/store/authStore.ts +++ b/frontend/src/store/authStore.ts @@ -1,3 +1,4 @@ +import axios from 'axios' import { create } from 'zustand' import { persist } from 'zustand/middleware' import type { User } from '../types' @@ -7,6 +8,7 @@ interface AuthState { refreshToken: string | null user: User | null isAuthenticated: boolean + isRestoring: boolean setTokens: (access: string, refresh: string) => void setUser: (user: User) => void logout: () => void @@ -19,6 +21,7 @@ export const useAuthStore = create()( refreshToken: null, user: null, isAuthenticated: false, + isRestoring: true, setTokens: (access, refresh) => set({ accessToken: access, refreshToken: refresh, isAuthenticated: true }), @@ -26,12 +29,41 @@ export const useAuthStore = create()( setUser: (user) => set({ user }), logout: () => - set({ accessToken: null, refreshToken: null, user: null, isAuthenticated: false }), + set({ accessToken: null, refreshToken: null, user: null, isAuthenticated: false, isRestoring: false }), }), { name: 'pm-auth', // Only persist refresh token; access token regenerated on load partialize: (state) => ({ refreshToken: state.refreshToken, user: state.user }), + onRehydrateStorage: () => { + return (state) => { + if (state?.refreshToken) { + // Proactively refresh the access token using the persisted refresh token + axios.post('/api/v1/auth/refresh', { refresh_token: state.refreshToken }) + .then(({ data }) => { + useAuthStore.setState({ + accessToken: data.access_token, + refreshToken: data.refresh_token, + isAuthenticated: true, + isRestoring: false, + }) + }) + .catch(() => { + // Refresh token expired or invalid — clear all auth state + useAuthStore.setState({ + accessToken: null, + refreshToken: null, + user: null, + isAuthenticated: false, + isRestoring: false, + }) + }) + } else { + // No refresh token — not logged in, skip restoration + useAuthStore.setState({ isRestoring: false }) + } + } + }, } ) )