Files
thrilltrack-explorer/lib/services/auth/tokenStorage.ts

142 lines
3.1 KiB
TypeScript

/**
* 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;
}