/** * Custom OAuth Provider for Authentik * * Este archivo extiende nuxt-auth-utils para soportar Authentik como provider OAuth */ import type { H3Event } from 'h3' import { eventHandler, getQuery, sendRedirect } from 'h3' import { withQuery } from 'ufo' import { defu } from 'defu' import { useRuntimeConfig } from '#imports' export interface OAuthAuthentikConfig { /** * Authentik OAuth Client ID * @default process.env.NUXT_OAUTH_AUTHENTIK_CLIENT_ID */ clientId?: string /** * Authentik OAuth Client Secret * @default process.env.NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET */ clientSecret?: string /** * Authentik Server URL * @default process.env.NUXT_OAUTH_AUTHENTIK_SERVER_URL */ serverUrl?: string /** * Redirect URL * @default process.env.NUXT_OAUTH_AUTHENTIK_REDIRECT_URL */ redirectURL?: string /** * Require email from user, adds the ['email'] scope if not present * @default false */ emailRequired?: boolean /** * Authentik OAuth Scope * @default ['openid', 'profile', 'email'] */ scope?: string[] } export function oauthAuthentikEventHandler({ config, onSuccess, onError, }: { config?: OAuthAuthentikConfig onSuccess: (event: H3Event, result: { user: any; tokens: any }) => Promise | void onError?: (event: H3Event, error: any) => Promise | void }) { return eventHandler(async (event: H3Event) => { const runtimeConfig = useRuntimeConfig(event) // Debug: Log configuration console.log('OAuth Authentik Config:', { clientId: runtimeConfig.oauth.authentik.clientId ? '***' : 'MISSING', clientSecret: runtimeConfig.oauth.authentik.clientSecret ? '***' : 'MISSING', serverUrl: runtimeConfig.oauth.authentik.serverUrl, redirectURL: runtimeConfig.oauth.authentik.redirectURL, }) // Merge config with defaults const authentikConfig = defu(config, { clientId: runtimeConfig.oauth.authentik.clientId, clientSecret: runtimeConfig.oauth.authentik.clientSecret, serverUrl: runtimeConfig.oauth.authentik.serverUrl, redirectURL: runtimeConfig.oauth.authentik.redirectURL, scope: ['openid', 'profile', 'email'], emailRequired: false }) as Required const query = getQuery(event) // Handle callback if (query.code) { try { // Exchange code for tokens const tokenUrl = `${authentikConfig.serverUrl}/application/o/token/` const tokenResponse = await $fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: authentikConfig.clientId, client_secret: authentikConfig.clientSecret, code: query.code as string, redirect_uri: authentikConfig.redirectURL, }), }) const tokens = tokenResponse as any // Get user info const userInfoUrl = `${authentikConfig.serverUrl}/application/o/userinfo/` const user = await $fetch(userInfoUrl, { headers: { Authorization: `Bearer ${tokens.access_token}`, }, }) return onSuccess(event, { user, tokens }) } catch (error: any) { if (onError) return onError(event, error) throw error } } // Initial redirect to Authentik const authorizationUrl = withQuery( `${authentikConfig.serverUrl}/application/o/authorize/`, { client_id: authentikConfig.clientId, redirect_uri: authentikConfig.redirectURL, response_type: 'code', scope: authentikConfig.scope.join(' '), } ) return sendRedirect(event, authorizationUrl) }) } // Export for use in defineOAuthAuthentikEventHandler export const oauth = { authentikEventHandler: oauthAuthentikEventHandler }