@thirdweb-dev/service-utils 0.0.0-dev-0231c73-20230926212531 → 0.0.0-dev-d86829b-20230927000710
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/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.dev.js +12 -1
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +12 -1
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +13 -3
- package/dist/declarations/src/cf-worker/index.d.ts +2 -0
- package/dist/declarations/src/cf-worker/index.d.ts.map +1 -1
- package/dist/declarations/src/core/rateLimit/index.d.ts +4 -14
- package/dist/declarations/src/core/rateLimit/index.d.ts.map +1 -1
- package/dist/declarations/src/core/rateLimit/types.d.ts +0 -2
- package/dist/declarations/src/core/rateLimit/types.d.ts.map +1 -1
- package/dist/{index-30f0f58c.cjs.prod.js → index-807f6a60.cjs.dev.js} +23 -38
- package/dist/{index-cd8f49a9.esm.js → index-bcf68113.esm.js} +23 -38
- package/dist/{index-6a597a58.cjs.dev.js → index-cfc8027b.cjs.prod.js} +23 -38
- package/node/dist/thirdweb-dev-service-utils-node.cjs.dev.js +1 -1
- package/node/dist/thirdweb-dev-service-utils-node.cjs.prod.js +1 -1
- package/node/dist/thirdweb-dev-service-utils-node.esm.js +2 -2
- package/package.json +1 -1
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
4
4
|
|
5
|
-
var index = require('../../dist/index-
|
5
|
+
var index = require('../../dist/index-807f6a60.cjs.dev.js');
|
6
6
|
var aws4fetch = require('aws4fetch');
|
7
7
|
var zod = require('zod');
|
8
8
|
var services = require('../../dist/services-79b4664f.cjs.dev.js');
|
@@ -91,6 +91,8 @@ async function publishUsageEvents(usageEvents, config) {
|
|
91
91
|
}
|
92
92
|
|
93
93
|
const DEFAULT_CACHE_TTL_SECONDS = 60;
|
94
|
+
// must be > DEFAULT_RATE_LIMIT_WINDOW_SECONDS
|
95
|
+
const DEFAULT_RATE_LIMIT_CACHE_TTL_SECONDS = 60;
|
94
96
|
async function authorizeWorker(authInput, serviceConfig) {
|
95
97
|
let authData;
|
96
98
|
try {
|
@@ -122,6 +124,14 @@ async function authorizeWorker(authInput, serviceConfig) {
|
|
122
124
|
cacheTtlSeconds: serviceConfig.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS
|
123
125
|
});
|
124
126
|
}
|
127
|
+
async function rateLimitWorker(authzResult, serviceConfig) {
|
128
|
+
return await index.rateLimit(authzResult, serviceConfig, {
|
129
|
+
get: async bucketId => serviceConfig.kvStore.get(bucketId),
|
130
|
+
put: (bucketId, count) => serviceConfig.kvStore.put(bucketId, count, {
|
131
|
+
expirationTtl: DEFAULT_RATE_LIMIT_CACHE_TTL_SECONDS
|
132
|
+
})
|
133
|
+
});
|
134
|
+
}
|
125
135
|
async function extractAuthorizationData(authInput) {
|
126
136
|
const requestUrl = new URL(authInput.req.url);
|
127
137
|
const headers = authInput.req.headers;
|
@@ -249,3 +259,4 @@ exports.extractAuthorizationData = extractAuthorizationData;
|
|
249
259
|
exports.hashSecretKey = hashSecretKey;
|
250
260
|
exports.logHttpRequest = logHttpRequest;
|
251
261
|
exports.publishUsageEvents = publishUsageEvents;
|
262
|
+
exports.rateLimitWorker = rateLimitWorker;
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
4
4
|
|
5
|
-
var index = require('../../dist/index-
|
5
|
+
var index = require('../../dist/index-cfc8027b.cjs.prod.js');
|
6
6
|
var aws4fetch = require('aws4fetch');
|
7
7
|
var zod = require('zod');
|
8
8
|
var services = require('../../dist/services-04997839.cjs.prod.js');
|
@@ -91,6 +91,8 @@ async function publishUsageEvents(usageEvents, config) {
|
|
91
91
|
}
|
92
92
|
|
93
93
|
const DEFAULT_CACHE_TTL_SECONDS = 60;
|
94
|
+
// must be > DEFAULT_RATE_LIMIT_WINDOW_SECONDS
|
95
|
+
const DEFAULT_RATE_LIMIT_CACHE_TTL_SECONDS = 60;
|
94
96
|
async function authorizeWorker(authInput, serviceConfig) {
|
95
97
|
let authData;
|
96
98
|
try {
|
@@ -122,6 +124,14 @@ async function authorizeWorker(authInput, serviceConfig) {
|
|
122
124
|
cacheTtlSeconds: serviceConfig.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS
|
123
125
|
});
|
124
126
|
}
|
127
|
+
async function rateLimitWorker(authzResult, serviceConfig) {
|
128
|
+
return await index.rateLimit(authzResult, serviceConfig, {
|
129
|
+
get: async bucketId => serviceConfig.kvStore.get(bucketId),
|
130
|
+
put: (bucketId, count) => serviceConfig.kvStore.put(bucketId, count, {
|
131
|
+
expirationTtl: DEFAULT_RATE_LIMIT_CACHE_TTL_SECONDS
|
132
|
+
})
|
133
|
+
});
|
134
|
+
}
|
125
135
|
async function extractAuthorizationData(authInput) {
|
126
136
|
const requestUrl = new URL(authInput.req.url);
|
127
137
|
const headers = authInput.req.headers;
|
@@ -249,3 +259,4 @@ exports.extractAuthorizationData = extractAuthorizationData;
|
|
249
259
|
exports.hashSecretKey = hashSecretKey;
|
250
260
|
exports.logHttpRequest = logHttpRequest;
|
251
261
|
exports.publishUsageEvents = publishUsageEvents;
|
262
|
+
exports.rateLimitWorker = rateLimitWorker;
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { a as authorize } from '../../dist/index-
|
2
|
-
export { r as rateLimit, u as usageLimit } from '../../dist/index-
|
1
|
+
import { a as authorize, r as rateLimit } from '../../dist/index-bcf68113.esm.js';
|
2
|
+
export { r as rateLimit, u as usageLimit } from '../../dist/index-bcf68113.esm.js';
|
3
3
|
import { AwsClient } from 'aws4fetch';
|
4
4
|
import { z } from 'zod';
|
5
5
|
export { b as SERVICES, S as SERVICE_DEFINITIONS, a as SERVICE_NAMES, g as getServiceByName } from '../../dist/services-bc12a5f6.esm.js';
|
@@ -88,6 +88,8 @@ async function publishUsageEvents(usageEvents, config) {
|
|
88
88
|
}
|
89
89
|
|
90
90
|
const DEFAULT_CACHE_TTL_SECONDS = 60;
|
91
|
+
// must be > DEFAULT_RATE_LIMIT_WINDOW_SECONDS
|
92
|
+
const DEFAULT_RATE_LIMIT_CACHE_TTL_SECONDS = 60;
|
91
93
|
async function authorizeWorker(authInput, serviceConfig) {
|
92
94
|
let authData;
|
93
95
|
try {
|
@@ -119,6 +121,14 @@ async function authorizeWorker(authInput, serviceConfig) {
|
|
119
121
|
cacheTtlSeconds: serviceConfig.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS
|
120
122
|
});
|
121
123
|
}
|
124
|
+
async function rateLimitWorker(authzResult, serviceConfig) {
|
125
|
+
return await rateLimit(authzResult, serviceConfig, {
|
126
|
+
get: async bucketId => serviceConfig.kvStore.get(bucketId),
|
127
|
+
put: (bucketId, count) => serviceConfig.kvStore.put(bucketId, count, {
|
128
|
+
expirationTtl: DEFAULT_RATE_LIMIT_CACHE_TTL_SECONDS
|
129
|
+
})
|
130
|
+
});
|
131
|
+
}
|
122
132
|
async function extractAuthorizationData(authInput) {
|
123
133
|
const requestUrl = new URL(authInput.req.url);
|
124
134
|
const headers = authInput.req.headers;
|
@@ -234,4 +244,4 @@ async function logHttpRequest(_ref) {
|
|
234
244
|
}
|
235
245
|
}
|
236
246
|
|
237
|
-
export { authorizeWorker, deriveClientIdFromSecretKeyHash, extractAuthorizationData, hashSecretKey, logHttpRequest, publishUsageEvents };
|
247
|
+
export { authorizeWorker, deriveClientIdFromSecretKeyHash, extractAuthorizationData, hashSecretKey, logHttpRequest, publishUsageEvents, rateLimitWorker };
|
@@ -3,6 +3,7 @@ import type { CoreServiceConfig } from "../core/api";
|
|
3
3
|
import type { Request } from "@cloudflare/workers-types";
|
4
4
|
import type { AuthorizationInput } from "../core/authorize";
|
5
5
|
import type { AuthorizationResult } from "../core/authorize/types";
|
6
|
+
import type { RateLimitResult } from "../core/rateLimit/types";
|
6
7
|
import type { CoreAuthInput } from "../core/types";
|
7
8
|
export * from "./usage";
|
8
9
|
export * from "../core/services";
|
@@ -17,6 +18,7 @@ type AuthInput = CoreAuthInput & {
|
|
17
18
|
req: Request;
|
18
19
|
};
|
19
20
|
export declare function authorizeWorker(authInput: AuthInput, serviceConfig: WorkerServiceConfig): Promise<AuthorizationResult>;
|
21
|
+
export declare function rateLimitWorker(authzResult: AuthorizationResult, serviceConfig: WorkerServiceConfig): Promise<RateLimitResult>;
|
20
22
|
export declare function extractAuthorizationData(authInput: AuthInput): Promise<AuthorizationInput>;
|
21
23
|
export declare function hashSecretKey(secretKey: string): Promise<string>;
|
22
24
|
export declare function deriveClientIdFromSecretKeyHash(secretKeyHash: string): string;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/cf-worker","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACT,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/cf-worker","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACT,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IACpD,OAAO,EAAE,WAAW,CAAC;IACrB,GAAG,EAAE,gBAAgB,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAMF,KAAK,SAAS,GAAG,aAAa,GAAG;IAC/B,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,wBAAsB,eAAe,CACnC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,mBAAmB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA0C9B;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,mBAAmB,EAChC,aAAa,EAAE,mBAAmB,GACjC,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,kBAAkB,CAAC,CA2E7B;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,mBAIpD;AAED,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,UAEpE;AAQD,wBAAsB,cAAc,CAAC,EACnC,MAAM,EACN,QAAQ,EACR,GAAG,EACH,GAAG,EACH,QAAQ,EACR,aAAa,GACd,EAAE,SAAS,GAAG;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,QAAQ,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CAChC,iBAwBA"}
|
@@ -1,20 +1,10 @@
|
|
1
1
|
import { CoreServiceConfig } from "../api";
|
2
2
|
import { AuthorizationResult } from "../authorize/types";
|
3
3
|
import { RateLimitResult } from "./types";
|
4
|
-
type
|
5
|
-
|
6
|
-
|
4
|
+
type CacheOptions = {
|
5
|
+
get: (bucketId: string) => Promise<string | null>;
|
6
|
+
put: (bucketId: string, count: string) => Promise<void> | void;
|
7
7
|
};
|
8
|
-
export declare function rateLimit(
|
9
|
-
authzResult: AuthorizationResult;
|
10
|
-
serviceConfig: CoreServiceConfig;
|
11
|
-
redis: IRedis;
|
12
|
-
/**
|
13
|
-
* Sample requests to reduce load on Redis.
|
14
|
-
* This scales down the request count and the rate limit threshold.
|
15
|
-
* @default 1.0
|
16
|
-
*/
|
17
|
-
sampleRate?: number;
|
18
|
-
}): Promise<RateLimitResult>;
|
8
|
+
export declare function rateLimit(authzResult: AuthorizationResult, serviceConfig: CoreServiceConfig, cacheOptions: CacheOptions): Promise<RateLimitResult>;
|
19
9
|
export {};
|
20
10
|
//# sourceMappingURL=index.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAuB,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAuB,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,KAAK,YAAY,GAAG;IAClB,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClD,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAChE,CAAC;AAEF,wBAAsB,SAAS,CAC7B,WAAW,EAAE,mBAAmB,EAChC,aAAa,EAAE,iBAAiB,EAChC,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,eAAe,CAAC,CAuD1B"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB;IACE,
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB;IACE,WAAW,EAAE,KAAK,CAAC;CACpB,GACD;IACE,WAAW,EAAE,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
|
@@ -449,21 +449,12 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
449
449
|
};
|
450
450
|
}
|
451
451
|
|
452
|
-
const
|
452
|
+
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS = 10;
|
453
|
+
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
453
454
|
|
454
|
-
|
455
|
-
|
456
|
-
async function rateLimit(args) {
|
457
|
-
const {
|
458
|
-
authzResult,
|
459
|
-
serviceConfig,
|
460
|
-
redis,
|
461
|
-
sampleRate = 1.0
|
462
|
-
} = args;
|
463
|
-
const shouldCountRequest = Math.random() < sampleRate;
|
464
|
-
if (!shouldCountRequest || !authzResult.authorized) {
|
455
|
+
async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
456
|
+
if (!authzResult.authorized) {
|
465
457
|
return {
|
466
|
-
requestCount: 0,
|
467
458
|
rateLimited: false
|
468
459
|
};
|
469
460
|
}
|
@@ -471,54 +462,48 @@ async function rateLimit(args) {
|
|
471
462
|
apiKeyMeta,
|
472
463
|
accountMeta
|
473
464
|
} = authzResult;
|
465
|
+
const {
|
466
|
+
rateLimits
|
467
|
+
} = apiKeyMeta || accountMeta || {};
|
474
468
|
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
475
469
|
const {
|
476
470
|
serviceScope
|
477
471
|
} = serviceConfig;
|
478
|
-
|
479
|
-
rateLimits
|
480
|
-
} = apiKeyMeta || accountMeta || {};
|
481
|
-
const limitPerSecond = rateLimits?.[serviceScope];
|
482
|
-
if (!limitPerSecond) {
|
472
|
+
if (!rateLimits || !(serviceScope in rateLimits)) {
|
483
473
|
// No rate limit is provided. Assume the request is not rate limited.
|
484
474
|
return {
|
485
|
-
requestCount: 0,
|
486
475
|
rateLimited: false
|
487
476
|
};
|
488
477
|
}
|
478
|
+
const limit = rateLimits[serviceScope];
|
489
479
|
|
490
|
-
//
|
491
|
-
const
|
492
|
-
const key =
|
493
|
-
|
494
|
-
|
495
|
-
const requestCount = await redis.incr(key);
|
496
|
-
if (requestCount === 1) {
|
497
|
-
// For the first increment, set an expiration to clean up this key.
|
498
|
-
await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
|
499
|
-
}
|
480
|
+
// Floors the current time to the nearest DEFAULT_RATE_LIMIT_WINDOW_SECONDS.
|
481
|
+
const bucketId = Math.floor(Date.now() / (1000 * DEFAULT_RATE_LIMIT_WINDOW_SECONDS)) * DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
|
482
|
+
const key = [serviceScope, accountId, bucketId].join(":");
|
483
|
+
const value = parseInt((await cacheOptions.get(key)) || "0");
|
484
|
+
const current = value + 1;
|
500
485
|
|
501
|
-
//
|
502
|
-
const
|
503
|
-
if (
|
504
|
-
//
|
486
|
+
// limit is in seconds, but we need in DEFAULT_RATE_LIMIT_WINDOW_SECONDS
|
487
|
+
const limitWindow = limit * DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
|
488
|
+
if (current > limitWindow) {
|
489
|
+
// report rate limit hits
|
505
490
|
if (apiKeyMeta?.id) {
|
506
491
|
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
507
492
|
}
|
508
493
|
|
509
|
-
//
|
510
|
-
if (
|
494
|
+
// actually rate limit only when reached hard limit
|
495
|
+
if (current > limitWindow * HARD_LIMIT_MULTIPLE) {
|
511
496
|
return {
|
512
|
-
requestCount,
|
513
497
|
rateLimited: true,
|
514
498
|
status: 429,
|
515
|
-
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${
|
499
|
+
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limit} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
|
516
500
|
errorCode: "RATE_LIMIT_EXCEEDED"
|
517
501
|
};
|
518
502
|
}
|
503
|
+
} else {
|
504
|
+
await cacheOptions.put(key, current.toString());
|
519
505
|
}
|
520
506
|
return {
|
521
|
-
requestCount,
|
522
507
|
rateLimited: false
|
523
508
|
};
|
524
509
|
}
|
@@ -447,21 +447,12 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
447
447
|
};
|
448
448
|
}
|
449
449
|
|
450
|
-
const
|
450
|
+
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS = 10;
|
451
|
+
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
451
452
|
|
452
|
-
|
453
|
-
|
454
|
-
async function rateLimit(args) {
|
455
|
-
const {
|
456
|
-
authzResult,
|
457
|
-
serviceConfig,
|
458
|
-
redis,
|
459
|
-
sampleRate = 1.0
|
460
|
-
} = args;
|
461
|
-
const shouldCountRequest = Math.random() < sampleRate;
|
462
|
-
if (!shouldCountRequest || !authzResult.authorized) {
|
453
|
+
async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
454
|
+
if (!authzResult.authorized) {
|
463
455
|
return {
|
464
|
-
requestCount: 0,
|
465
456
|
rateLimited: false
|
466
457
|
};
|
467
458
|
}
|
@@ -469,54 +460,48 @@ async function rateLimit(args) {
|
|
469
460
|
apiKeyMeta,
|
470
461
|
accountMeta
|
471
462
|
} = authzResult;
|
463
|
+
const {
|
464
|
+
rateLimits
|
465
|
+
} = apiKeyMeta || accountMeta || {};
|
472
466
|
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
473
467
|
const {
|
474
468
|
serviceScope
|
475
469
|
} = serviceConfig;
|
476
|
-
|
477
|
-
rateLimits
|
478
|
-
} = apiKeyMeta || accountMeta || {};
|
479
|
-
const limitPerSecond = rateLimits?.[serviceScope];
|
480
|
-
if (!limitPerSecond) {
|
470
|
+
if (!rateLimits || !(serviceScope in rateLimits)) {
|
481
471
|
// No rate limit is provided. Assume the request is not rate limited.
|
482
472
|
return {
|
483
|
-
requestCount: 0,
|
484
473
|
rateLimited: false
|
485
474
|
};
|
486
475
|
}
|
476
|
+
const limit = rateLimits[serviceScope];
|
487
477
|
|
488
|
-
//
|
489
|
-
const
|
490
|
-
const key =
|
491
|
-
|
492
|
-
|
493
|
-
const requestCount = await redis.incr(key);
|
494
|
-
if (requestCount === 1) {
|
495
|
-
// For the first increment, set an expiration to clean up this key.
|
496
|
-
await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
|
497
|
-
}
|
478
|
+
// Floors the current time to the nearest DEFAULT_RATE_LIMIT_WINDOW_SECONDS.
|
479
|
+
const bucketId = Math.floor(Date.now() / (1000 * DEFAULT_RATE_LIMIT_WINDOW_SECONDS)) * DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
|
480
|
+
const key = [serviceScope, accountId, bucketId].join(":");
|
481
|
+
const value = parseInt((await cacheOptions.get(key)) || "0");
|
482
|
+
const current = value + 1;
|
498
483
|
|
499
|
-
//
|
500
|
-
const
|
501
|
-
if (
|
502
|
-
//
|
484
|
+
// limit is in seconds, but we need in DEFAULT_RATE_LIMIT_WINDOW_SECONDS
|
485
|
+
const limitWindow = limit * DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
|
486
|
+
if (current > limitWindow) {
|
487
|
+
// report rate limit hits
|
503
488
|
if (apiKeyMeta?.id) {
|
504
489
|
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
505
490
|
}
|
506
491
|
|
507
|
-
//
|
508
|
-
if (
|
492
|
+
// actually rate limit only when reached hard limit
|
493
|
+
if (current > limitWindow * HARD_LIMIT_MULTIPLE) {
|
509
494
|
return {
|
510
|
-
requestCount,
|
511
495
|
rateLimited: true,
|
512
496
|
status: 429,
|
513
|
-
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${
|
497
|
+
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limit} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
|
514
498
|
errorCode: "RATE_LIMIT_EXCEEDED"
|
515
499
|
};
|
516
500
|
}
|
501
|
+
} else {
|
502
|
+
await cacheOptions.put(key, current.toString());
|
517
503
|
}
|
518
504
|
return {
|
519
|
-
requestCount,
|
520
505
|
rateLimited: false
|
521
506
|
};
|
522
507
|
}
|
@@ -449,21 +449,12 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
449
449
|
};
|
450
450
|
}
|
451
451
|
|
452
|
-
const
|
452
|
+
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS = 10;
|
453
|
+
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
453
454
|
|
454
|
-
|
455
|
-
|
456
|
-
async function rateLimit(args) {
|
457
|
-
const {
|
458
|
-
authzResult,
|
459
|
-
serviceConfig,
|
460
|
-
redis,
|
461
|
-
sampleRate = 1.0
|
462
|
-
} = args;
|
463
|
-
const shouldCountRequest = Math.random() < sampleRate;
|
464
|
-
if (!shouldCountRequest || !authzResult.authorized) {
|
455
|
+
async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
456
|
+
if (!authzResult.authorized) {
|
465
457
|
return {
|
466
|
-
requestCount: 0,
|
467
458
|
rateLimited: false
|
468
459
|
};
|
469
460
|
}
|
@@ -471,54 +462,48 @@ async function rateLimit(args) {
|
|
471
462
|
apiKeyMeta,
|
472
463
|
accountMeta
|
473
464
|
} = authzResult;
|
465
|
+
const {
|
466
|
+
rateLimits
|
467
|
+
} = apiKeyMeta || accountMeta || {};
|
474
468
|
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
475
469
|
const {
|
476
470
|
serviceScope
|
477
471
|
} = serviceConfig;
|
478
|
-
|
479
|
-
rateLimits
|
480
|
-
} = apiKeyMeta || accountMeta || {};
|
481
|
-
const limitPerSecond = rateLimits?.[serviceScope];
|
482
|
-
if (!limitPerSecond) {
|
472
|
+
if (!rateLimits || !(serviceScope in rateLimits)) {
|
483
473
|
// No rate limit is provided. Assume the request is not rate limited.
|
484
474
|
return {
|
485
|
-
requestCount: 0,
|
486
475
|
rateLimited: false
|
487
476
|
};
|
488
477
|
}
|
478
|
+
const limit = rateLimits[serviceScope];
|
489
479
|
|
490
|
-
//
|
491
|
-
const
|
492
|
-
const key =
|
493
|
-
|
494
|
-
|
495
|
-
const requestCount = await redis.incr(key);
|
496
|
-
if (requestCount === 1) {
|
497
|
-
// For the first increment, set an expiration to clean up this key.
|
498
|
-
await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
|
499
|
-
}
|
480
|
+
// Floors the current time to the nearest DEFAULT_RATE_LIMIT_WINDOW_SECONDS.
|
481
|
+
const bucketId = Math.floor(Date.now() / (1000 * DEFAULT_RATE_LIMIT_WINDOW_SECONDS)) * DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
|
482
|
+
const key = [serviceScope, accountId, bucketId].join(":");
|
483
|
+
const value = parseInt((await cacheOptions.get(key)) || "0");
|
484
|
+
const current = value + 1;
|
500
485
|
|
501
|
-
//
|
502
|
-
const
|
503
|
-
if (
|
504
|
-
//
|
486
|
+
// limit is in seconds, but we need in DEFAULT_RATE_LIMIT_WINDOW_SECONDS
|
487
|
+
const limitWindow = limit * DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
|
488
|
+
if (current > limitWindow) {
|
489
|
+
// report rate limit hits
|
505
490
|
if (apiKeyMeta?.id) {
|
506
491
|
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
507
492
|
}
|
508
493
|
|
509
|
-
//
|
510
|
-
if (
|
494
|
+
// actually rate limit only when reached hard limit
|
495
|
+
if (current > limitWindow * HARD_LIMIT_MULTIPLE) {
|
511
496
|
return {
|
512
|
-
requestCount,
|
513
497
|
rateLimited: true,
|
514
498
|
status: 429,
|
515
|
-
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${
|
499
|
+
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limit} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
|
516
500
|
errorCode: "RATE_LIMIT_EXCEEDED"
|
517
501
|
};
|
518
502
|
}
|
503
|
+
} else {
|
504
|
+
await cacheOptions.put(key, current.toString());
|
519
505
|
}
|
520
506
|
return {
|
521
|
-
requestCount,
|
522
507
|
rateLimited: false
|
523
508
|
};
|
524
509
|
}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
4
4
|
|
5
5
|
var node_crypto = require('node:crypto');
|
6
|
-
var index = require('../../dist/index-
|
6
|
+
var index = require('../../dist/index-807f6a60.cjs.dev.js');
|
7
7
|
var services = require('../../dist/services-79b4664f.cjs.dev.js');
|
8
8
|
|
9
9
|
/**
|
@@ -3,7 +3,7 @@
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
4
4
|
|
5
5
|
var node_crypto = require('node:crypto');
|
6
|
-
var index = require('../../dist/index-
|
6
|
+
var index = require('../../dist/index-cfc8027b.cjs.prod.js');
|
7
7
|
var services = require('../../dist/services-04997839.cjs.prod.js');
|
8
8
|
|
9
9
|
/**
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { createHash } from 'node:crypto';
|
2
|
-
import { a as authorize } from '../../dist/index-
|
3
|
-
export { r as rateLimit, u as usageLimit } from '../../dist/index-
|
2
|
+
import { a as authorize } from '../../dist/index-bcf68113.esm.js';
|
3
|
+
export { r as rateLimit, u as usageLimit } from '../../dist/index-bcf68113.esm.js';
|
4
4
|
export { b as SERVICES, S as SERVICE_DEFINITIONS, a as SERVICE_NAMES, g as getServiceByName } from '../../dist/services-bc12a5f6.esm.js';
|
5
5
|
|
6
6
|
/**
|
package/package.json
CHANGED