import { keycloakClient } from '~/server/utils/keycloak-client' export default defineEventHandler(async (event) => { const startTime = Date.now() const requestId = Math.random().toString(36).substring(7) console.log(`[REFRESH:${requestId}] Processing token refresh request`) try { // Get current session const oidcSession = getCookie(event, 'nuxt-oidc-auth') if (!oidcSession) { console.error(`[REFRESH:${requestId}] No session found`) throw createError({ statusCode: 401, statusMessage: 'No session found' }) } let sessionData try { sessionData = JSON.parse(oidcSession) } catch (parseError) { console.error(`[REFRESH:${requestId}] Failed to parse session:`, parseError) throw createError({ statusCode: 401, statusMessage: 'Invalid session format' }) } // Check if we have a refresh token if (!sessionData.refreshToken) { console.error(`[REFRESH:${requestId}] No refresh token available`) throw createError({ statusCode: 401, statusMessage: 'No refresh token available' }) } // Validate environment variables const clientSecret = process.env.KEYCLOAK_CLIENT_SECRET if (!clientSecret) { console.error(`[REFRESH:${requestId}] KEYCLOAK_CLIENT_SECRET not configured`) throw createError({ statusCode: 500, statusMessage: 'Authentication service misconfigured' }) } // Use refresh token to get new access token with enhanced error handling console.log(`[REFRESH:${requestId}] Using Keycloak client for token refresh...`) const tokenResponse = await keycloakClient.refreshAccessToken(sessionData.refreshToken) .catch((error: any) => { // Check if it's a transient error if (error.statusMessage === 'KEYCLOAK_TEMPORARILY_UNAVAILABLE') { console.log(`[REFRESH:${requestId}] Keycloak temporarily unavailable, using grace period`) // Return current session with extended grace period return { success: true, expiresAt: sessionData.expiresAt, gracePeriod: true } } throw error // Re-throw for permanent failures }) const refreshDuration = Date.now() - startTime console.log(`[REFRESH:${requestId}] Token refresh successful in ${refreshDuration}ms:`, { hasAccessToken: !!tokenResponse.access_token, hasRefreshToken: !!tokenResponse.refresh_token, expiresIn: tokenResponse.expires_in, gracePeriod: tokenResponse.gracePeriod }) // Handle grace period response if (tokenResponse.gracePeriod) { console.log(`[REFRESH:${requestId}] Using grace period - session extended`) return { success: true, expiresAt: tokenResponse.expiresAt, gracePeriod: true } } // Update session with new tokens const updatedSessionData = { ...sessionData, accessToken: tokenResponse.access_token, refreshToken: tokenResponse.refresh_token || sessionData.refreshToken, // Keep old refresh token if new one not provided expiresAt: Date.now() + (tokenResponse.expires_in * 1000), refreshedAt: Date.now() } // Set updated session cookie with proper session duration const sessionDuration = 8 * 60 * 60; // 8 hours in seconds const cookieDomain = process.env.COOKIE_DOMAIN || '.portnimara.dev'; setCookie(event, 'nuxt-oidc-auth', JSON.stringify(updatedSessionData), { httpOnly: true, secure: true, sameSite: 'lax', maxAge: sessionDuration, domain: cookieDomain, path: '/' }) console.log(`[REFRESH:${requestId}] Session updated successfully`) return { success: true, expiresAt: updatedSessionData.expiresAt } } catch (error: any) { console.error(`[REFRESH:${requestId}] Token refresh failed:`, error) // Only clear session for permanent failures if (error.statusMessage === 'REFRESH_TOKEN_INVALID') { console.log(`[REFRESH:${requestId}] Clearing session due to invalid refresh token`) const cookieDomain = process.env.COOKIE_DOMAIN || '.portnimara.dev'; deleteCookie(event, 'nuxt-oidc-auth', { domain: cookieDomain, path: '/' }) } throw createError({ statusCode: 401, statusMessage: 'Token refresh failed - please login again' }) } })