139 lines
3.3 KiB
TypeScript
Executable file
139 lines
3.3 KiB
TypeScript
Executable file
/**
|
|
* Token storage utilities for managing JWT tokens in localStorage
|
|
* Provides a centralized API for token management across the application
|
|
*
|
|
* All methods are SSR-safe and will return null/false/undefined in SSR context
|
|
*/
|
|
|
|
const ACCESS_TOKEN_KEY = 'auth_token';
|
|
const REFRESH_TOKEN_KEY = 'refresh_token';
|
|
|
|
/**
|
|
* Check if we're in a browser environment
|
|
*/
|
|
function isBrowser(): boolean {
|
|
return typeof window !== 'undefined';
|
|
}
|
|
|
|
export const authStorage = {
|
|
/**
|
|
* Get the access token from localStorage
|
|
*/
|
|
getAccessToken(): string | null {
|
|
if (!isBrowser()) return null;
|
|
return localStorage.getItem(ACCESS_TOKEN_KEY);
|
|
},
|
|
|
|
/**
|
|
* Set the access token in localStorage
|
|
*/
|
|
setAccessToken(token: string): void {
|
|
if (!isBrowser()) return;
|
|
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
|
},
|
|
|
|
/**
|
|
* Get the refresh token from localStorage
|
|
*/
|
|
getRefreshToken(): string | null {
|
|
if (!isBrowser()) return null;
|
|
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
},
|
|
|
|
/**
|
|
* Set the refresh token in localStorage
|
|
*/
|
|
setRefreshToken(token: string): void {
|
|
if (!isBrowser()) return;
|
|
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
},
|
|
|
|
/**
|
|
* Set both tokens at once (used after login/register/refresh)
|
|
*/
|
|
setTokens(accessToken: string, refreshToken: string): void {
|
|
this.setAccessToken(accessToken);
|
|
this.setRefreshToken(refreshToken);
|
|
},
|
|
|
|
/**
|
|
* Remove the access token from localStorage
|
|
*/
|
|
removeAccessToken(): void {
|
|
if (!isBrowser()) return;
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
},
|
|
|
|
/**
|
|
* Remove the refresh token from localStorage
|
|
*/
|
|
removeRefreshToken(): void {
|
|
if (!isBrowser()) return;
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
},
|
|
|
|
/**
|
|
* Remove all auth tokens from localStorage (used on logout)
|
|
*/
|
|
clearTokens(): void {
|
|
this.removeAccessToken();
|
|
this.removeRefreshToken();
|
|
},
|
|
|
|
/**
|
|
* Check if user has valid tokens
|
|
*/
|
|
hasTokens(): boolean {
|
|
if (!isBrowser()) return false;
|
|
return !!this.getAccessToken() && !!this.getRefreshToken();
|
|
},
|
|
|
|
/**
|
|
* Decode JWT token payload (without verification - for client-side only)
|
|
* Returns null if token is invalid or expired
|
|
*/
|
|
decodeToken(token: string): Record<string, any> | null {
|
|
try {
|
|
const parts = token.split('.');
|
|
if (parts.length !== 3) {
|
|
return null;
|
|
}
|
|
|
|
const payload = JSON.parse(atob(parts[1]!));
|
|
|
|
// Check if token is expired
|
|
if (payload.exp && payload.exp * 1000 < Date.now()) {
|
|
return null;
|
|
}
|
|
|
|
return payload;
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get token expiration time in milliseconds
|
|
*/
|
|
getTokenExpiration(token: string): number | null {
|
|
const payload = this.decodeToken(token);
|
|
return payload?.exp ? payload.exp * 1000 : null;
|
|
},
|
|
|
|
/**
|
|
* Check if access token is expired or will expire soon (within 1 minute)
|
|
*/
|
|
isAccessTokenExpired(): boolean {
|
|
const token = this.getAccessToken();
|
|
if (!token) return true;
|
|
|
|
const expiration = this.getTokenExpiration(token);
|
|
if (!expiration) return true;
|
|
|
|
const now = Date.now();
|
|
const oneMinute = 60 * 1000;
|
|
|
|
// Consider token expired if it expires within 1 minute
|
|
return expiration - now < oneMinute;
|
|
},
|
|
};
|