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 } const TOKEN_EXPIRY = 2 * 60 * 60 * 1000 // 2 hours // 不需要认证的路径 const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/demo-login', '/api/auth/refresh', '/api/health'] /** 已删除用户 ID 缓存(5 分钟 TTL) */ const deletedUserCache = new Map() // userId → cachedAt async function isUserDeleted(userId: number): Promise { 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() } // 从 Authorization header 获取 token const authHeader = req.headers.authorization if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ code: 40100, message: '未登录' }) } const token = authHeader.slice(7) try { const decoded = Buffer.from(token, 'base64').toString('utf-8') const parts = decoded.split(':') if (parts.length !== 3) { return res.status(401).json({ code: 40100, message: 'token无效' }) } const [userIdStr, timestamp, signature] = parts const userId = parseInt(userIdStr) const ts = parseInt(timestamp) if (isNaN(userId) || isNaN(ts)) { return res.status(401).json({ code: 40100, message: 'token无效' }) } // Check expiry and reject future timestamps const now = Date.now() if (now - ts > TOKEN_EXPIRY) { return res.status(401).json({ code: 40101, message: 'token已过期' }) } if (ts > now + 60000) { return res.status(401).json({ code: 40100, message: 'token无效' }) } // Verify HMAC signature (constant-time comparison) const payload = `${userId}:${timestamp}` const expectedSig = createHmac('sha256', TOKEN_SECRET).update(payload).digest('hex') const sigBuf = Buffer.from(signature, 'hex') const expectedBuf = Buffer.from(expectedSig, 'hex') if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) { 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无效' }) } next() }