@upstash/ratelimit 1.2.0-canary → 1.2.1-canary
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +58 -2
- package/dist/index.d.ts +58 -2
- package/dist/index.js +135 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +140 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
// src/analytics.ts
|
|
2
8
|
import { Analytics as CoreAnalytics } from "@upstash/core-analytics";
|
|
3
9
|
var Analytics = class {
|
|
@@ -266,7 +272,107 @@ var resetScript = `
|
|
|
266
272
|
until cursor == "0"
|
|
267
273
|
`;
|
|
268
274
|
|
|
269
|
-
// src/
|
|
275
|
+
// src/types.ts
|
|
276
|
+
var DenyListExtension = "denyList";
|
|
277
|
+
var IpDenyListKey = "ipDenyList";
|
|
278
|
+
var IpDenyListStatusKey = "ipDenyListStatus";
|
|
279
|
+
|
|
280
|
+
// src/deny-list/scripts.ts
|
|
281
|
+
var checkDenyListScript = `
|
|
282
|
+
-- Checks if values provideed in ARGV are present in the deny lists.
|
|
283
|
+
-- This is done using the allDenyListsKey below.
|
|
284
|
+
|
|
285
|
+
-- Additionally, checks the status of the ip deny list using the
|
|
286
|
+
-- ipDenyListStatusKey below. Here are the possible states of the
|
|
287
|
+
-- ipDenyListStatusKey key:
|
|
288
|
+
-- * status == -1: set to "disabled" with no TTL
|
|
289
|
+
-- * status == -2: not set, meaning that is was set before but expired
|
|
290
|
+
-- * status > 0: set to "valid", with a TTL
|
|
291
|
+
--
|
|
292
|
+
-- In the case of status == -2, we set the status to "pending" with
|
|
293
|
+
-- 30 second ttl. During this time, the process which got status == -2
|
|
294
|
+
-- will update the ip deny list.
|
|
295
|
+
|
|
296
|
+
local allDenyListsKey = KEYS[1]
|
|
297
|
+
local ipDenyListStatusKey = KEYS[2]
|
|
298
|
+
|
|
299
|
+
local results = redis.call('SMISMEMBER', allDenyListsKey, unpack(ARGV))
|
|
300
|
+
local status = redis.call('TTL', ipDenyListStatusKey)
|
|
301
|
+
if status == -2 then
|
|
302
|
+
redis.call('SETEX', ipDenyListStatusKey, 30, "pending")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
return { results, status }
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
// src/deny-list/ip-deny-list.ts
|
|
309
|
+
var ip_deny_list_exports = {};
|
|
310
|
+
__export(ip_deny_list_exports, {
|
|
311
|
+
ThresholdError: () => ThresholdError,
|
|
312
|
+
disableIpDenyList: () => disableIpDenyList,
|
|
313
|
+
updateIpDenyList: () => updateIpDenyList
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// src/deny-list/time.ts
|
|
317
|
+
var MILLISECONDS_IN_HOUR = 60 * 60 * 1e3;
|
|
318
|
+
var MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR;
|
|
319
|
+
var MILLISECONDS_TO_2AM = 2 * MILLISECONDS_IN_HOUR;
|
|
320
|
+
var getIpListTTL = (time) => {
|
|
321
|
+
const now = time || Date.now();
|
|
322
|
+
const timeSinceLast2AM = (now - MILLISECONDS_TO_2AM) % MILLISECONDS_IN_DAY;
|
|
323
|
+
return MILLISECONDS_IN_DAY - timeSinceLast2AM;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// src/deny-list/ip-deny-list.ts
|
|
327
|
+
var baseUrl = "https://raw.githubusercontent.com/stamparm/ipsum/master/levels";
|
|
328
|
+
var ThresholdError = class extends Error {
|
|
329
|
+
constructor(threshold) {
|
|
330
|
+
super(`Allowed threshold values are from 1 to 8, 1 and 8 included. Received: ${threshold}`);
|
|
331
|
+
this.name = "ThresholdError";
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var getIpDenyList = async (threshold) => {
|
|
335
|
+
if (typeof threshold !== "number" || threshold < 1 || threshold > 8) {
|
|
336
|
+
throw new ThresholdError(threshold);
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const response = await fetch(`${baseUrl}/${threshold}.txt`);
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
throw new Error(`Error fetching data: ${response.statusText}`);
|
|
342
|
+
}
|
|
343
|
+
const data = await response.text();
|
|
344
|
+
const lines = data.split("\n");
|
|
345
|
+
return lines.filter((value) => value.length > 0);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
throw new Error(`Failed to fetch ip deny list: ${error}`);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var updateIpDenyList = async (redis, prefix, threshold, ttl) => {
|
|
351
|
+
const allIps = await getIpDenyList(threshold);
|
|
352
|
+
const allDenyLists = [prefix, DenyListExtension, "all"].join(":");
|
|
353
|
+
const ipDenyList = [prefix, DenyListExtension, IpDenyListKey].join(":");
|
|
354
|
+
const statusKey = [prefix, IpDenyListStatusKey].join(":");
|
|
355
|
+
const transaction = redis.multi();
|
|
356
|
+
transaction.sdiffstore(allDenyLists, allDenyLists, ipDenyList);
|
|
357
|
+
transaction.del(ipDenyList);
|
|
358
|
+
transaction.sadd(ipDenyList, ...allIps);
|
|
359
|
+
transaction.sdiffstore(ipDenyList, ipDenyList, allDenyLists);
|
|
360
|
+
transaction.sunionstore(allDenyLists, allDenyLists, ipDenyList);
|
|
361
|
+
transaction.set(statusKey, "valid", { px: ttl ?? getIpListTTL() });
|
|
362
|
+
return await transaction.exec();
|
|
363
|
+
};
|
|
364
|
+
var disableIpDenyList = async (redis, prefix) => {
|
|
365
|
+
const allDenyListsKey = [prefix, DenyListExtension, "all"].join(":");
|
|
366
|
+
const ipDenyListKey = [prefix, DenyListExtension, IpDenyListKey].join(":");
|
|
367
|
+
const statusKey = [prefix, IpDenyListStatusKey].join(":");
|
|
368
|
+
const transaction = redis.multi();
|
|
369
|
+
transaction.sdiffstore(allDenyListsKey, allDenyListsKey, ipDenyListKey);
|
|
370
|
+
transaction.del(ipDenyListKey);
|
|
371
|
+
transaction.set(statusKey, "disabled");
|
|
372
|
+
return await transaction.exec();
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/deny-list/deny-list.ts
|
|
270
376
|
var denyListCache = new Cache(/* @__PURE__ */ new Map());
|
|
271
377
|
var checkDenyListCache = (members) => {
|
|
272
378
|
return members.find(
|
|
@@ -279,25 +385,39 @@ var blockMember = (member) => {
|
|
|
279
385
|
denyListCache.blockUntil(member, Date.now() + 6e4);
|
|
280
386
|
};
|
|
281
387
|
var checkDenyList = async (redis, prefix, members) => {
|
|
282
|
-
const
|
|
283
|
-
|
|
388
|
+
const [deniedValues, ipDenyListStatus] = await redis.eval(
|
|
389
|
+
checkDenyListScript,
|
|
390
|
+
[
|
|
391
|
+
[prefix, DenyListExtension, "all"].join(":"),
|
|
392
|
+
[prefix, IpDenyListStatusKey].join(":")
|
|
393
|
+
],
|
|
284
394
|
members
|
|
285
395
|
);
|
|
286
|
-
let
|
|
287
|
-
|
|
396
|
+
let deniedValue = void 0;
|
|
397
|
+
deniedValues.map((memberDenied, index) => {
|
|
288
398
|
if (memberDenied) {
|
|
289
399
|
blockMember(members[index]);
|
|
290
|
-
|
|
400
|
+
deniedValue = members[index];
|
|
291
401
|
}
|
|
292
402
|
});
|
|
293
|
-
return
|
|
403
|
+
return {
|
|
404
|
+
deniedValue,
|
|
405
|
+
invalidIpDenyList: ipDenyListStatus === -2
|
|
406
|
+
};
|
|
294
407
|
};
|
|
295
|
-
var
|
|
296
|
-
if (denyListResponse) {
|
|
408
|
+
var resolveLimitPayload = (redis, prefix, [ratelimitResponse, denyListResponse], threshold) => {
|
|
409
|
+
if (denyListResponse.deniedValue) {
|
|
297
410
|
ratelimitResponse.success = false;
|
|
298
411
|
ratelimitResponse.remaining = 0;
|
|
299
412
|
ratelimitResponse.reason = "denyList";
|
|
300
|
-
ratelimitResponse.deniedValue = denyListResponse;
|
|
413
|
+
ratelimitResponse.deniedValue = denyListResponse.deniedValue;
|
|
414
|
+
}
|
|
415
|
+
if (denyListResponse.invalidIpDenyList) {
|
|
416
|
+
const updatePromise = updateIpDenyList(redis, prefix, threshold);
|
|
417
|
+
ratelimitResponse.pending = Promise.all([
|
|
418
|
+
ratelimitResponse.pending,
|
|
419
|
+
updatePromise
|
|
420
|
+
]);
|
|
301
421
|
}
|
|
302
422
|
return ratelimitResponse;
|
|
303
423
|
};
|
|
@@ -322,12 +442,14 @@ var Ratelimit = class {
|
|
|
322
442
|
primaryRedis;
|
|
323
443
|
analytics;
|
|
324
444
|
enableProtection;
|
|
445
|
+
denyListThreshold;
|
|
325
446
|
constructor(config) {
|
|
326
447
|
this.ctx = config.ctx;
|
|
327
448
|
this.limiter = config.limiter;
|
|
328
449
|
this.timeout = config.timeout ?? 5e3;
|
|
329
450
|
this.prefix = config.prefix ?? "@upstash/ratelimit";
|
|
330
451
|
this.enableProtection = config.enableProtection ?? false;
|
|
452
|
+
this.denyListThreshold = config.denyListThreshold ?? 6;
|
|
331
453
|
this.primaryRedis = "redis" in this.ctx ? this.ctx.redis : this.ctx.regionContexts[0].redis;
|
|
332
454
|
this.analytics = config.analytics ? new Analytics({
|
|
333
455
|
redis: this.primaryRedis,
|
|
@@ -457,21 +579,17 @@ var Ratelimit = class {
|
|
|
457
579
|
getRatelimitResponse = async (identifier, req) => {
|
|
458
580
|
const key = this.getKey(identifier);
|
|
459
581
|
const definedMembers = this.getDefinedMembers(identifier, req);
|
|
460
|
-
const
|
|
582
|
+
const deniedValue = checkDenyListCache(definedMembers);
|
|
461
583
|
let result;
|
|
462
|
-
if (
|
|
463
|
-
result = [defaultDeniedResponse(
|
|
584
|
+
if (deniedValue) {
|
|
585
|
+
result = [defaultDeniedResponse(deniedValue), { deniedValue, invalidIpDenyList: false }];
|
|
464
586
|
} else {
|
|
465
587
|
result = await Promise.all([
|
|
466
588
|
this.limiter().limit(this.ctx, key, req?.rate),
|
|
467
|
-
checkDenyList(
|
|
468
|
-
this.primaryRedis,
|
|
469
|
-
this.prefix,
|
|
470
|
-
definedMembers
|
|
471
|
-
)
|
|
589
|
+
this.enableProtection ? checkDenyList(this.primaryRedis, this.prefix, definedMembers) : { deniedValue: void 0, invalidIpDenyList: false }
|
|
472
590
|
]);
|
|
473
591
|
}
|
|
474
|
-
return
|
|
592
|
+
return resolveLimitPayload(this.primaryRedis, this.prefix, result, this.denyListThreshold);
|
|
475
593
|
};
|
|
476
594
|
/**
|
|
477
595
|
* Creates an array with the original response promise and a timeout promise
|
|
@@ -1081,7 +1199,8 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1081
1199
|
cacheScripts: config.cacheScripts ?? true
|
|
1082
1200
|
},
|
|
1083
1201
|
ephemeralCache: config.ephemeralCache,
|
|
1084
|
-
enableProtection: config.enableProtection
|
|
1202
|
+
enableProtection: config.enableProtection,
|
|
1203
|
+
denyListThreshold: config.denyListThreshold
|
|
1085
1204
|
});
|
|
1086
1205
|
}
|
|
1087
1206
|
/**
|
|
@@ -1447,6 +1566,7 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1447
1566
|
};
|
|
1448
1567
|
export {
|
|
1449
1568
|
Analytics,
|
|
1569
|
+
ip_deny_list_exports as IpDenyList,
|
|
1450
1570
|
MultiRegionRatelimit,
|
|
1451
1571
|
RegionRatelimit as Ratelimit
|
|
1452
1572
|
};
|