116 lines
3.6 KiB
TypeScript
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')
|
|
}
|
|
})
|