/** * Token Storage Utility * * Handles JWT token storage, retrieval, and validation in localStorage. */ import { TokenPayload, TokenStorage } from '@/lib/types/auth'; // Storage keys const ACCESS_TOKEN_KEY = 'auth_access_token'; const REFRESH_TOKEN_KEY = 'auth_refresh_token'; /** * Decode JWT token payload without verification * (Verification happens on the server) */ function decodeToken(token: string): TokenPayload | null { try { const parts = token.split('.'); if (parts.length !== 3) { return null; } const payload = parts[1]; const decoded = JSON.parse(atob(payload)); return decoded as TokenPayload; } catch (error) { console.error('Failed to decode token:', error); return null; } } /** * Check if a token is expired */ function isTokenExpired(token: string): boolean { const decoded = decodeToken(token); if (!decoded || !decoded.exp) { return true; } // Check if token expires in the next 60 seconds (add buffer) const expiryTime = decoded.exp * 1000; // Convert to milliseconds const now = Date.now(); const bufferTime = 60 * 1000; // 60 seconds buffer return now >= (expiryTime - bufferTime); } /** * Token storage implementation */ export const tokenStorage: TokenStorage = { getAccessToken(): string | null { if (typeof window === 'undefined') { return null; } return localStorage.getItem(ACCESS_TOKEN_KEY); }, setAccessToken(token: string): void { if (typeof window === 'undefined') { return; } localStorage.setItem(ACCESS_TOKEN_KEY, token); }, getRefreshToken(): string | null { if (typeof window === 'undefined') { return null; } return localStorage.getItem(REFRESH_TOKEN_KEY); }, setRefreshToken(token: string): void { if (typeof window === 'undefined') { return; } localStorage.setItem(REFRESH_TOKEN_KEY, token); }, clearTokens(): void { if (typeof window === 'undefined') { return; } localStorage.removeItem(ACCESS_TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY); }, hasValidAccessToken(): boolean { const token = this.getAccessToken(); if (!token) { return false; } return !isTokenExpired(token); }, }; /** * Get the decoded payload from the access token */ export function getTokenPayload(): TokenPayload | null { const token = tokenStorage.getAccessToken(); if (!token) { return null; } return decodeToken(token); } /** * Check if the refresh token is expired */ export function isRefreshTokenExpired(): boolean { const token = tokenStorage.getRefreshToken(); if (!token) { return true; } return isTokenExpired(token); } /** * Get time until access token expires (in milliseconds) */ export function getTimeUntilExpiry(): number | null { const token = tokenStorage.getAccessToken(); if (!token) { return null; } const decoded = decodeToken(token); if (!decoded || !decoded.exp) { return null; } const expiryTime = decoded.exp * 1000; const now = Date.now(); const timeRemaining = expiryTime - now; return timeRemaining > 0 ? timeRemaining : 0; }