feat: implement refresh token functionality and update local storage management

This commit is contained in:
Usman Baig
2026-02-28 23:02:43 +01:00
parent 5ef6eafc63
commit bce56fa64d
3 changed files with 25 additions and 6 deletions

View File

@@ -51,9 +51,25 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const refreshToken = useCallback(async (): Promise<boolean> => {
try {
const res = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include',
})
if (res.ok) {
localStorage.setItem('ciphera_token_refreshed_at', Date.now().toString())
}
return res.ok
} catch {
return false
}
}, [])
const login = (userData: User) => { const login = (userData: User) => {
// * We still store user profile in localStorage for optimistic UI, but NOT the token // * We still store user profile in localStorage for optimistic UI, but NOT the token
localStorage.setItem('user', JSON.stringify(userData)) localStorage.setItem('user', JSON.stringify(userData))
localStorage.setItem('ciphera_token_refreshed_at', Date.now().toString())
setUser(userData) setUser(userData)
router.refresh() router.refresh()
// * Fetch full profile (including display_name) so header shows correct name without page refresh // * Fetch full profile (including display_name) so header shows correct name without page refresh
@@ -76,6 +92,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setIsLoggingOut(true) setIsLoggingOut(true)
await logoutAction() await logoutAction()
localStorage.removeItem('user') localStorage.removeItem('user')
localStorage.removeItem('ciphera_token_refreshed_at')
localStorage.removeItem('ciphera_last_activity')
// * Broadcast logout to other tabs (BroadcastChannel will handle if available) // * Broadcast logout to other tabs (BroadcastChannel will handle if available)
if (typeof window !== 'undefined' && 'BroadcastChannel' in window) { if (typeof window !== 'undefined' && 'BroadcastChannel' in window) {
const channel = new BroadcastChannel('ciphera_session') const channel = new BroadcastChannel('ciphera_session')
@@ -131,6 +149,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
if (session) { if (session) {
setUser(session) setUser(session)
localStorage.setItem('user', JSON.stringify(session)) localStorage.setItem('user', JSON.stringify(session))
localStorage.setItem('ciphera_token_refreshed_at', Date.now().toString())
// * Fetch full profile (including display_name) from API; preserve org_id/role from session // * Fetch full profile (including display_name) from API; preserve org_id/role from session
try { try {
const userData = await apiRequest<User>('/auth/user/me') const userData = await apiRequest<User>('/auth/user/me')
@@ -221,7 +240,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
{isLoggingOut && <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />} {isLoggingOut && <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />}
<SessionExpiryWarning <SessionExpiryWarning
isAuthenticated={!!user} isAuthenticated={!!user}
onExtendSession={refresh} onRefreshToken={refreshToken}
onExpired={logout} onExpired={logout}
/> />
{children} {children}

8
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "pulse-frontend", "name": "pulse-frontend",
"version": "0.11.1-alpha", "version": "0.11.1-alpha",
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.0.77", "@ciphera-net/ui": "^0.0.78",
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/browser": "^13.2.2",
@@ -1543,9 +1543,9 @@
} }
}, },
"node_modules/@ciphera-net/ui": { "node_modules/@ciphera-net/ui": {
"version": "0.0.77", "version": "0.0.78",
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.77/c243fce3b29ee4b3d90dd5b6b5a7d93ff3a955b9", "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.78/d012b211ddba7a83f7468ec269a842709a2409b9",
"integrity": "sha512-Z+aLef5843t9TIvaSV+ecC4sQi2VzX+hE6/7A/4/Y49CT91vg0x9QuQuQiV7B4j93Ui1yv+aZyWN21NY3mhKbA==", "integrity": "sha512-c9B/cZggjWnCSpICEvRBAAPgsRfjN2j3NFczQRZxRR6sZmzkwd3KzEbDfTEa2DUExBMWB4+bOgiCz+AnP2OR3g==",
"dependencies": { "dependencies": {
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",

View File

@@ -10,7 +10,7 @@
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.0.77", "@ciphera-net/ui": "^0.0.78",
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/browser": "^13.2.2",