169 lines
5.2 KiB
TypeScript
169 lines
5.2 KiB
TypeScript
/**
|
|
* 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<string, ServiceAuthConfig> = {
|
|
// 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;
|
|
}
|
|
});
|
|
}
|