117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import { NextAuthOptions } from 'next-auth'
|
||
import CredentialsProvider from 'next-auth/providers/credentials'
|
||
import { prisma } from '@/lib/prisma'
|
||
import bcrypt from 'bcryptjs'
|
||
|
||
export const authOptions: NextAuthOptions = {
|
||
providers: [
|
||
CredentialsProvider({
|
||
name: 'Credentials',
|
||
credentials: {
|
||
email: { label: 'Email', type: 'email' },
|
||
password: { label: 'Password', type: 'password' },
|
||
},
|
||
async authorize(credentials) {
|
||
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 fiveMinutes = 5 * 60 * 1000;
|
||
|
||
// 如果超过5分钟未刷新,或者手动触发更新,则从数据库获取最新信息
|
||
if (trigger === 'update' || (now - avatarUpdatedAt) > fiveMinutes) {
|
||
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,
|
||
}
|