import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth/next'; import { authOptions } from '../options'; import { getServiceConfigByUrl, generateAuthenticatedUrl, serviceAuthConfigs } from '@/lib/services/iframe-auth'; /** * API endpoint to generate authenticated URLs for iframe services * * This endpoint exchanges the user's Keycloak access token for a * service-specific authentication URL that can be used in iframes. * * Usage: * GET /api/auth/service-sso?service=nextcloud&path=/apps/files * GET /api/auth/service-sso?url=https://espace.slm-lab.net/apps/mail */ export async function GET(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session?.accessToken) { return NextResponse.json( { error: 'Unauthorized', message: 'No active session' }, { status: 401 } ); } const searchParams = request.nextUrl.searchParams; const serviceName = searchParams.get('service'); const targetUrl = searchParams.get('url'); const targetPath = searchParams.get('path') || '/'; const keycloakIssuer = process.env.KEYCLOAK_ISSUER; if (!keycloakIssuer) { return NextResponse.json( { error: 'Configuration error', message: 'Keycloak issuer not configured' }, { status: 500 } ); } let authenticatedUrl: string; if (serviceName) { // Get auth URL by service name const config = serviceAuthConfigs[serviceName.toLowerCase()]; if (!config) { return NextResponse.json( { error: 'Unknown service', message: `Service '${serviceName}' not configured`, availableServices: Object.keys(serviceAuthConfigs) }, { status: 400 } ); } authenticatedUrl = generateAuthenticatedUrl( serviceName, session.accessToken, targetPath, keycloakIssuer ); } else if (targetUrl) { // Get auth URL by target URL const config = getServiceConfigByUrl(targetUrl); if (!config) { // Service not configured, return original URL return NextResponse.json({ success: true, url: targetUrl, authType: 'none', message: 'Service not configured for SSO, using original URL' }); } // Extract path from target URL const urlObj = new URL(targetUrl); authenticatedUrl = generateAuthenticatedUrl( config.name.toLowerCase(), session.accessToken, urlObj.pathname + urlObj.search, keycloakIssuer ); } else { return NextResponse.json( { error: 'Missing parameter', message: 'Provide either "service" or "url" parameter', availableServices: Object.keys(serviceAuthConfigs) }, { status: 400 } ); } return NextResponse.json({ success: true, url: authenticatedUrl, expiresIn: 300, // URL is valid for 5 minutes (token expiry) }); } catch (error) { console.error('Error generating service SSO URL:', error); return NextResponse.json( { error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } } /** * POST endpoint for RocketChat iframe authentication * This generates a login token for RocketChat's iframe auth */ export async function POST(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session?.accessToken || !session?.user) { return NextResponse.json( { error: 'Unauthorized', message: 'No active session' }, { status: 401 } ); } const body = await request.json(); const { service } = body; if (service === 'rocketchat') { // For RocketChat, we need to use their OAuth login or iframe auth // This returns the token that can be used with postMessage return NextResponse.json({ success: true, authData: { token: session.accessToken, userId: session.user.id, email: session.user.email, username: session.user.username, }, message: 'Use postMessage to send this to RocketChat iframe' }); } return NextResponse.json( { error: 'Unsupported service for POST', message: 'Use GET for most services' }, { status: 400 } ); } catch (error) { console.error('Error in service SSO POST:', error); return NextResponse.json( { error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } }