feat(authz): 重写权限模板分配与班级授权边界
拆分能力、分配范围和业务关系,固定管理员能力并收紧组织级写入口。 补齐内容、课表、设备会话和实时通道授权,兼容旧库分配及历史快照。 重写权限分配预览,按实际可操作范围展示维护入口,合并学生入口并独立学年升级页。 增加授权攻击矩阵及静态门禁,更新隔离回归与开发契约。 验证:授权矩阵 23 段通过,Node 24 下 Web 63/63,类型检查、lint 与文档严格构建通过。
This commit is contained in:
1 parent
682818dd4a
commit
66be3a63b1
115 files changed
+4463
-2747
No files matched your search
@@ -0,0 +1,23 @@
|
||||
import type { UserRole } from '@classhelper/shared';
|
||||
import { prisma } from '../db.js';
|
||||
import { ApiError } from '../http.js';
|
||||
import type { TokenPayload } from '../jwt.js';
|
||||
export async function loadSessionUser(payload: TokenPayload): Promise<TokenPayload> {
|
||||
if (payload.classSession && payload.role === 'CLASS_DEVICE') {
|
||||
const cls = await prisma.class.findUnique({ where: { id: payload.classId ?? payload.sub } });
|
||||
if (!cls || cls.archivedYearId) throw ApiError.unauthorized('班级不存在或已归档');
|
||||
return {
|
||||
sub: cls.id,
|
||||
username: cls.code,
|
||||
name: cls.name,
|
||||
role: 'CLASS_DEVICE',
|
||||
classId: cls.id,
|
||||
classSession: true,
|
||||
classCode: cls.code,
|
||||
};
|
||||
}
|
||||
if (payload.role === 'CLASS_DEVICE') throw ApiError.unauthorized('无效班级会话');
|
||||
const row = await prisma.user.findUnique({ where: { id: payload.sub } });
|
||||
if (!row || !['ADMIN', 'TEACHER'].includes(row.role)) throw ApiError.unauthorized('账号不存在');
|
||||
return { sub: row.id, username: row.username, name: row.name, role: row.role as UserRole, classId: null };
|
||||
}
|
||||
Reference in new issue
Block a user