fix: SQL 参数化查询 + bcrypt cost 12 + 密码长度改为 8 位
- admin users API 改用 dbQueryParams 参数化查询 - dbExec 支持可选 params 参数 - bcrypt cost factor 10 → 12 - 密码最小长度 6 → 8
This commit is contained in:
parent
2afb98ff1d
commit
adbe1a877f
|
|
@ -48,8 +48,8 @@ export default function UsersPage() {
|
||||||
setError('两次输入的密码不一致')
|
setError('两次输入的密码不一致')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (editForm.password && editForm.password.length < 6) {
|
if (editForm.password && editForm.password.length < 8) {
|
||||||
setError('密码至少 6 位')
|
setError('密码至少 8 位')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from 'bcryptjs'
|
||||||
import { verifyJwt } from '@shared/lib/auth/jwt'
|
import { verifyJwt } from '@shared/lib/auth/jwt'
|
||||||
import { authConfig } from '@/lib/auth-config'
|
import { authConfig } from '@/lib/auth-config'
|
||||||
import { dbQuery, dbExec } from '@/lib/db'
|
import { dbQueryParams, dbExec } from '@/lib/db'
|
||||||
import { writeAuditLog } from '@/lib/audit'
|
import { writeAuditLog } from '@/lib/audit'
|
||||||
import { hasPermission } from '@/lib/permissions'
|
import { hasPermission } from '@/lib/permissions'
|
||||||
|
|
||||||
|
|
@ -16,18 +16,24 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params
|
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 body = await request.json()
|
||||||
const { role, display_name, email, password } = body
|
const { role, display_name, email, password } = body
|
||||||
|
|
||||||
// 检查用户是否存在
|
// 检查用户是否存在(参数化查询)
|
||||||
const existing = dbQuery<{ id: number; username: string }>(
|
const existing = dbQueryParams<{ id: number; username: string }>(
|
||||||
`SELECT id, username FROM users WHERE id = ${Number(id)}`
|
'SELECT id, username FROM users WHERE id = ?', [userId]
|
||||||
)
|
)
|
||||||
if (existing.length === 0) {
|
if (existing.length === 0) {
|
||||||
return NextResponse.json({ error: '用户不存在' }, { status: 404 })
|
return NextResponse.json({ error: '用户不存在' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updates: string[] = []
|
const setClauses: string[] = []
|
||||||
|
const values: unknown[] = []
|
||||||
const details: Record<string, unknown> = {}
|
const details: Record<string, unknown> = {}
|
||||||
|
|
||||||
if (role !== undefined) {
|
if (role !== undefined) {
|
||||||
|
|
@ -40,36 +46,41 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||||
if (!validRoles.includes(role)) {
|
if (!validRoles.includes(role)) {
|
||||||
return NextResponse.json({ error: `无效角色,允许值: ${validRoles.join(', ')}` }, { status: 400 })
|
return NextResponse.json({ error: `无效角色,允许值: ${validRoles.join(', ')}` }, { status: 400 })
|
||||||
}
|
}
|
||||||
updates.push(`role = '${role.replace(/'/g, "''")}'`)
|
setClauses.push('role = ?')
|
||||||
|
values.push(role)
|
||||||
details.role = role
|
details.role = role
|
||||||
}
|
}
|
||||||
if (display_name !== undefined) {
|
if (display_name !== undefined) {
|
||||||
updates.push(`display_name = '${String(display_name).replace(/'/g, "''")}'`)
|
setClauses.push('display_name = ?')
|
||||||
|
values.push(String(display_name))
|
||||||
details.display_name = display_name
|
details.display_name = display_name
|
||||||
}
|
}
|
||||||
if (email !== undefined) {
|
if (email !== undefined) {
|
||||||
updates.push(`email = '${String(email).replace(/'/g, "''")}'`)
|
setClauses.push('email = ?')
|
||||||
|
values.push(String(email) || null)
|
||||||
details.email = email
|
details.email = email
|
||||||
}
|
}
|
||||||
if (password) {
|
if (password) {
|
||||||
const hash = bcrypt.hashSync(password, 10)
|
const hash = bcrypt.hashSync(password, 12)
|
||||||
updates.push(`password_hash = '${hash.replace(/'/g, "''")}'`)
|
setClauses.push('password_hash = ?')
|
||||||
|
values.push(hash)
|
||||||
details.password = '***'
|
details.password = '***'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updates.length === 0) {
|
if (setClauses.length === 0) {
|
||||||
return NextResponse.json({ error: '无可更新字段' }, { status: 400 })
|
return NextResponse.json({ error: '无可更新字段' }, { status: 400 })
|
||||||
}
|
}
|
||||||
updates.push(`updated_at = datetime('now', '+8 hours')`)
|
setClauses.push("updated_at = datetime('now', '+8 hours')")
|
||||||
|
values.push(userId)
|
||||||
|
|
||||||
dbExec(`UPDATE users SET ${updates.join(', ')} WHERE id = ${Number(id)}`)
|
dbExec(`UPDATE users SET ${setClauses.join(', ')} WHERE id = ?`, values)
|
||||||
|
|
||||||
writeAuditLog({
|
writeAuditLog({
|
||||||
userId: Number(payload.sub) || null,
|
userId: Number(payload.sub) || null,
|
||||||
username: String(payload.username || ''),
|
username: String(payload.username || ''),
|
||||||
action: 'update_user',
|
action: 'update_user',
|
||||||
entityType: 'user',
|
entityType: 'user',
|
||||||
entityId: Number(id),
|
entityId: userId,
|
||||||
details,
|
details,
|
||||||
ipAddress: request.headers.get('x-forwarded-for') || '127.0.0.1',
|
ipAddress: request.headers.get('x-forwarded-for') || '127.0.0.1',
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,16 @@ function ensureDb() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 execFileSync 直接传参,不经过 shell
|
// 执行 SQL(支持可选参数化)
|
||||||
export function dbExec(sql: string): void {
|
export function dbExec(sql: string, params?: unknown[]): void {
|
||||||
ensureDb()
|
ensureDb()
|
||||||
|
if (params && params.length > 0) {
|
||||||
|
let i = 0
|
||||||
|
const escaped = sql.replace(/\?/g, () => escapeSql(params[i++]))
|
||||||
|
execFileSync('sqlite3', [DB_PATH, escaped], { timeout: 10000 })
|
||||||
|
} else {
|
||||||
execFileSync('sqlite3', [DB_PATH, sql], { timeout: 10000 })
|
execFileSync('sqlite3', [DB_PATH, sql], { timeout: 10000 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dbQuery<T = Record<string, unknown>>(sql: string): T[] {
|
export function dbQuery<T = Record<string, unknown>>(sql: string): T[] {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue