// PUT /api/admin/users/[id] — 修改用户(角色、密码等) import { NextRequest, NextResponse } from 'next/server' import bcrypt from 'bcryptjs' import { verifyJwt } from '@shared/lib/auth/jwt' import { authConfig } from '@/lib/auth-config' import { dbQueryParams, dbExec } from '@/lib/db' import { writeAuditLog } from '@/lib/audit' import { hasPermission } from '@/lib/permissions' export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const token = request.cookies.get('tlyq_session')?.value if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) const payload = verifyJwt(token, authConfig.jwtSecret) if (!payload || !hasPermission(String(payload.role || 'viewer'), 'users:manage')) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } const { id } = await params const userId = Number(id) if (!userId || isNaN(userId)) { return NextResponse.json({ error: '无效的用户 ID' }, { status: 400 }) } const body = await request.json() const { role, display_name, email, password } = body // 检查用户是否存在(参数化查询) const existing = dbQueryParams<{ id: number; username: string }>( 'SELECT id, username FROM users WHERE id = ?', [userId] ) if (existing.length === 0) { return NextResponse.json({ error: '用户不存在' }, { status: 404 }) } const setClauses: string[] = [] const values: unknown[] = [] const details: Record = {} if (role !== undefined) { // 禁止修改系统保留用户的角色 if (existing[0].username === 'admin' || existing[0].username === 'localadmin') { return NextResponse.json({ error: '不能修改系统保留用户的角色' }, { status: 400 }) } // role 白名单校验 const validRoles = ['admin', 'editor', 'viewer'] if (!validRoles.includes(role)) { return NextResponse.json({ error: `无效角色,允许值: ${validRoles.join(', ')}` }, { status: 400 }) } setClauses.push('role = ?') values.push(role) details.role = role } if (display_name !== undefined) { setClauses.push('display_name = ?') values.push(String(display_name)) details.display_name = display_name } if (email !== undefined) { setClauses.push('email = ?') values.push(String(email) || null) details.email = email } if (password) { // 服务端密码长度验证 if (password.length < 8 || password.length > 128) { return NextResponse.json({ error: '密码长度需在 8-128 位之间' }, { status: 400 }) } const hash = bcrypt.hashSync(password, 12) setClauses.push('password_hash = ?') values.push(hash) details.password = '***' } if (setClauses.length === 0) { return NextResponse.json({ error: '无可更新字段' }, { status: 400 }) } setClauses.push("updated_at = datetime('now', '+8 hours')") values.push(userId) dbExec(`UPDATE users SET ${setClauses.join(', ')} WHERE id = ?`, values) writeAuditLog({ userId: Number(payload.sub) || null, username: String(payload.username || ''), action: 'update_user', entityType: 'user', entityId: userId, details, ipAddress: request.headers.get('x-forwarded-for') || '127.0.0.1', }) return NextResponse.json({ success: true }) }