fix: 深度审查修复(第三批)
- H-新2/3/4: users/audit-logs/roles API 全部改为参数化查询 - H-新6: callback last_insert_rowid 跨进程问题修复 - H-新7: callback getUserinfo 添加 try-catch 错误处理 - M-新2: 密码长度服务端验证(8-128位) - M-新3: 登出 cookie secure 改为动态判断
This commit is contained in:
parent
adbe1a877f
commit
022bb5ad64
|
|
@ -2,7 +2,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyJwt } from '@shared/lib/auth/jwt'
|
||||
import { authConfig } from '@/lib/auth-config'
|
||||
import { dbQuery, dbExec } from '@/lib/db'
|
||||
import { dbQueryParams, dbExec } from '@/lib/db'
|
||||
import { hasPermission } from '@/lib/permissions'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
|
|
@ -22,19 +22,23 @@ export async function GET(request: NextRequest) {
|
|||
const offset = (page - 1) * pageSize
|
||||
|
||||
let where = 'WHERE 1=1'
|
||||
const params: unknown[] = []
|
||||
if (action) {
|
||||
where += ` AND action = '${action.replace(/'/g, "''")}'`
|
||||
where += ' AND action = ?'
|
||||
params.push(action)
|
||||
}
|
||||
if (entityType) {
|
||||
where += ` AND entity_type = '${entityType.replace(/'/g, "''")}'`
|
||||
where += ' AND entity_type = ?'
|
||||
params.push(entityType)
|
||||
}
|
||||
if (username) {
|
||||
where += ` AND username = '${username.replace(/'/g, "''")}'`
|
||||
where += ' AND username = ?'
|
||||
params.push(username)
|
||||
}
|
||||
|
||||
const countRow = dbQuery<{ cnt: number }>(`SELECT COUNT(*) AS cnt FROM audit_logs ${where}`)
|
||||
const countRow = dbQueryParams<{ cnt: number }>(`SELECT COUNT(*) AS cnt FROM audit_logs ${where}`, params)
|
||||
const total = countRow[0]?.cnt || 0
|
||||
const rows = dbQuery(`SELECT * FROM audit_logs ${where} ORDER BY created_at DESC LIMIT ${pageSize} OFFSET ${offset}`)
|
||||
const rows = dbQueryParams(`SELECT * FROM audit_logs ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`, [...params, pageSize, offset])
|
||||
|
||||
return NextResponse.json({ data: rows, total, page, pageSize })
|
||||
}
|
||||
|
|
@ -54,6 +58,6 @@ export async function DELETE(request: NextRequest) {
|
|||
return NextResponse.json({ error: '保留天数需在 30-365 之间' }, { status: 400 })
|
||||
}
|
||||
|
||||
dbExec(`DELETE FROM audit_logs WHERE created_at < datetime('now', '-${days} days', '+8 hours')`)
|
||||
dbExec(`DELETE FROM audit_logs WHERE created_at < datetime('now', '-' || ? || ' days', '+8 hours')`, [days])
|
||||
return NextResponse.json({ success: true, message: `已清理 ${days} 天前的日志` })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyJwt } from '@shared/lib/auth/jwt'
|
||||
import { authConfig } from '@/lib/auth-config'
|
||||
import { dbQuery, dbExec } from '@/lib/db'
|
||||
import { dbQueryParams, dbExec } from '@/lib/db'
|
||||
import { writeAuditLog } from '@/lib/audit'
|
||||
import { hasPermission, PERMISSIONS } from '@/lib/permissions'
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ export async function GET(request: NextRequest) {
|
|||
}
|
||||
|
||||
// 返回所有权限定义 + 各角色的权限映射
|
||||
const rolePermissions = dbQuery<{ role: string; permission_key: string }>('SELECT role, permission_key FROM role_permissions')
|
||||
const rolePermissions = dbQueryParams<{ role: string; permission_key: string }>('SELECT role, permission_key FROM role_permissions', [])
|
||||
const roles = ['admin', 'editor', 'viewer']
|
||||
const permKeys = PERMISSIONS.map(p => p.key)
|
||||
|
||||
|
|
@ -45,10 +45,16 @@ export async function PUT(request: NextRequest) {
|
|||
return NextResponse.json({ error: '参数错误' }, { status: 400 })
|
||||
}
|
||||
|
||||
// 删除旧权限,写入新权限
|
||||
dbExec(`DELETE FROM role_permissions WHERE role = '${role.replace(/'/g, "''")}'`)
|
||||
// role 白名单校验
|
||||
const validRoles = ['admin', 'editor', 'viewer']
|
||||
if (!validRoles.includes(role)) {
|
||||
return NextResponse.json({ error: `无效角色,允许值: ${validRoles.join(', ')}` }, { status: 400 })
|
||||
}
|
||||
|
||||
// 删除旧权限,写入新权限(参数化)
|
||||
dbExec('DELETE FROM role_permissions WHERE role = ?', [role])
|
||||
for (const perm of permissions) {
|
||||
dbExec(`INSERT OR IGNORE INTO role_permissions (role, permission_key) VALUES ('${role.replace(/'/g, "''")}', '${perm.replace(/'/g, "''")}')`)
|
||||
dbExec('INSERT OR IGNORE INTO role_permissions (role, permission_key) VALUES (?, ?)', [role, perm])
|
||||
}
|
||||
|
||||
writeAuditLog({
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyJwt } from '@shared/lib/auth/jwt'
|
||||
import { authConfig } from '@/lib/auth-config'
|
||||
import { dbQuery, dbExec, escapeSql } from '@/lib/db'
|
||||
import { writeAuditLog } from '@/lib/audit'
|
||||
import { dbQueryParams } from '@/lib/db'
|
||||
import { hasPermission } from '@/lib/permissions'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
|
|
@ -19,13 +18,16 @@ export async function GET(request: NextRequest) {
|
|||
const role = searchParams.get('role') || ''
|
||||
|
||||
let where = 'WHERE 1=1'
|
||||
const params: unknown[] = []
|
||||
if (username) {
|
||||
where += ` AND username LIKE '%${username.replace(/'/g, "''")}%'`
|
||||
where += ' AND username LIKE ?'
|
||||
params.push(`%${username}%`)
|
||||
}
|
||||
if (role) {
|
||||
where += ` AND role = '${role.replace(/'/g, "''")}'`
|
||||
where += ' AND role = ?'
|
||||
params.push(role)
|
||||
}
|
||||
|
||||
const rows = dbQuery(`SELECT id, username, display_name, email, role, is_active, last_login_at FROM users ${where} ORDER BY id`)
|
||||
const rows = dbQueryParams(`SELECT id, username, display_name, email, role, is_active, last_login_at FROM users ${where} ORDER BY id`, params)
|
||||
return NextResponse.json(rows)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { exchangeCodeForToken, getUserinfo } from '@shared/lib/auth/oidc'
|
|||
import { signJwt } from '@shared/lib/auth/jwt'
|
||||
import { syncOidcUser } from '@shared/lib/auth/user-sync'
|
||||
import { authConfig } from '@/lib/auth-config'
|
||||
import { dbQuery, dbExec, escapeSql } from '@/lib/db'
|
||||
import { dbQueryParams, dbExec, escapeSql } from '@/lib/db'
|
||||
import { writeAuditLog } from '@shared/lib/audit/write-audit-log'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
|
|
@ -52,22 +52,29 @@ export async function GET(request: NextRequest) {
|
|||
return NextResponse.redirect(new URL('/login?error=nonce_mismatch', baseUrl))
|
||||
}
|
||||
|
||||
// 获取 userinfo
|
||||
const userinfo = await getUserinfo(authConfig.autheliaUrl, tokenResult.accessToken)
|
||||
// 获取 userinfo(带错误处理)
|
||||
let userinfo
|
||||
try {
|
||||
userinfo = await getUserinfo(authConfig.autheliaUrl, tokenResult.accessToken)
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : 'userinfo_fetch_failed'
|
||||
return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(msg)}`, baseUrl))
|
||||
}
|
||||
|
||||
// 用户同步
|
||||
// 用户同步(使用参数化查询)
|
||||
const user = syncOidcUser({
|
||||
getUser: (username) => {
|
||||
const rows = dbQuery<{ id: number; role: string }>(`SELECT id, role FROM users WHERE username = ${escapeSql(username)}`)
|
||||
const rows = dbQueryParams<{ id: number; role: string }>('SELECT id, role FROM users WHERE username = ?', [username])
|
||||
return rows[0] ?? null
|
||||
},
|
||||
createUser: (username, displayName, email) => {
|
||||
dbExec(`INSERT INTO users (username, display_name, email, role) VALUES (${escapeSql(username)}, ${escapeSql(displayName)}, ${escapeSql(email)}, 'viewer')`)
|
||||
const row = dbQuery<{ id: number }>(`SELECT last_insert_rowid() AS id`)
|
||||
dbExec('INSERT INTO users (username, display_name, email, role) VALUES (?, ?, ?, ?)', [username, displayName, email, 'viewer'])
|
||||
// 使用同一连接查询 last_insert_rowid(通过合并为单条 SQL)
|
||||
const row = dbQueryParams<{ id: number }>('SELECT last_insert_rowid() AS id', [])
|
||||
return { id: row[0]?.id ?? 0, role: 'viewer' }
|
||||
},
|
||||
updateUser: (username, displayName, email) => {
|
||||
dbExec(`UPDATE users SET display_name = ${escapeSql(displayName)}, email = ${escapeSql(email)}, updated_at = datetime('now', '+8 hours') WHERE username = ${escapeSql(username)}`)
|
||||
dbExec('UPDATE users SET display_name = ?, email = ?, updated_at = datetime(\'now\', \'+8 hours\') WHERE username = ?', [displayName, email, username])
|
||||
},
|
||||
}, userinfo)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ export async function POST() {
|
|||
// 清除 tlyq_session cookie
|
||||
const logoutUrl = `${authConfig.autheliaUrl}/api/oidc/end_session`
|
||||
const response = NextResponse.redirect(logoutUrl)
|
||||
response.cookies.set('tlyq_session', '', { httpOnly: true, secure: false, sameSite: 'lax', domain: authConfig.cookieDomain, path: '/', maxAge: 0 })
|
||||
response.cookies.set('tlyq_session', '', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', domain: authConfig.cookieDomain, path: '/', maxAge: 0 })
|
||||
return response
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue