Files
eSIM-Tools/netlify/functions/_shared/rate-limiter.js
Abner 7fd54dde39 ♻️ refactor: 修复安全漏洞并优化架构债务
安全修复:
- 修复 dom.js 和 simyo/app.js 中的 innerHTML XSS 注入风险
- 使用 HTMLSanitizer.escapeHtml/escapeAttr 替代直接模板拼接
- 将 onclick 内联事件替换为 data-* 属性 + addEventListener
- 移除 server.js 中硬编码的 Simyo X-Client-Token

架构优化:
- 新增 _shared/rate-limiter.js 分布式限流模块 (Netlify Blobs)
- verify-cookie.js 内存限流替换为 KV 跨实例共享方案
- giffgaff/utils.js debounce/throttle 改为委托共享实现
- simyo/app.js 会话存储迁移至 SecureStorage (自动 TTL 过期)
- 合并 notifications-internal.js 至 notifications.js 消除双维护路径
2026-04-25 16:30:19 +08:00

66 lines
1.9 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.
'use strict';
/**
* 分布式限流模块
* 优先使用 Netlify Blobs 实现跨实例共享,降级为内存限流
*/
// 内存限流降级方案
const memoryStore = new Map();
function memoryRateLimit(key, windowMs, maxRequests) {
const now = Date.now();
const arr = memoryStore.get(key) || [];
const recent = arr.filter(ts => now - ts < windowMs);
recent.push(now);
memoryStore.set(key, recent);
return recent.length > maxRequests;
}
/**
* Netlify Blobs 限流(跨实例共享)
*/
async function blobsRateLimit(key, windowMs, maxRequests) {
try {
const { getStore } = await import('@netlify/blobs');
const store = getStore('rate-limits');
const data = await store.get(key, { type: 'json' });
const now = Date.now();
let timestamps = [];
if (data && Array.isArray(data.timestamps)) {
timestamps = data.timestamps.filter(ts => now - ts < windowMs);
}
timestamps.push(now);
const limited = timestamps.length > maxRequests;
// 更新时间戳记录,设置过期时间为窗口宽度 + 10 秒缓冲
await store.set(key, JSON.stringify({ timestamps }), {
expirationTtl: Math.ceil(windowMs / 1000) + 10
});
return limited;
} catch (error) {
// Blobs 不可用时降级到内存限流
console.warn('[RateLimiter] Blobs 不可用,降级到内存限流:', error.message);
return memoryRateLimit(key, windowMs, maxRequests);
}
}
/**
* 检查是否被限流
* @param {string} identifier - 限流标识(通常是 IP
* @param {number} windowMs - 时间窗口(毫秒),默认 5 分钟
* @param {number} maxRequests - 窗口内最大请求数,默认 15 次
* @returns {Promise<boolean>} 是否被限流
*/
async function isRateLimited(identifier, windowMs = 5 * 60 * 1000, maxRequests = 15) {
const key = `ratelimit:${identifier}`;
return blobsRateLimit(key, windowMs, maxRequests);
}
module.exports = { isRateLimited };