flnxi-Wu/lib/auth.ts
2026-05-25 16:20:26 +08:00

142 lines
4.8 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 { NextAuthOptions } from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import { prisma } from '@/lib/prisma'
import { consumeDingtalkLoginToken } from '@/lib/dingtalk-login-token'
import bcrypt from 'bcryptjs'
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
id: 'credentials',
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
dingtalkToken: { label: 'Dingtalk Token', type: 'text' },
},
async authorize(credentials) {
const dingtalkToken = credentials?.dingtalkToken?.trim()
if (dingtalkToken) {
const user = await consumeDingtalkLoginToken(dingtalkToken, 'login')
if (!user) {
throw new Error('钉钉登录凭证已失效,请重新扫码')
}
const settings = await prisma.systemSettings.findFirst()
if (settings?.enableUserActivation && !user.isActive && user.role !== 'ADMIN' && !(user as any).isSuperAdmin) {
throw new Error('账号未激活,请联系管理员')
}
return {
id: user.id,
email: user.email,
name: user.username,
role: user.role as "ADMIN" | "USER",
isSuperAdmin: (user as any).isSuperAdmin,
avatar: user.avatar,
}
}
if (!credentials?.email || !credentials?.password) {
throw new Error('请输入邮箱和密码')
}
const user = await prisma.user.findUnique({
where: { email: credentials.email },
})
if (!user) {
throw new Error('用户不存在')
}
const isPasswordValid = await bcrypt.compare(
credentials.password,
user.password
)
if (!isPasswordValid) {
throw new Error('密码错误')
}
// 检查用户是否已激活(如果启用了激活功能且用户不是管理员或超级管理员)
const settings = await prisma.systemSettings.findFirst()
if (settings?.enableUserActivation && !user.isActive && user.role !== 'ADMIN' && !(user as any).isSuperAdmin) {
throw new Error('账号未激活,请联系管理员')
}
return {
id: user.id,
email: user.email,
name: user.username,
role: user.role as "ADMIN" | "USER",
isSuperAdmin: (user as any).isSuperAdmin,
avatar: user.avatar,
}
},
}),
],
callbacks: {
async jwt({ token, user, trigger }) {
// 登录时,从 user 对象获取信息
if (user) {
token.role = (user as any).role;
token.id = user.id;
(token as any).isSuperAdmin = (user as any).isSuperAdmin || false;
(token as any).avatar = (user as any).avatar;
(token as any).avatarUpdatedAt = Date.now();
}
// 如果 token 中有 id,定期从数据库刷新用户信息(包括头像)
// 每5分钟刷新一次,或者如果 trigger 是 'update'(手动刷新)
if (token.id && typeof token.id === 'string') {
const avatarUpdatedAt = (token as any).avatarUpdatedAt || 0;
const now = Date.now();
const refreshInterval = 5 * 60 * 1000; // 5分钟
// 如果超过刷新间隔未刷新,或者手动触发更新,则从数据库获取最新信息
if (trigger === 'update' || (now - avatarUpdatedAt) > refreshInterval) {
try {
const dbUser = await prisma.user.findUnique({
where: { id: token.id as string },
select: {
avatar: true,
role: true,
isSuperAdmin: true,
},
});
if (dbUser) {
(token as any).avatar = dbUser.avatar;
(token as any).role = dbUser.role;
(token as any).isSuperAdmin = (dbUser as any).isSuperAdmin || false;
(token as any).avatarUpdatedAt = now;
}
} catch (error) {
// 如果查询失败,继续使用 token 中的旧值
console.error('[Auth] Failed to refresh user info:', error);
}
}
}
return token;
},
async session({ session, token }) {
if (session?.user && token) {
(session.user as any).role = token.role || 'USER';
(session.user as any).id = token.id;
(session.user as any).isSuperAdmin = (token as any).isSuperAdmin || false;
(session.user as any).avatar = (token as any).avatar;
}
return session;
},
},
pages: {
signIn: '/login',
signOut: '/login',
},
session: {
strategy: 'jwt',
},
secret: process.env.NEXTAUTH_SECRET,
}