port-nimara-client-portal/server/api/auth/keycloak/callback.ts

116 lines
3.6 KiB
TypeScript

export default defineEventHandler(async (event) => {
const query = getQuery(event)
const { code, state, error } = query
console.log('[KEYCLOAK] Callback received:', { code: !!code, state, error })
if (error) {
console.error('[KEYCLOAK] OAuth error:', error)
throw createError({
statusCode: 400,
statusMessage: `Authentication failed: ${error}`
})
}
if (!code) {
console.error('[KEYCLOAK] No authorization code received')
throw createError({
statusCode: 400,
statusMessage: 'No authorization code received'
})
}
try {
// Validate environment variables
const clientSecret = process.env.KEYCLOAK_CLIENT_SECRET
if (!clientSecret) {
console.error('[KEYCLOAK] KEYCLOAK_CLIENT_SECRET not configured')
throw createError({
statusCode: 500,
statusMessage: 'Authentication service misconfigured'
})
}
// Exchange authorization code for tokens
const tokenResponse = await $fetch('https://auth.portnimara.dev/realms/client-portal/protocol/openid-connect/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'client-portal',
client_secret: clientSecret,
code: code as string,
redirect_uri: 'https://client.portnimara.dev/api/auth/keycloak/callback'
}).toString()
}) as any
console.log('[KEYCLOAK] Token exchange successful:', {
hasAccessToken: !!tokenResponse.access_token,
hasRefreshToken: !!tokenResponse.refresh_token,
expiresIn: tokenResponse.expires_in
})
// Get user info
const userInfo = await $fetch('https://auth.portnimara.dev/realms/client-portal/protocol/openid-connect/userinfo', {
headers: {
'Authorization': `Bearer ${tokenResponse.access_token}`
}
}) as any
console.log('[KEYCLOAK] User info retrieved:', {
sub: userInfo.sub,
email: userInfo.email,
username: userInfo.preferred_username,
name: userInfo.name
})
// Set session cookie with proper configuration
const sessionData = {
user: {
id: userInfo.sub,
email: userInfo.email,
username: userInfo.preferred_username || userInfo.email,
name: userInfo.name || userInfo.preferred_username || userInfo.email,
authMethod: 'keycloak'
},
accessToken: tokenResponse.access_token,
refreshToken: tokenResponse.refresh_token,
expiresAt: Date.now() + (tokenResponse.expires_in * 1000),
createdAt: Date.now()
}
// Create session cookie with proper session duration (8 hours = 28800 seconds)
// Not tied to access token lifetime since we'll refresh tokens automatically
const sessionDuration = 8 * 60 * 60; // 8 hours in seconds
const cookieDomain = process.env.COOKIE_DOMAIN || '.portnimara.dev';
setCookie(event, 'nuxt-oidc-auth', JSON.stringify(sessionData), {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: sessionDuration,
domain: cookieDomain,
path: '/'
})
console.log('[KEYCLOAK] Session cookie set successfully')
console.log('[KEYCLOAK] Redirecting to dashboard...')
// Redirect to dashboard
await sendRedirect(event, '/dashboard')
} catch (error: any) {
console.error('[KEYCLOAK] Token exchange failed:', error)
console.error('[KEYCLOAK] Error details:', {
message: error.message,
status: error.status,
data: error.data
})
// Redirect to login with error
await sendRedirect(event, '/login?error=auth_failed')
}
})