NeahStable/app/api/auth/service-sso/route.ts
2026-01-10 12:12:30 +01:00

153 lines
4.7 KiB
TypeScript

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 }
);
}
}