import type { KeycloakAdminConfig } from '~/utils/types'; export class KeycloakAdminClient { private config: KeycloakAdminConfig; constructor(config: KeycloakAdminConfig) { this.config = config; } /** * Get an admin access token using client credentials grant */ async getAdminToken(): Promise { const response = await fetch(`${this.config.issuer}/protocol/openid-connect/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'MonacoUSA-Portal/1.0' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: this.config.clientId, client_secret: this.config.clientSecret }) }); if (!response.ok) { const errorText = await response.text().catch(() => 'Unknown error'); throw new Error(`Failed to get admin token: ${response.status} - ${errorText}`); } const tokenData = await response.json(); return tokenData.access_token; } /** * Find a user by email address */ async findUserByEmail(email: string, adminToken: string): Promise { const adminBaseUrl = this.config.issuer.replace('/realms/', '/admin/realms/'); const response = await fetch(`${adminBaseUrl}/users?email=${encodeURIComponent(email)}&exact=true`, { headers: { 'Authorization': `Bearer ${adminToken}`, 'User-Agent': 'MonacoUSA-Portal/1.0' } }); if (!response.ok) { const errorText = await response.text().catch(() => 'Unknown error'); throw new Error(`Failed to search users: ${response.status} - ${errorText}`); } return response.json(); } /** * Send password reset email to a user */ async sendPasswordResetEmail(userId: string, adminToken: string, portalClientId: string, callbackUrl: string): Promise { const adminBaseUrl = this.config.issuer.replace('/realms/', '/admin/realms/'); const resetUrl = new URL(`${adminBaseUrl}/users/${userId}/execute-actions-email`); // Add query parameters for better email template rendering resetUrl.searchParams.set('clientId', portalClientId); resetUrl.searchParams.set('redirectUri', callbackUrl.replace('/auth/callback', '/login')); resetUrl.searchParams.set('lifespan', '43200'); // 12 hours // Create AbortController for timeout handling const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout try { const response = await fetch(resetUrl.toString(), { method: 'PUT', headers: { 'Authorization': `Bearer ${adminToken}`, 'Content-Type': 'application/json', 'User-Agent': 'MonacoUSA-Portal/1.0' }, body: JSON.stringify(['UPDATE_PASSWORD']), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { const errorText = await response.text().catch(() => 'Unknown error'); throw new Error(`Failed to send reset email: ${response.status} - ${errorText}`); } } catch (error) { clearTimeout(timeoutId); throw error; } } } export function createKeycloakAdminClient(): KeycloakAdminClient { const config = useRuntimeConfig() as any; if (!config.keycloakAdmin?.clientId || !config.keycloakAdmin?.clientSecret || !config.keycloak?.issuer) { throw new Error('Missing Keycloak admin configuration'); } return new KeycloakAdminClient({ issuer: config.keycloak.issuer, clientId: config.keycloakAdmin.clientId, clientSecret: config.keycloakAdmin.clientSecret }); }