export interface AuthenticatedUser { id: string; email: string; username: string; name: string; authMethod: string; groups: string[]; } export interface AuthSession { user: AuthenticatedUser | null; authenticated: boolean; groups: string[]; } /** * Check if the request is authenticated via Keycloak OIDC session */ export const isAuthenticated = async (event: any): Promise => { console.log('[auth] Checking authentication for:', event.node.req.url); // Check OIDC session authentication try { const oidcSession = getCookie(event, 'nuxt-oidc-auth'); console.log('[auth] OIDC session cookie:', oidcSession ? 'present' : 'not found'); if (!oidcSession) { console.log('[auth] No OIDC session found'); return false; } // Parse and validate session data let sessionData; try { sessionData = JSON.parse(oidcSession); } catch (parseError) { console.error('[auth] Failed to parse session cookie:', parseError); return false; } // Validate session structure if (!sessionData.user || !sessionData.accessToken) { console.error('[auth] Invalid session structure:', { hasUser: !!sessionData.user, hasAccessToken: !!sessionData.accessToken }); return false; } // Check if session is still valid if (sessionData.expiresAt && Date.now() > sessionData.expiresAt) { console.log('[auth] Session expired:', { expiresAt: sessionData.expiresAt, currentTime: Date.now(), expiredSince: Date.now() - sessionData.expiresAt }); return false; } console.log('[auth] Valid OIDC session found for user:', { id: sessionData.user.id, email: sessionData.user.email }); return true; } catch (error) { console.error('[auth] OIDC session check failed:', error); return false; } } /** * Get the full authenticated session with user and groups */ export const getAuthSession = async (event: any): Promise => { try { const sessionData = await $fetch('/api/auth/session', { headers: { cookie: getHeader(event, 'cookie') || '' } }) as AuthSession; return sessionData; } catch (error) { console.error('[auth] Failed to get auth session:', error); return { user: null, authenticated: false, groups: [] }; } } /** * Get user groups from the session */ export const getUserGroups = async (event: any): Promise => { const session = await getAuthSession(event); return session.groups || []; } /** * Check if user has specific role/group */ export const hasRole = async (event: any, role: string): Promise => { const groups = await getUserGroups(event); return groups.includes(role); } /** * Check if user has any of the specified roles */ export const hasAnyRole = async (event: any, roles: string[]): Promise => { const groups = await getUserGroups(event); return roles.some(role => groups.includes(role)); } /** * Require authentication and optionally specific roles */ export const requireAuth = async (event: any, requiredRoles?: string[]): Promise => { console.log('[requireAuth] Checking authentication for:', event.node.req.url, 'Required roles:', requiredRoles); // First check for internal API authentication const internalAuth = checkInternalAuth(event); if (internalAuth) { console.log('[requireAuth] Internal API authentication successful'); return { user: { id: 'system', email: 'system@internal', username: 'system', name: 'System', authMethod: 'internal', groups: ['admin'] // Internal calls have admin privileges }, authenticated: true, groups: ['admin'] }; } // Get full session with groups const session = await getAuthSession(event); if (!session.authenticated || !session.user) { console.log('[requireAuth] Authentication failed for:', event.node.req.url); throw createError({ statusCode: 401, statusMessage: "Authentication required. Please login." }); } // Check role requirements if specified if (requiredRoles && requiredRoles.length > 0) { const userGroups = session.groups || []; const hasRequiredRole = requiredRoles.some(role => userGroups.includes(role)); if (!hasRequiredRole) { console.log('[requireAuth] Access denied. User groups:', userGroups, 'Required roles:', requiredRoles); throw createError({ statusCode: 403, statusMessage: 'Insufficient permissions. This action requires one of the following roles: ' + requiredRoles.join(', ') }); } } console.log('[requireAuth] Authentication successful for user:', session.user.email, 'Groups:', session.groups); return session; } /** * Check if the request is from an internal service/background task */ const checkInternalAuth = (event: any): boolean => { const headers = event.node.req.headers; // Check for internal service header with the system tag const xTag = headers['x-tag']; const xInternalSecret = headers['x-internal-secret']; // System tag authentication (for background tasks) if (xTag === '094ut234') { console.log('[auth] Internal system tag authentication successful'); return true; } // Internal secret authentication (if set in environment) const internalSecret = process.env.INTERNAL_API_SECRET; if (internalSecret && xInternalSecret === internalSecret) { console.log('[auth] Internal secret authentication successful'); return true; } // Check if request is from localhost (same container) const xForwardedFor = headers['x-forwarded-for']; const xRealIp = headers['x-real-ip']; const remoteAddress = event.node.req.socket?.remoteAddress; const isLocalhost = !xForwardedFor && !xRealIp && (remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1'); if (isLocalhost && xTag) { console.log('[auth] Localhost with system tag authentication successful'); return true; } return false; } /** * Get the authenticated user from the session */ export const getAuthenticatedUser = async (event: any): Promise => { try { const session = await getAuthSession(event); return session.user; } catch (error) { console.error('[getAuthenticatedUser] Error:', error); return null; } } /** * Authorization helper functions for common roles */ export const requireAdmin = async (event: any): Promise => { return requireAuth(event, ['admin']); } export const requireSalesOrAdmin = async (event: any): Promise => { return requireAuth(event, ['sales', 'admin']); } export const requireUserOrAbove = async (event: any): Promise => { return requireAuth(event, ['user', 'sales', 'admin']); } function parseCookies(cookieString: string): Record { return cookieString.split(';').reduce((cookies: Record, cookie) => { const [name, value] = cookie.trim().split('='); if (name && value) { cookies[name] = value; } return cookies; }, {}); }