shared/lib/auth/handle-login.ts

65 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// shared/lib/auth/handle-login.ts — OIDC 登录工厂(步骤 1a 新增)
// 生成 PKCE + state + nonce → 构建 Authelia 授权 URL → 设置临时 cookie → 302 跳转
import { NextResponse } from 'next/server'
import { generatePkce, generateState, buildAuthorizeUrl, type OidcClientConfig } from './oidc'
interface HandleOidcLoginConfig {
autheliaUrl: string
clientId: string
clientSecret: string
redirectUri: string
/** 强制重新认证("使用其他账号登录",对应 OIDC prompt=login */
switchUser?: boolean
}
const cookieOpts = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
path: '/',
maxAge: 300, // 5 分钟
} as const
/** OIDC 登录入口。生成 PKCE + nonce → 设置临时 cookie → 重定向到 Authelia */
export async function handleOidcLogin(config: HandleOidcLoginConfig): Promise<NextResponse> {
// 切换账号:通过 Authelia /logout 页面清除 authelia_session
// Authelia 4.38 不支持 /api/oidc/end_session404且 prompt=login&max_age=0
// 均被忽略(仍然跳转同意页面)。唯一可靠方式:先到 Authelia /logout 清除 session
// 用户确认后再回跳本页重新发起 OIDC 授权(此时无 session → Authelia 显示登录页)
if (config.switchUser) {
// 注意rd 参数必须用不带 ?switch=1 的 login/oidc URL否则无限循环
const returnUrl = `${config.redirectUri.replace('/api/auth/callback', '')}/api/auth/login/oidc`
const logoutUrl = `${config.autheliaUrl}/logout?rd=${encodeURIComponent(returnUrl)}`
const response = NextResponse.redirect(logoutUrl)
response.cookies.set('tlyq_session', '', { maxAge: 0, path: '/' })
response.cookies.set('session', '', { maxAge: 0, path: '/' })
return response
}
const { codeVerifier, codeChallenge } = generatePkce()
const state = generateState()
const nonce = generateState()
const oidcConfig: OidcClientConfig = {
autheliaUrl: config.autheliaUrl,
clientId: config.clientId,
clientSecret: config.clientSecret,
redirectUri: config.redirectUri,
}
const authorizeUrl = buildAuthorizeUrl(oidcConfig, {
codeChallenge,
state,
nonce,
})
const response = NextResponse.redirect(authorizeUrl)
// OIDC 临时 cookie5 分钟生命周期)
response.cookies.set('oidc_code_verifier', codeVerifier, cookieOpts)
response.cookies.set('oidc_state', state, cookieOpts)
response.cookies.set('oidc_nonce', nonce, cookieOpts)
return response
}