diff --git a/src/app/api/admin/audit-logs/route.ts b/src/app/api/admin/audit-logs/route.ts index 902f1c1..ccddbfa 100644 --- a/src/app/api/admin/audit-logs/route.ts +++ b/src/app/api/admin/audit-logs/route.ts @@ -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} 天前的日志` }) } diff --git a/src/app/api/admin/roles/route.ts b/src/app/api/admin/roles/route.ts index c74ed51..22aaa21 100644 --- a/src/app/api/admin/roles/route.ts +++ b/src/app/api/admin/roles/route.ts @@ -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({ diff --git a/src/app/api/admin/users/[id]/route.ts b/src/app/api/admin/users/[id]/route.ts index fc35511..a5d1499 100644 --- a/src/app/api/admin/users/[id]/route.ts +++ b/src/app/api/admin/users/[id]/route.ts @@ -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) diff --git a/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts index 66ffa07..666f8d9 100644 --- a/src/app/api/admin/users/route.ts +++ b/src/app/api/admin/users/route.ts @@ -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) } diff --git a/src/app/api/auth/callback/route.ts b/src/app/api/auth/callback/route.ts index 27946a7..8225b5a 100644 --- a/src/app/api/auth/callback/route.ts +++ b/src/app/api/auth/callback/route.ts @@ -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) diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index 780d751..6e3efbc 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -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 }