2025-11-10 17:25:56 +09:00
|
|
|
/**
|
|
|
|
|
* Token Refresh Utility
|
|
|
|
|
*
|
|
|
|
|
* Handles automatic token refresh logic for client and server
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Client-side token refresh
|
|
|
|
|
* Call this when you receive 401 errors from protected API calls
|
|
|
|
|
*/
|
|
|
|
|
export async function refreshTokenClient(): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/auth/refresh', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'include', // Include HttpOnly cookies
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
// If refresh failed and needs re-authentication
|
|
|
|
|
if (data.needsReauth) {
|
|
|
|
|
console.warn('🔄 Token refresh failed - redirecting to login');
|
|
|
|
|
window.location.href = '/login';
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('✅ Token refreshed successfully');
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('❌ Token refresh error:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Server-side token refresh (for API routes and middleware)
|
|
|
|
|
*/
|
|
|
|
|
export async function refreshTokenServer(refreshToken: string): Promise<{
|
|
|
|
|
success: boolean;
|
|
|
|
|
accessToken?: string;
|
|
|
|
|
newRefreshToken?: string;
|
|
|
|
|
expiresIn?: number;
|
|
|
|
|
}> {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/refresh`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'Authorization': `Bearer ${refreshToken}`,
|
2025-12-20 14:33:11 +09:00
|
|
|
'X-API-KEY': process.env.API_KEY || '',
|
2025-11-10 17:25:56 +09:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
return { success: false };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
accessToken: data.access_token,
|
|
|
|
|
newRefreshToken: data.refresh_token,
|
|
|
|
|
expiresIn: data.expires_in,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Token refresh error:', error);
|
|
|
|
|
return { success: false };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if token is expired or about to expire (within 5 minutes)
|
|
|
|
|
* @param expiresAt - Expiration timestamp from backend (e.g., "2025-11-10 15:49:38")
|
|
|
|
|
*/
|
|
|
|
|
export function shouldRefreshToken(expiresAt: string | null): boolean {
|
|
|
|
|
if (!expiresAt) return true; // If no expiration info, assume expired
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const expiryTime = new Date(expiresAt).getTime();
|
|
|
|
|
const currentTime = Date.now();
|
|
|
|
|
const fiveMinutes = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
|
|
|
|
|
|
|
|
// Refresh if token expires in less than 5 minutes
|
|
|
|
|
return (expiryTime - currentTime) < fiveMinutes;
|
|
|
|
|
} catch {
|
|
|
|
|
return true; // If parsing fails, assume expired
|
|
|
|
|
}
|
|
|
|
|
}
|