feat(authz): 重写权限模板分配与班级授权边界

拆分能力、分配范围和业务关系,固定管理员能力并收紧组织级写入口。
补齐内容、课表、设备会话和实时通道授权,兼容旧库分配及历史快照。
重写权限分配预览,按实际可操作范围展示维护入口,合并学生入口并独立学年升级页。
增加授权攻击矩阵及静态门禁,更新隔离回归与开发契约。

验证:授权矩阵 23 段通过,Node 24 下 Web 63/63,类型检查、lint 与文档严格构建通过。
This commit is contained in:
LoveChengke committed 2026-10-31 09:40:23 +08:00
1 parent 682818dd4a
commit 66be3a63b1
115 files changed
+4463 -2747

No files matched your search

@@ -6,7 +6,7 @@ import { idParamSchema } from '../../lib/schemas.js';
import {
authenticate,
getAuthUser,
requireStaffPermission,
requirePermissionOrClassDevice,
requirePermission,
requireAnyPermission,
} from '../../middleware/auth.js';
@@ -115,7 +115,7 @@ router.post(
const input = validatedBody<TableCommitInput>(req);
await assertImportKindAllowed(input.kind, getAuthUser(req));
// 教师名单与班级无关;成绩/学生名单必须带班级,否则拒绝(避免写到"全局")
if (input.kind !== 'teachers' && input.kind !== 'classTeachers' && !input.classId) {
if (input.kind === 'grades' && !input.classId) {
throw ApiError.badRequest('请先选择要导入的班级');
}
const result = await commitTable(getAuthUser(req), input);
@@ -147,7 +147,7 @@ router.post(
/** GET /api/imports/time-layout?classId= - 查看该班已导入的时间配置 */
router.get(
'/time-layout',
requireStaffPermission('schedule:read'),
requirePermissionOrClassDevice('schedule:read'),
validate({ query: timeLayoutQuerySchema }),
async (req, res) => {
const { classId } = validatedQuery<{ classId: string }>(req);
@@ -161,7 +161,7 @@ router.get(
*/
router.post(
'/time-layout',
requirePermission('timetable:manage', 'import:schedule'),
requirePermission('schedule:write', 'import:schedule'),
validate({ body: timeLayoutImportSchema }),
async (req, res) => {
const result = await importTimeLayout(getAuthUser(req), validatedBody<TimeLayoutImportInput>(req));
@@ -178,7 +178,7 @@ router.post(
/** DELETE /api/imports/time-layout/:id - 删除某份时间配置 */
router.delete(
'/time-layout/:id',
requirePermission('timetable:manage'),
requirePermission('import:schedule', 'schedule:write'),
validate({ params: idParamSchema }),
async (req, res) => {
const { id } = validatedParams<{ id: string }>(req);