133 lines
4.3 KiB
TypeScript
133 lines
4.3 KiB
TypeScript
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'
|
|
})
|
|
}
|
|
})
|