From 9a21c7519c62e7ec650422064e07fdc06d2afdc6 Mon Sep 17 00:00:00 2001 From: biss Date: Mon, 20 Jul 2026 20:18:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/security/session.mjs | 9 +++++++-- tests/system.test.mjs | 24 +++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/security/session.mjs b/src/security/session.mjs index 8c78f08..6c20b02 100644 --- a/src/security/session.mjs +++ b/src/security/session.mjs @@ -1,4 +1,9 @@ export function createSessionManager({ sessions, readDb, sendError }) { + function normalizeUser(user) { + if (user?.role === 'admin' && !user.adminLevel) return { ...user, adminLevel: 'super' }; + return user; + } + function parseCookies(request) { return Object.fromEntries(String(request.headers.cookie || '').split(';').map(part => part.trim()).filter(Boolean).map(part => { const index = part.indexOf('='); @@ -15,7 +20,7 @@ export function createSessionManager({ sessions, readDb, sendError }) { } const db = await readDb(); const user = db.users.find(item => item.id === session.userId) || null; - return user?.active === false || user?.archivedAt ? null : user; + return user?.active === false || user?.archivedAt ? null : normalizeUser(user); } function safeUser(user) { @@ -23,7 +28,7 @@ export function createSessionManager({ sessions, readDb, sendError }) { id: user.id, username: user.username, role: user.role, - adminLevel: user.adminLevel || null, + adminLevel: user.role === 'admin' ? user.adminLevel || 'super' : null, schoolId: user.schoolId || null, classId: user.classId || null, displayName: user.displayName, diff --git a/tests/system.test.mjs b/tests/system.test.mjs index dfc6322..968981c 100644 --- a/tests/system.test.mjs +++ b/tests/system.test.mjs @@ -72,7 +72,18 @@ process.env.DATABASE_CLIENT = 'sqlite'; process.env.SQLITE_PATH = testDb; const seededTestDatabase = await createDatabase({ root, - seed: () => createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword }) + seed: () => { + const state = createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword }); + const currentSuperAdmin = state.users.find(user => user.role === 'admin' && user.adminLevel === 'super'); + state.users.push({ + ...currentSuperAdmin, + id: 'admin_legacy_super', + username: 'legacy_super', + adminLevel: null, + displayName: '旧版超级管理员' + }); + return state; + } }); await seededTestDatabase.close(); @@ -116,6 +127,7 @@ function createClient() { } const admin = createClient(); +const legacyAdmin = createClient(); const schoolAdmin = createClient(); const schoolAdmin2 = createClient(); const classAdmin = createClient(); @@ -210,6 +222,8 @@ try { const loginAdmin = await admin.request('/api/auth/login', { method: 'POST', body: { username: 'admin', password: '12345678' } }); assert.equal(loginAdmin.data.user.role, 'admin'); assert.equal(loginAdmin.data.user.adminLevel, 'super', '默认管理员应为超级管理员'); + const loginLegacyAdmin = await legacyAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'legacy_super', password: '12345678' } }); + assert.equal(loginLegacyAdmin.data.user.adminLevel, 'super', '未保存层级的旧版管理员登录后应规范化为超级管理员'); assert.equal((await schoolAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin', password: '12345678' } })).data.user.adminLevel, 'school'); assert.equal((await schoolAdmin2.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin_2', password: '12345678' } })).data.user.adminLevel, 'school'); assert.equal((await classAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'class_admin', password: '12345678' } })).data.user.adminLevel, 'class'); @@ -258,6 +272,9 @@ try { assert.ok(adminDirectory.data.admins.filter(item => item.adminLevel === 'school' && item.schoolId === 'school_hz1').length >= 2, '同一学校应支持多个同级管理员'); const schoolCenters = await schoolAdmin.request('/api/admin/centers'); assert.ok(schoolCenters.data.centers.every(item => item.schoolId === 'school_hz1'), '校级管理员只能读取本校考点'); + const legacySuperCenters = await legacyAdmin.request('/api/admin/centers'); + assert.ok(legacySuperCenters.data.centers.some(item => item.schoolId === 'school_hz3'), '旧版超级管理员应能跨校读取学校维护的正式考点'); + assert.ok(legacySuperCenters.data.centers.flatMap(item => item.rooms).some(item => item.centerId === 'center_hz3'), '旧版超级管理员应能读取其他学校维护的结构化考场'); const newCenter = await schoolAdmin.request('/api/admin/centers', { method: 'POST', body: { code: 'HZ01-EAST', name: '海州市第一中学东区考点', provinceCode: '320000', cityCode: '320700', districtCode: '320706', address: '测试路 8 号', contact: '0518-12345678', managerName: '测试负责人', managerPhone: '13800001234', emergencyPhone: '0518-120', gateOpenTime: '07:00', transport: '东门入场', status: 'active', notes: '自动化测试档案', @@ -265,6 +282,9 @@ try { } }); assert.equal(newCenter.response.status, 202, '新增考点应创建审批申请而不是直接落库'); assert.equal(newCenter.data.changeRequest.schoolId, 'school_hz1', '校级管理员新增考点必须自动归属本校'); + const legacyPendingCenters = await legacyAdmin.request('/api/admin/centers'); + const legacyPendingCenter = legacyPendingCenters.data.changeRequests.find(item => item.id === newCenter.data.changeRequest.id); + assert.equal(legacyPendingCenter?.rooms[0]?.code, 'E001', '旧版超级管理员应能读取学校提交的待审批考点与考场快照'); const beforeCenterApproval = await schoolAdmin.request('/api/admin/centers'); assert.ok(!beforeCenterApproval.data.centers.some(item => item.code === 'HZ01-EAST'), '考点审批通过前不得进入正式档案'); const approveCenter = await admin.request(`/api/admin/center-change-requests/${newCenter.data.changeRequest.id}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '考务条件核验通过' } }); @@ -273,6 +293,8 @@ try { const createdCenter = afterCenterApproval.data.centers.find(item => item.code === 'HZ01-EAST'); assert.equal(createdCenter.rooms.length, 1, '审批通过后应同时写入结构化考场'); assert.equal(createdCenter.totalCapacity, 30, '考场容量应汇总到考点档案'); + const legacyApprovedCenter = (await legacyAdmin.request('/api/admin/centers')).data.centers.find(item => item.code === 'HZ01-EAST'); + assert.equal(legacyApprovedCenter?.rooms[0]?.code, 'E001', '旧版超级管理员应能读取学校维护并审批生效的考点与考场'); const centerUpdate = await schoolAdmin.request(`/api/admin/centers/${createdCenter.id}`, { method: 'PATCH', body: { ...createdCenter, managerName: '变更后负责人', rooms: [ ...createdCenter.rooms,