@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
110 lines (85 loc) • 3.71 kB
text/typescript
import debug from 'debug';
import { NextRequest, NextResponse, after } from 'next/server';
import { OAuthHandoffModel } from '@/database/models/oauthHandoff';
import { serverDB } from '@/database/server';
import { correctOIDCUrl } from '@/utils/server/correctOIDCUrl';
const log = debug('lobe-oidc:callback:desktop');
const errorPathname = '/oauth/callback/error';
/**
* 安全地构建重定向URL
*/
const buildRedirectUrl = (req: NextRequest, pathname: string): URL => {
const forwardedHost = req.headers.get('x-forwarded-host');
const requestHost = req.headers.get('host');
const forwardedProto =
req.headers.get('x-forwarded-proto') || req.headers.get('x-forwarded-protocol');
// 确定实际的主机名,提供后备值
const actualHost = forwardedHost || requestHost;
const actualProto = forwardedProto || 'https';
log(
'Building redirect URL - host: %s, proto: %s, pathname: %s',
actualHost,
actualProto,
pathname,
);
// 如果主机名仍然无效,使用req.nextUrl作为后备
if (!actualHost) {
log('Warning: Invalid host detected, using req.nextUrl as fallback');
const fallbackUrl = req.nextUrl.clone();
fallbackUrl.pathname = pathname;
return fallbackUrl;
}
try {
return new URL(`${actualProto}://${actualHost}${pathname}`);
} catch (error) {
log('Error constructing URL, using req.nextUrl as fallback: %O', error);
const fallbackUrl = req.nextUrl.clone();
fallbackUrl.pathname = pathname;
return fallbackUrl;
}
};
export const GET = async (req: NextRequest) => {
try {
const searchParams = req.nextUrl.searchParams;
const code = searchParams.get('code');
const state = searchParams.get('state'); // This `state` is the handoff ID
if (!code || !state || typeof code !== 'string' || typeof state !== 'string') {
log('Missing code or state in form data');
const errorUrl = buildRedirectUrl(req, errorPathname);
errorUrl.searchParams.set('reason', 'invalid_request');
log('Redirecting to error URL: %s', errorUrl.toString());
return NextResponse.redirect(errorUrl);
}
log('Received OIDC callback. state(handoffId): %s', state);
// The 'client' is 'desktop' because this redirect_uri is for the desktop client.
const client = 'desktop';
const payload = { code, state };
const id = state;
const authHandoffModel = new OAuthHandoffModel(serverDB);
await authHandoffModel.create({ client, id, payload });
log('Handoff record created successfully for id: %s', id);
const successUrl = buildRedirectUrl(req, '/oauth/callback/success');
// 添加调试日志
log('Request host header: %s', req.headers.get('host'));
log('Request x-forwarded-host: %s', req.headers.get('x-forwarded-host'));
log('Request x-forwarded-proto: %s', req.headers.get('x-forwarded-proto'));
log('Constructed success URL: %s', successUrl.toString());
const correctedUrl = correctOIDCUrl(req, successUrl);
log('Final redirect URL: %s', correctedUrl.toString());
// cleanup expired
after(async () => {
const cleanedCount = await authHandoffModel.cleanupExpired();
log('Cleaned up %d expired handoff records', cleanedCount);
});
return NextResponse.redirect(correctedUrl);
} catch (error) {
log('Error in OIDC callback: %O', error);
const errorUrl = buildRedirectUrl(req, errorPathname);
errorUrl.searchParams.set('reason', 'internal_error');
if (error instanceof Error) {
errorUrl.searchParams.set('errorMessage', error.message);
}
log('Redirecting to error URL: %s', errorUrl.toString());
return NextResponse.redirect(errorUrl);
}
};