refactor: RBAC权限系统重构 + UI组件拆分 + 测试修复 + 架构文档
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:
SpecialX
2026-06-16 23:38:33 +08:00
parent 99f116cb64
commit 125f7ec54c
75 changed files with 9480 additions and 3289 deletions

View File

@@ -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
}