import { NextResponse } from 'next/server' import { cookies } from 'next/headers' import { getOidcClient, generatePKCE, generateState, generateNonce } from '@/lib/oidc' import { verifySharedJwt } from '@/lib/jwt' export async function GET(request: Request) { const cookieStore = await cookies() const existingSession = cookieStore.get('tlyq_session')?.value const url = new URL(request.url) const switchUser = url.searchParams.get('switch') === '1' // 检查是否已有登录用户 if (existingSession && !switchUser) { const existing = verifySharedJwt(existingSession) if (existing) { return NextResponse.json({ conflict: true, currentUser: existing.username, displayName: existing.displayName, }) } } // 预检 Authelia 健康状态 const autheliaUrl = process.env.AUTHELIA_URL || 'https://sso.tlyq.ai' try { const healthRes = await fetch(`${autheliaUrl}/api/health`, { signal: AbortSignal.timeout(5000), }) if (!healthRes.ok) { return NextResponse.json({ fallback: 'ldap', error: 'Authelia 不可用' }, { status: 503 }) } } catch { return NextResponse.json({ fallback: 'ldap', error: 'Authelia 不可达' }, { status: 503 }) } // 生成 PKCE 参数 const { codeVerifier, codeChallenge } = generatePKCE() const state = generateState() const nonce = generateNonce() // 构建授权 URL const client = await getOidcClient() const authorizationUrl = client.authorizationUrl({ scope: 'openid profile email', state, nonce, code_challenge: codeChallenge, code_challenge_method: 'S256', ...(switchUser && { prompt: 'login' }), }) // 存储到 httpOnly cookie(5 分钟过期) const response = NextResponse.redirect(authorizationUrl) response.cookies.set('oidc_code_verifier', codeVerifier, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 300, path: '/', }) response.cookies.set('oidc_state', state, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 300, path: '/', }) response.cookies.set('oidc_nonce', nonce, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 300, path: '/', }) return response }