fix: discover OIDC metadata server-side to avoid CORS failures #382
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { configManager } from '@/lib/admin/config-manager';
|
||||
import { getMetadata, getRequiredConfig } from '@/lib/oauth/token-exchange';
|
||||
|
||||
/**
|
||||
* Same-origin OAuth metadata (discovery) proxy.
|
||||
*
|
||||
* The login page needs the authorization_endpoint to build the PKCE authorize
|
||||
* URL in the browser. Discovering it directly from the browser means a
|
||||
* cross-origin fetch to the IdP's /.well-known/* documents, which is subject
|
||||
* to CORS: providers like Authentik serve those documents without an
|
||||
* Access-Control-Allow-Origin header, so the browser blocks the response and
|
||||
* discovery fails (issue #382). Performing discovery here - server to server,
|
||||
* where CORS does not apply - and handing the result back as a same-origin
|
||||
* response sidesteps the problem entirely.
|
||||
*
|
||||
* The discovery URL is resolved from admin config (via server_id), never from
|
||||
* client input, so this cannot be abused as an open SSRF proxy. Endpoint URLs
|
||||
* in the discovered document are still gated by the SSRF validator inside
|
||||
* discoverOAuth. The returned fields are public well-known metadata.
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
await configManager.ensureLoaded();
|
||||
const serverId = request.nextUrl.searchParams.get('server_id');
|
||||
|
||||
let discoveryUrl: string;
|
||||
try {
|
||||
({ discoveryUrl } = getRequiredConfig(serverId));
|
||||
} catch {
|
||||
// OAuth not configured for this server - surface as "no metadata" rather
|
||||
// than a 500 so the login page just hides the SSO button.
|
||||
return NextResponse.json({ error: 'OAuth not configured' }, { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const metadata = await getMetadata(serverId);
|
||||
if (!metadata?.authorization_endpoint || !metadata.token_endpoint) {
|
||||
logger.warn('OAuth metadata discovery returned no usable endpoints', { discoveryUrl });
|
||||
return NextResponse.json({ error: 'OAuth discovery failed' }, { status: 502 });
|
||||
}
|
||||
return NextResponse.json(metadata, {
|
||||
// Mirror the in-process discovery cache TTL so repeated login-page loads
|
||||
// hit the CDN/browser cache instead of re-running discovery.
|
||||
headers: { 'Cache-Control': 'private, max-age=600' },
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('OAuth metadata discovery error', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
return NextResponse.json({ error: 'OAuth discovery failed' }, { status: 502 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user