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 })
+ }
+ }
+ },
}
)
)