import NextAuth from "next-auth" import GitHub from "next-auth/providers/github" import Google from "next-auth/providers/google" import { DrizzleAdapter } from "@auth/drizzle-adapter" import { createDb, Db } from "./db" import { accounts, users, roles, userRoles } from "./schema" import { eq } from "drizzle-orm" import { getRequestContext } from "@cloudflare/next-on-pages" import { Permission, hasPermission, ROLES, Role } from "./permissions" import CredentialsProvider from "next-auth/providers/credentials" import { hashPassword, comparePassword } from "@/lib/utils" import { authSchema, AuthSchema } from "@/lib/validation" import { generateAvatarUrl } from "./avatar" import { getUserId } from "./apiKey" import { verifyTurnstileToken } from "./turnstile" const ROLE_DESCRIPTIONS: Record = { [ROLES.EMPEROR]: "皇帝(网站所有者)", [ROLES.DUKE]: "公爵(超级用户)", [ROLES.KNIGHT]: "骑士(高级用户)", [ROLES.CIVILIAN]: "平民(普通用户)", } const getDefaultRole = async (): Promise => { const defaultRole = await getRequestContext().env.SITE_CONFIG.get("DEFAULT_ROLE") if ( defaultRole === ROLES.DUKE || defaultRole === ROLES.KNIGHT || defaultRole === ROLES.CIVILIAN ) { return defaultRole as Role } return ROLES.CIVILIAN } async function findOrCreateRole(db: Db, roleName: Role) { let role = await db.query.roles.findFirst({ where: eq(roles.name, roleName), }) if (!role) { const [newRole] = await db.insert(roles) .values({ name: roleName, description: ROLE_DESCRIPTIONS[roleName], }) .returning() role = newRole } return role } export async function assignRoleToUser(db: Db, userId: string, roleId: string) { await db.delete(userRoles) .where(eq(userRoles.userId, userId)) await db.insert(userRoles) .values({ userId, roleId, }) } export async function getUserRole(userId: string) { const db = createDb() const userRoleRecords = await db.query.userRoles.findMany({ where: eq(userRoles.userId, userId), with: { role: true }, }) return userRoleRecords[0].role.name } export async function checkPermission(permission: Permission) { const userId = await getUserId() if (!userId) return false const db = createDb() const userRoleRecords = await db.query.userRoles.findMany({ where: eq(userRoles.userId, userId), with: { role: true }, }) const userRoleNames = userRoleRecords.map(ur => ur.role.name) return hasPermission(userRoleNames as Role[], permission) } export const { handlers: { GET, POST }, auth, signIn, signOut } = NextAuth(() => ({ secret: process.env.AUTH_SECRET, adapter: DrizzleAdapter(createDb(), { usersTable: users, accountsTable: accounts, }), providers: [ GitHub({ clientId: process.env.AUTH_GITHUB_ID, clientSecret: process.env.AUTH_GITHUB_SECRET, allowDangerousEmailAccountLinking: true, }), Google({ clientId: process.env.AUTH_GOOGLE_ID, clientSecret: process.env.AUTH_GOOGLE_SECRET, allowDangerousEmailAccountLinking: true, }), CredentialsProvider({ name: "Credentials", credentials: { username: { label: "用户名", type: "text", placeholder: "请输入用户名" }, password: { label: "密码", type: "password", placeholder: "请输入密码" }, }, async authorize(credentials) { if (!credentials) { throw new Error("请输入用户名和密码") } const { username, password, turnstileToken } = credentials as Record let parsedCredentials: AuthSchema try { parsedCredentials = authSchema.parse({ username, password, turnstileToken }) // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { throw new Error("输入格式不正确") } const verification = await verifyTurnstileToken(parsedCredentials.turnstileToken) if (!verification.success) { if (verification.reason === "missing-token") { throw new Error("请先完成安全验证") } throw new Error("安全验证未通过") } const db = createDb() const user = await db.query.users.findFirst({ where: eq(users.username, parsedCredentials.username), }) if (!user) { throw new Error("用户名或密码错误") } const isValid = await comparePassword(parsedCredentials.password, user.password as string) if (!isValid) { throw new Error("用户名或密码错误") } return { ...user, password: undefined, } }, }), ], events: { async signIn({ user }) { if (!user.id) return try { const db = createDb() const existingRole = await db.query.userRoles.findFirst({ where: eq(userRoles.userId, user.id), }) if (existingRole) return const defaultRole = await getDefaultRole() const role = await findOrCreateRole(db, defaultRole) await assignRoleToUser(db, user.id, role.id) } catch (error) { console.error('Error assigning role:', error) } }, }, callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id token.name = user.name || user.username token.username = user.username token.image = user.image || generateAvatarUrl(token.name as string) } return token }, async session({ session, token }) { if (token && session.user) { session.user.id = token.id as string session.user.name = token.name as string session.user.username = token.username as string session.user.image = token.image as string const db = createDb() let userRoleRecords = await db.query.userRoles.findMany({ where: eq(userRoles.userId, session.user.id), with: { role: true }, }) if (!userRoleRecords.length) { const defaultRole = await getDefaultRole() const role = await findOrCreateRole(db, defaultRole) await assignRoleToUser(db, session.user.id, role.id) userRoleRecords = [{ userId: session.user.id, roleId: role.id, createdAt: new Date(), role: role }] } session.user.roles = userRoleRecords.map(ur => ({ name: ur.role.name, })) const userAccounts = await db.query.accounts.findMany({ where: eq(accounts.userId, session.user.id), }) session.user.providers = userAccounts.map(account => account.provider) } return session }, }, session: { strategy: "jwt", }, })) export async function register(username: string, password: string) { const db = createDb() const existing = await db.query.users.findFirst({ where: eq(users.username, username) }) if (existing) { throw new Error("用户名已存在") } const hashedPassword = await hashPassword(password) const [user] = await db.insert(users) .values({ username, password: hashedPassword, }) .returning() return user }