1
1
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-12-07 13:03:09 +00:00
ClassworksKV/middleware/rateLimiter.js
SunWuyuan 2ab90ffebc
feat: Implement Refresh Token system with enhanced security and user experience
- Added refresh token support in the account model with new fields: refreshToken, refreshTokenExpiry, and tokenVersion.
- Created a new token management utility (utils/tokenManager.js) for generating and verifying access and refresh tokens.
- Updated JWT utility (utils/jwt.js) to maintain backward compatibility while introducing new token generation methods.
- Enhanced middleware for JWT authentication to support new token types and automatic token refreshing.
- Expanded API endpoints in routes/accounts.js to include refresh token functionality, logout options, and token info retrieval.
- Introduced automatic token refresh mechanism in the front-end integration examples.
- Comprehensive migration checklist and documentation for the new refresh token system.
- Added database migration script to accommodate new fields in the Account table.
2025-11-02 09:48:03 +08:00

121 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import rateLimit from "express-rate-limit";
// 获取客户端真实IP的函数
export const getClientIp = (req) => {
return (
req.headers["x-forwarded-for"] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket?.remoteAddress ||
"0.0.0.0"
);
};
// 从请求中提取Token的函数
const extractToken = (req) => {
return (
req.headers["x-app-token"] ||
req.query.apptoken ||
req.body?.apptoken ||
null
);
};
// 获取限速键优先使用token没有token则使用IP
export const getRateLimitKey = (req) => {
const token = extractToken(req);
if (token) {
return `token:${token}`;
}
return `ip:${getClientIp(req)}`;
};
// 纯基于Token的keyGenerator用于KV Token专用路由
// 这个函数假设token已经通过中间件设置在req对象上
export const getTokenOnlyKey = (req) => {
// 尝试从多个位置获取token
const token =
req.locals?.token || // 如果token被设置在req.locals中
req.res?.locals?.token || // 如果token在res.locals中
extractToken(req); // 从headers/query/body提取
if (!token) {
// 如果没有token返回一个特殊键用于统一限制
return "no-token";
}
return `token:${token}`;
};
// 创建一个中间件来将res.locals.token复制到req.locals.token以便限速器使用
export const prepareTokenForRateLimit = (req, res, next) => {
if (res.locals.token) {
req.locals = req.locals || {};
req.locals.token = res.locals.token;
}
next();
};
// 认证相关路由限速器(防止暴力破解)
export const authLimiter = rateLimit({
windowMs: 30 * 60 * 1000, // 30分钟
limit: 5, // 每个IP在windowMs时间内最多允许5次认证尝试
standardHeaders: "draft-7",
legacyHeaders: false,
message: "认证请求过于频繁请30分钟后再试",
keyGenerator: getClientIp,
skipSuccessfulRequests: true, // 成功的认证不计入限制
skipFailedRequests: false, // 失败的认证计入限制
});
// === Token 专用限速器更宽松的限制纯基于Token ===
// Token 读操作限速器
export const tokenReadLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
limit: 1024, // 每个token在1分钟内最多1024次读操作
standardHeaders: "draft-7",
legacyHeaders: false,
message: "读操作请求过于频繁,请稍后再试",
keyGenerator: getTokenOnlyKey,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});
// Token 写操作限速器
export const tokenWriteLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
limit: 512, // 每个token在1分钟内最多512次写操作
standardHeaders: "draft-7",
legacyHeaders: false,
message: "写操作请求过于频繁,请稍后再试",
keyGenerator: getTokenOnlyKey,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});
// Token 删除操作限速器
export const tokenDeleteLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
limit: 256, // 每个token在1分钟内最多256次删除操作
standardHeaders: "draft-7",
legacyHeaders: false,
message: "删除操作请求过于频繁,请稍后再试",
keyGenerator: getTokenOnlyKey,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});
// Token 批量操作限速器
export const tokenBatchLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
limit: 128, // 每个token在1分钟内最多128次批量操作
standardHeaders: "draft-7",
legacyHeaders: false,
message: "批量操作请求过于频繁,请稍后再试",
keyGenerator: getTokenOnlyKey,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});