refactor: RBAC权限系统重构 + UI组件拆分 + 测试修复 + 架构文档
Some checks failed
CI / build-deploy (push) Has been cancelled
Some checks failed
CI / build-deploy (push) Has been cancelled
- RBAC: 新增30个权限点、DataScope行级权限、requirePermission守卫,所有57+ Server Action接入权限校验 - UI拆分: exam-form(1623行→11文件)、textbook-reader(744行→7文件),均降至300行以内 - 测试: 新增5个单元测试文件(19用例),修复4个集成测试文件(38用例全部通过) - 架构文档: 新增架构影响地图(004/005)、标准功能清单(006)、差距审计报告(007) - 项目规则: 架构图优先规则,改码必同步图 - 安全: rehype-sanitize净化、AES加密API Key、权限路由守卫 - 无障碍: skip-link、aria-label、prefers-reduced-motion - 性能: next/font优化、next/image、代码分割
This commit is contained in:
28
src/auth.ts
28
src/auth.ts
@@ -1,6 +1,7 @@
|
||||
import { compare } from "bcryptjs"
|
||||
import NextAuth from "next-auth"
|
||||
import Credentials from "next-auth/providers/credentials"
|
||||
import { resolvePermissions } from "@/shared/lib/permissions"
|
||||
|
||||
const normalizeRole = (value: unknown) => {
|
||||
const role = String(value ?? "").trim().toLowerCase()
|
||||
@@ -64,13 +65,15 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
.innerJoin(roles, eq(usersToRoles.roleId, roles.id))
|
||||
.where(eq(usersToRoles.userId, user.id))
|
||||
|
||||
const resolvedRole = resolvePrimaryRole(roleRows.map((r) => r.name))
|
||||
const roleNames = roleRows.map((r) => r.name)
|
||||
const resolvedRole = resolvePrimaryRole(roleNames)
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name ?? undefined,
|
||||
email: user.email,
|
||||
role: resolvedRole,
|
||||
roles: roleNames,
|
||||
}
|
||||
},
|
||||
}),
|
||||
@@ -78,11 +81,17 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }) => {
|
||||
if (user) {
|
||||
token.id = (user as { id: string }).id
|
||||
token.role = normalizeRole((user as { role?: string }).role)
|
||||
token.name = (user as { name?: string }).name
|
||||
const u = user as { id: string; role?: string; roles?: string[]; name?: string }
|
||||
token.id = u.id
|
||||
token.role = normalizeRole(u.role)
|
||||
token.name = u.name ?? undefined
|
||||
// Store all roles (not just primary) and resolved permissions
|
||||
const allRoles = u.roles ?? [u.role ?? "student"]
|
||||
token.roles = allRoles
|
||||
token.permissions = resolvePermissions(allRoles)
|
||||
}
|
||||
|
||||
// Refresh roles/permissions from DB on each JWT refresh
|
||||
const userId = String(token.id ?? "").trim()
|
||||
if (userId) {
|
||||
const [{ eq }, { db }, { roles, users, usersToRoles }] = await Promise.all([
|
||||
@@ -93,8 +102,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
|
||||
const [fresh, roleRows] = await Promise.all([
|
||||
db.query.users.findFirst({
|
||||
where: eq(users.id, userId),
|
||||
columns: { name: true },
|
||||
where: eq(users.id, userId),
|
||||
columns: { name: true },
|
||||
}),
|
||||
db
|
||||
.select({ name: roles.name })
|
||||
@@ -104,8 +113,11 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
])
|
||||
|
||||
if (fresh) {
|
||||
token.role = resolvePrimaryRole(roleRows.map((r) => r.name))
|
||||
const allRoles = roleRows.map((r) => r.name)
|
||||
token.role = resolvePrimaryRole(allRoles)
|
||||
token.name = fresh.name ?? token.name
|
||||
token.roles = allRoles
|
||||
token.permissions = resolvePermissions(allRoles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +127,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
if (session.user) {
|
||||
session.user.id = String(token.id ?? "")
|
||||
session.user.role = normalizeRole(token.role)
|
||||
session.user.roles = (token.roles ?? []) as string[]
|
||||
session.user.permissions = (token.permissions ?? []) as typeof token.permissions
|
||||
if (typeof token.name === "string") {
|
||||
session.user.name = token.name
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user