/** * Iframe Authentication Service * * Handles SSO token forwarding for embedded iframe services. * Since browsers block third-party cookies, we need to pass authentication * tokens directly to iframe services. */ export interface ServiceAuthConfig { name: string; baseUrl: string; authType: 'oidc-redirect' | 'bearer-token' | 'custom-api' | 'iframe-auth' | 'none'; // OIDC redirect: Redirect to Keycloak with service's client_id // bearer-token: Pass access_token as URL param or header // custom-api: Service has custom auth endpoint // iframe-auth: Service supports postMessage authentication // none: No auth needed or service handles it internally clientId?: string; authEndpoint?: string; tokenParam?: string; } // Service configurations - customize based on your Keycloak client setup export const serviceAuthConfigs: Record = { // NextCloud - supports OIDC and bearer token nextcloud: { name: 'NextCloud', baseUrl: process.env.NEXT_PUBLIC_IFRAME_DRIVE_URL?.split('/apps')[0] || 'https://espace.slm-lab.net', authType: 'oidc-redirect', clientId: 'nextcloud', // Your Keycloak client ID for NextCloud authEndpoint: '/apps/user_oidc/login', }, // RocketChat - supports iframe authentication rocketchat: { name: 'RocketChat', baseUrl: process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0] || 'https://parole.slm-lab.net', authType: 'iframe-auth', authEndpoint: '/api/v1/login', }, // Moodle - supports OIDC moodle: { name: 'Moodle', baseUrl: process.env.NEXT_PUBLIC_IFRAME_LEARN_URL || 'https://apprendre.slm-lab.net', authType: 'oidc-redirect', clientId: 'moodle', authEndpoint: '/auth/oidc/', }, // Penpot - supports OIDC penpot: { name: 'Penpot', baseUrl: process.env.NEXT_PUBLIC_IFRAME_ARTLAB_URL || 'https://artlab.slm-lab.net', authType: 'oidc-redirect', clientId: 'penpot', }, // Open-WebUI - supports Bearer token openwebui: { name: 'Open-WebUI', baseUrl: process.env.NEXT_PUBLIC_IFRAME_AI_ASSISTANT_URL || 'https://alma.slm-lab.net', authType: 'bearer-token', tokenParam: 'token', // or use Authorization header }, // ListMonk - may need custom auth listmonk: { name: 'ListMonk', baseUrl: process.env.NEXT_PUBLIC_IFRAME_THEMESSAGE_URL || 'https://lemessage.slm-lab.net', authType: 'none', // ListMonk admin typically uses basic auth }, // Leantime - already has OIDC login endpoint leantime: { name: 'Leantime', baseUrl: 'https://agilite.slm-lab.net', authType: 'oidc-redirect', clientId: 'leantime', authEndpoint: '/oidc/login', }, // Jitsi - typically uses JWT jitsi: { name: 'Jitsi', baseUrl: process.env.NEXT_PUBLIC_IFRAME_CONFERENCE_URL || 'https://vision.slm-lab.net', authType: 'bearer-token', tokenParam: 'jwt', }, // BookStack - supports OIDC bookstack: { name: 'BookStack', baseUrl: process.env.NEXT_PUBLIC_IFRAME_CHAPTER_URL || 'https://chapitre.slm-lab.net', authType: 'oidc-redirect', clientId: 'bookstack', }, }; /** * Generate an authenticated URL for a service * This builds the appropriate auth URL based on the service type */ export function generateAuthenticatedUrl( serviceName: string, accessToken: string, targetPath: string = '/', keycloakIssuer: string ): string { const config = serviceAuthConfigs[serviceName.toLowerCase()]; if (!config) { console.warn(`No auth config found for service: ${serviceName}`); return targetPath; } switch (config.authType) { case 'oidc-redirect': { // Build Keycloak authorization URL that will redirect to the service const authUrl = new URL(`${keycloakIssuer}/protocol/openid-connect/auth`); authUrl.searchParams.set('client_id', config.clientId || serviceName); authUrl.searchParams.set('redirect_uri', `${config.baseUrl}${config.authEndpoint || '/'}${targetPath}`); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('scope', 'openid profile email'); // Use login_hint to pre-fill username if available // Use prompt=none to attempt silent auth (won't show login page) authUrl.searchParams.set('prompt', 'none'); return authUrl.toString(); } case 'bearer-token': { // Add token as URL parameter const url = new URL(`${config.baseUrl}${targetPath}`); if (config.tokenParam) { url.searchParams.set(config.tokenParam, accessToken); } return url.toString(); } case 'iframe-auth': { // For iframe auth, we'll handle it via postMessage // Return base URL, authentication happens via JS return `${config.baseUrl}${targetPath}`; } case 'custom-api': case 'none': default: return `${config.baseUrl}${targetPath}`; } } /** * Get the auth configuration for a service based on its URL */ export function getServiceConfigByUrl(url: string): ServiceAuthConfig | undefined { const urlObj = new URL(url); const hostname = urlObj.hostname; return Object.values(serviceAuthConfigs).find(config => { try { const configUrl = new URL(config.baseUrl); return configUrl.hostname === hostname; } catch { return false; } }); }