export function createAuthRoutes(context) { const { database, readDb, sendJson, sendError, readJson, readBodyBuffer, sendWorkbook, currentUser, parseCookies, safeUser, requireUser, hasPermission, requirePermission, profileInScope, registrationInScope, adminScopeLabel, adminsForStep, activeWorkflow, createWorkflowSubmission, workflowView, pendingWorkflow, candidateSequence, generateCandidateNumber, cleanText, centerScopeProfile, workflowScopeProfile, candidateAccountBatchView, centerChangeView, parseCenterChange, maskId, publicExam, examRegistrationView, logAction, excelResourceNames, excelRowsForResource, importExcelResource, admitCardHtml, hashPassword, verifyPassword, randomBytes, uid, nowIso, sessions, buildWorkbook, hasExcelResource, parseWorkbook, adminLevelNames, permissionsByLevel } = context; async function handleAuth(request, response, pathname) { if (request.method === 'GET' && pathname === '/api/auth/me') { const user = await currentUser(request); if (!user) return sendJson(response, 200, { ok: true, user: null }); const db = await readDb(); const profile = user.role === 'candidate' ? db.candidateProfiles.find(item => item.userId === user.id) : null; return sendJson(response, 200, { ok: true, user: safeUser(user), profile, ...(user.role === 'admin' ? { permissions: permissionsByLevel[user.adminLevel || 'super'], scopeLabel: adminScopeLabel(db, user) } : {}) }); } if (request.method === 'POST' && pathname === '/api/auth/register') { const body = await readJson(request); const password = String(body.password || ''); const name = cleanText(body.name, 30); const gender = cleanText(body.gender, 10); if (!name || !['男', '女'].includes(gender)) return sendError(response, 400, '请填写姓名并选择性别'); if (password.length < 8) return sendError(response, 400, '密码至少需要 8 位'); const db = await readDb(); if (!db.settings.selfRegistrationEnabled) return sendError(response, 403, '当前未开放自主注册,请使用学校下发的报名号和初始密码登录'); const schoolId = cleanText(body.schoolId, 64); const classId = cleanText(body.classId, 64); const school = db.schools.find(item => item.id === schoolId && item.active); const schoolClass = db.classes.find(item => item.id === classId && item.schoolId === schoolId && item.active); if (!school || !schoolClass) return sendError(response, 400, '请选择有效的学校和班级'); const draftProfile = { schoolId, classId, gender }; const generated = generateCandidateNumber(db, draftProfile); const userId = uid('usr'); const user = { id: userId, username: generated.number, candidateNumber: generated.number, passwordHash: hashPassword(password), role: 'candidate', displayName: name, active: true, mustChangePassword: false, createdAt: nowIso() }; const profile = { id: uid('profile'), userId, name, idNumber: `PENDING-${userId}`, phone: '', gender, email: '', school: school.name, grade: schoolClass.name, schoolId, classId, address: '', emergencyContact: '', emergencyPhone: '', nativePlace: '', birthDate: '', ethnicity: '', postalCode: '', guardianName: '', guardianPhone: '', profileCompleted: false, status: 'pending', reviewNote: '', updatedAt: nowIso() }; await database.createCandidate(user, profile, null, null); return sendJson(response, 201, { ok: true, registrationNumber: generated.number, message: '报名号已生成,请使用该号码登录并补全个人信息' }); } if (request.method === 'POST' && pathname === '/api/auth/login') { const body = await readJson(request); const db = await readDb(); const account = cleanText(body.username, 120).toLowerCase(); const user = db.users.find(item => item.username.toLowerCase() === account || String(item.candidateNumber || '').toLowerCase() === account); if (!user || user.active === false || !verifyPassword(String(body.password || ''), user.passwordHash)) return sendError(response, 401, '账号或密码不正确'); const token = randomBytes(32).toString('hex'); sessions.set(token, { userId: user.id, expiresAt: Date.now() + 8 * 60 * 60 * 1000 }); return sendJson(response, 200, { ok: true, user: safeUser(user) }, { 'Set-Cookie': `hz_session=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=28800` }); } if (request.method === 'POST' && pathname === '/api/auth/change-password') { const user = await requireUser(request, response); if (!user) return true; const body = await readJson(request); const currentPassword = String(body.currentPassword || ''); const newPassword = String(body.newPassword || ''); if (!verifyPassword(currentPassword, user.passwordHash)) return sendError(response, 400, '当前密码不正确'); if (newPassword.length < 8) return sendError(response, 400, '新密码至少需要 8 位'); if (newPassword === currentPassword) return sendError(response, 400, '新密码不能与初始密码相同'); user.passwordHash = hashPassword(newPassword); user.mustChangePassword = false; const db = await readDb(); const log = logAction(db, user, '修改登录密码', user.role === 'candidate' ? `报名号 ${user.candidateNumber}` : user.username); await database.changePassword(user, log); return sendJson(response, 200, { ok: true, user: safeUser(user) }); } if (request.method === 'POST' && pathname === '/api/auth/logout') { const token = parseCookies(request).hz_session; if (token) sessions.delete(token); return sendJson(response, 200, { ok: true }, { 'Set-Cookie': 'hz_session=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0' }); } return false; } return handleAuth; }