Files
hair-keeper/src/app/api/auth/iaaa/callback/route.ts

94 lines
3.0 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.
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
}