fix(iter-v2): T01 QA fixes - race condition, hook regex, deleted user check

- BUG-1: Pre-commit hook allows .env.example (grep -v filter)
- BUG-2: /auth/refresh uses transaction + FOR UPDATE to prevent race condition
- WARN-1: Auth middleware checks deleted_at with 5-min TTL cache
- WARN-2: PUT /admin/users/:id/status filters deleted_at IS NULL
- WARN-3: Install node-cron@^3.0.3
This commit is contained in:
2026-06-11 09:34:27 +08:00
parent 263a7c4616
commit d170df5c51
7 changed files with 108 additions and 32 deletions

View File

@@ -1,6 +1,7 @@
import { Request, Response, NextFunction } from 'express'
import { createHmac, timingSafeEqual } from 'crypto'
import { TOKEN_SECRET } from '../config/token'
import pool from '../db/connection'
export interface AuthRequest extends Request {
userId?: number
@@ -11,7 +12,24 @@ const TOKEN_EXPIRY = 2 * 60 * 60 * 1000 // 2 hours
// 不需要认证的路径
const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/demo-login', '/api/auth/refresh', '/api/health']
export function authMiddleware(req: AuthRequest, res: Response, next: NextFunction) {
/** 已删除用户 ID 缓存5 分钟 TTL */
const deletedUserCache = new Map<number, number>() // userId → cachedAt
async function isUserDeleted(userId: number): Promise<boolean> {
const cached = deletedUserCache.get(userId)
if (cached && Date.now() - cached < 5 * 60 * 1000) {
return true // 仍在缓存中,视为已删除
}
const [rows] = await pool.query('SELECT deleted_at FROM users WHERE id = ?', [userId])
if (!(rows as any[]).length || (rows as any[])[0].deleted_at !== null) {
deletedUserCache.set(userId, Date.now())
return true
}
deletedUserCache.delete(userId)
return false
}
export async function authMiddleware(req: AuthRequest, res: Response, next: NextFunction) {
// 公开路径跳过认证
if (PUBLIC_PATHS.some(p => req.path === p)) {
return next()
@@ -57,6 +75,11 @@ export function authMiddleware(req: AuthRequest, res: Response, next: NextFuncti
return res.status(401).json({ code: 40100, message: 'token无效' })
}
// 检查用户是否已被软删除
if (await isUserDeleted(userId)) {
return res.status(401).json({ code: 40100, message: '账号已禁用' })
}
req.userId = userId
} catch {
return res.status(401).json({ code: 40100, message: 'token无效' })