94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { encode } from 'next-auth/jwt'
|
||
import { db } from '@/server/db'
|
||
import { authOptions, buildUserJwtPayload, userAuthInclude } from '@/server/auth'
|
||
import { validateIaaaToken } from '@/server/service/iaaa'
|
||
import { clearSessionInvalidation } from '@/server/service/session'
|
||
|
||
/**
|
||
* IAAA 统一认证回调路由
|
||
*
|
||
* IAAA 认证成功后会 302 重定向到此路由,携带 token 参数。
|
||
* 本路由验证 token、查找本地用户、生成 JWT session cookie,然后重定向到首页。
|
||
*/
|
||
export async function GET(req: NextRequest) {
|
||
const token = req.nextUrl.searchParams.get('token')
|
||
const loginUrl = new URL('/login', req.url)
|
||
|
||
if (!token) {
|
||
loginUrl.searchParams.set('iaaa_error', 'iaaa_no_token')
|
||
return NextResponse.redirect(loginUrl)
|
||
}
|
||
|
||
// 获取客户端真实 IP(反向代理场景取 X-Forwarded-For 第一个值)
|
||
const remoteAddr =
|
||
req.headers.get('x-forwarded-for')?.split(',')[0].trim() ||
|
||
req.headers.get('x-real-ip') ||
|
||
'127.0.0.1'
|
||
|
||
// 调用 IAAA 验证
|
||
const userInfo = await validateIaaaToken(token, remoteAddr)
|
||
if (!userInfo) {
|
||
loginUrl.searchParams.set('iaaa_error', 'iaaa_validate_failed')
|
||
return NextResponse.redirect(loginUrl)
|
||
}
|
||
|
||
// 用 identityId(学号/职工号)匹配本地用户
|
||
const user = await db.user.findUnique({
|
||
where: { id: userInfo.identityId },
|
||
include: userAuthInclude,
|
||
})
|
||
|
||
if (!user) {
|
||
console.warn(`[IAAA] 用户 ${userInfo.identityId}(${userInfo.name})在系统中不存在`)
|
||
loginUrl.searchParams.set('iaaa_error', 'iaaa_user_not_found')
|
||
return NextResponse.redirect(loginUrl)
|
||
}
|
||
|
||
// 更新最近登录时间
|
||
await db.user.update({
|
||
where: { id: user.id },
|
||
data: { lastLoginAt: new Date() },
|
||
})
|
||
|
||
// 清除会话失效标记
|
||
await clearSessionInvalidation(user.id)
|
||
|
||
// 构建 JWT payload(与密码登录完全一致)
|
||
const payload = buildUserJwtPayload(user)
|
||
|
||
// 生成 next-auth 兼容的 JWT
|
||
const secret = authOptions.secret || process.env.NEXTAUTH_SECRET
|
||
if (!secret) {
|
||
console.error('[IAAA] NEXTAUTH_SECRET 未配置')
|
||
loginUrl.searchParams.set('iaaa_error', 'iaaa_server_error')
|
||
return NextResponse.redirect(loginUrl)
|
||
}
|
||
|
||
const maxAge = authOptions.session?.maxAge ?? 30 * 24 * 60 * 60
|
||
const encodedToken = await encode({
|
||
token: { ...payload, sub: payload.id },
|
||
secret,
|
||
maxAge,
|
||
})
|
||
|
||
// 设置 session cookie 并重定向到首页
|
||
// next-auth 在 HTTPS 环境下使用 __Secure- 前缀
|
||
const useSecureCookie = req.nextUrl.protocol === 'https:'
|
||
const cookieName = useSecureCookie
|
||
? '__Secure-next-auth.session-token'
|
||
: 'next-auth.session-token'
|
||
|
||
const response = NextResponse.redirect(new URL('/', req.url))
|
||
response.cookies.set(cookieName, encodedToken, {
|
||
httpOnly: true,
|
||
secure: useSecureCookie,
|
||
sameSite: 'lax',
|
||
path: '/',
|
||
maxAge,
|
||
})
|
||
|
||
console.info(`[IAAA] 登录成功: ${userInfo.identityId}(${userInfo.name})`)
|
||
return response
|
||
}
|