import { query } from '../../utils/db' function parseJwt(token: string) { try { const base64Payload = token.split('.')[1] const payload = Buffer.from(base64Payload, 'base64').toString('utf8') return JSON.parse(payload) } catch { return null } } export default defineEventHandler(async (event) => { const config = useRuntimeConfig(event) const body = await readBody(event) if (!body?.code || !body?.redirectUri) { throw createError({ statusCode: 400, statusMessage: 'Missing code or redirectUri' }) } const tokenUrl = `${config.public.keycloakUrl}/realms/${config.public.keycloakRealm}/protocol/openid-connect/token` const params = new URLSearchParams({ grant_type: 'authorization_code', client_id: config.public.keycloakClientId, code: body.code, redirect_uri: body.redirectUri }) if (config.keycloakClientSecret) { params.append('client_secret', config.keycloakClientSecret) } try { const response = await $fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }) const decodedToken = parseJwt(response.access_token) const realmRoles: string[] = decodedToken?.realm_access?.roles || [] if (!realmRoles.includes('portal-access')) { throw createError({ statusCode: 403, statusMessage: 'Access Denied' }) } const keycloakId = decodedToken?.sub const email = decodedToken?.email let isFirstLogin = false if (keycloakId) { try { const dbResult = await query( `INSERT INTO users (keycloak_id, email) VALUES ($1, $2) ON CONFLICT (keycloak_id) DO UPDATE SET updated_at = CURRENT_TIMESTAMP RETURNING first_login_completed`, [keycloakId, email] ) isFirstLogin = !dbResult.rows[0]?.first_login_completed } catch (dbErr) { console.error('[DB ERROR in token.post.ts]:', dbErr) isFirstLogin = false } } return { ...response, isFirstLogin } } catch (error: any) { console.error('[AUTH ERROR in token.post.ts]:', error?.data || error) throw createError({ statusCode: error.statusCode || error.response?.status || 401, statusMessage: error.data?.message || error.response?._data?.error_description || 'Authentication failed' }) } })