@thirdweb-dev/service-utils 0.0.0-dev-8dbcee9-20230925135804 → 0.0.0-dev-0231c73-20230926212531
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 +1 -12
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +1 -12
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +3 -13
- package/dist/declarations/src/cf-worker/index.d.ts +0 -2
- package/dist/declarations/src/cf-worker/index.d.ts.map +1 -1
- package/dist/declarations/src/core/rateLimit/index.d.ts +14 -4
- package/dist/declarations/src/core/rateLimit/index.d.ts.map +1 -1
- package/dist/declarations/src/core/rateLimit/types.d.ts +2 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts.map +1 -1
- package/dist/{index-807f6a60.cjs.dev.js → index-30f0f58c.cjs.prod.js} +38 -23
- package/dist/{index-cfc8027b.cjs.prod.js → index-6a597a58.cjs.dev.js} +38 -23
- package/dist/{index-bcf68113.esm.js → index-cd8f49a9.esm.js} +38 -23
- 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-6a597a58.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,8 +91,6 @@ 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;
|
96
94
|
async function authorizeWorker(authInput, serviceConfig) {
|
97
95
|
let authData;
|
98
96
|
try {
|
@@ -124,14 +122,6 @@ async function authorizeWorker(authInput, serviceConfig) {
|
|
124
122
|
cacheTtlSeconds: serviceConfig.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS
|
125
123
|
});
|
126
124
|
}
|
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
|
-
}
|
135
125
|
async function extractAuthorizationData(authInput) {
|
136
126
|
const requestUrl = new URL(authInput.req.url);
|
137
127
|
const headers = authInput.req.headers;
|
@@ -259,4 +249,3 @@ exports.extractAuthorizationData = extractAuthorizationData;
|
|
259
249
|
exports.hashSecretKey = hashSecretKey;
|
260
250
|
exports.logHttpRequest = logHttpRequest;
|
261
251
|
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-30f0f58c.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,8 +91,6 @@ 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;
|
96
94
|
async function authorizeWorker(authInput, serviceConfig) {
|
97
95
|
let authData;
|
98
96
|
try {
|
@@ -124,14 +122,6 @@ async function authorizeWorker(authInput, serviceConfig) {
|
|
124
122
|
cacheTtlSeconds: serviceConfig.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS
|
125
123
|
});
|
126
124
|
}
|
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
|
-
}
|
135
125
|
async function extractAuthorizationData(authInput) {
|
136
126
|
const requestUrl = new URL(authInput.req.url);
|
137
127
|
const headers = authInput.req.headers;
|
@@ -259,4 +249,3 @@ exports.extractAuthorizationData = extractAuthorizationData;
|
|
259
249
|
exports.hashSecretKey = hashSecretKey;
|
260
250
|
exports.logHttpRequest = logHttpRequest;
|
261
251
|
exports.publishUsageEvents = publishUsageEvents;
|
262
|
-
exports.rateLimitWorker = rateLimitWorker;
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { a as authorize
|
2
|
-
export { r as rateLimit, u as usageLimit } from '../../dist/index-
|
1
|
+
import { a as authorize } from '../../dist/index-cd8f49a9.esm.js';
|
2
|
+
export { r as rateLimit, u as usageLimit } from '../../dist/index-cd8f49a9.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,8 +88,6 @@ 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;
|
93
91
|
async function authorizeWorker(authInput, serviceConfig) {
|
94
92
|
let authData;
|
95
93
|
try {
|
@@ -121,14 +119,6 @@ async function authorizeWorker(authInput, serviceConfig) {
|
|
121
119
|
cacheTtlSeconds: serviceConfig.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS
|
122
120
|
});
|
123
121
|
}
|
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
|
-
}
|
132
122
|
async function extractAuthorizationData(authInput) {
|
133
123
|
const requestUrl = new URL(authInput.req.url);
|
134
124
|
const headers = authInput.req.headers;
|
@@ -244,4 +234,4 @@ async function logHttpRequest(_ref) {
|
|
244
234
|
}
|
245
235
|
}
|
246
236
|
|
247
|
-
export { authorizeWorker, deriveClientIdFromSecretKeyHash, extractAuthorizationData, hashSecretKey, logHttpRequest, publishUsageEvents
|
237
|
+
export { authorizeWorker, deriveClientIdFromSecretKeyHash, extractAuthorizationData, hashSecretKey, logHttpRequest, publishUsageEvents };
|
@@ -3,7 +3,6 @@ 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";
|
7
6
|
import type { CoreAuthInput } from "../core/types";
|
8
7
|
export * from "./usage";
|
9
8
|
export * from "../core/services";
|
@@ -18,7 +17,6 @@ type AuthInput = CoreAuthInput & {
|
|
18
17
|
req: Request;
|
19
18
|
};
|
20
19
|
export declare function authorizeWorker(authInput: AuthInput, serviceConfig: WorkerServiceConfig): Promise<AuthorizationResult>;
|
21
|
-
export declare function rateLimitWorker(authzResult: AuthorizationResult, serviceConfig: WorkerServiceConfig): Promise<RateLimitResult>;
|
22
20
|
export declare function extractAuthorizationData(authInput: AuthInput): Promise<AuthorizationInput>;
|
23
21
|
export declare function hashSecretKey(secretKey: string): Promise<string>;
|
24
22
|
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,
|
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;AAEnD,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;AAIF,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,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,10 +1,20 @@
|
|
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 IRedis = {
|
5
|
+
incr: (key: string) => Promise<number>;
|
6
|
+
expire: (key: string, ttlSeconds: number) => Promise<0 | 1>;
|
7
7
|
};
|
8
|
-
export declare function rateLimit(
|
8
|
+
export declare function rateLimit(args: {
|
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>;
|
9
19
|
export {};
|
10
20
|
//# 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;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,KAAK,MAAM,GAAG;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAC7D,CAAC;AAEF,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,WAAW,EAAE,mBAAmB,CAAC;IACjC,aAAa,EAAE,iBAAiB,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,eAAe,CAAC,CAiE3B"}
|
@@ -1 +1 @@
|
|
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"}
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB;IACE,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,KAAK,CAAC;CACpB,GACD;IACE,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
|
@@ -449,12 +449,21 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
449
449
|
};
|
450
450
|
}
|
451
451
|
|
452
|
-
const
|
453
|
-
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
452
|
+
const RATE_LIMIT_WINDOW_SECONDS = 10;
|
454
453
|
|
455
|
-
|
456
|
-
|
454
|
+
// Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
|
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) {
|
457
465
|
return {
|
466
|
+
requestCount: 0,
|
458
467
|
rateLimited: false
|
459
468
|
};
|
460
469
|
}
|
@@ -462,48 +471,54 @@ async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
|
462
471
|
apiKeyMeta,
|
463
472
|
accountMeta
|
464
473
|
} = authzResult;
|
465
|
-
const {
|
466
|
-
rateLimits
|
467
|
-
} = apiKeyMeta || accountMeta || {};
|
468
474
|
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
469
475
|
const {
|
470
476
|
serviceScope
|
471
477
|
} = serviceConfig;
|
472
|
-
|
478
|
+
const {
|
479
|
+
rateLimits
|
480
|
+
} = apiKeyMeta || accountMeta || {};
|
481
|
+
const limitPerSecond = rateLimits?.[serviceScope];
|
482
|
+
if (!limitPerSecond) {
|
473
483
|
// No rate limit is provided. Assume the request is not rate limited.
|
474
484
|
return {
|
485
|
+
requestCount: 0,
|
475
486
|
rateLimited: false
|
476
487
|
};
|
477
488
|
}
|
478
|
-
const limit = rateLimits[serviceScope];
|
479
489
|
|
480
|
-
//
|
481
|
-
const
|
482
|
-
const key =
|
483
|
-
|
484
|
-
|
490
|
+
// Gets the 10-second window for the current timestamp.
|
491
|
+
const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
|
492
|
+
const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
|
493
|
+
|
494
|
+
// Increment and get the current request count in this window.
|
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
|
+
}
|
485
500
|
|
486
|
-
// limit
|
487
|
-
const
|
488
|
-
if (
|
489
|
-
//
|
501
|
+
// Get the limit for this window accounting for the sample rate.
|
502
|
+
const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
|
503
|
+
if (requestCount > limitPerWindow) {
|
504
|
+
// Report rate limit hits.
|
490
505
|
if (apiKeyMeta?.id) {
|
491
506
|
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
492
507
|
}
|
493
508
|
|
494
|
-
//
|
495
|
-
if (
|
509
|
+
// Reject requests when they've exceeded 2x the rate limit.
|
510
|
+
if (requestCount > 2 * limitPerWindow) {
|
496
511
|
return {
|
512
|
+
requestCount,
|
497
513
|
rateLimited: true,
|
498
514
|
status: 429,
|
499
|
-
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${
|
515
|
+
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limitPerSecond} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
|
500
516
|
errorCode: "RATE_LIMIT_EXCEEDED"
|
501
517
|
};
|
502
518
|
}
|
503
|
-
} else {
|
504
|
-
await cacheOptions.put(key, current.toString());
|
505
519
|
}
|
506
520
|
return {
|
521
|
+
requestCount,
|
507
522
|
rateLimited: false
|
508
523
|
};
|
509
524
|
}
|
@@ -449,12 +449,21 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
449
449
|
};
|
450
450
|
}
|
451
451
|
|
452
|
-
const
|
453
|
-
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
452
|
+
const RATE_LIMIT_WINDOW_SECONDS = 10;
|
454
453
|
|
455
|
-
|
456
|
-
|
454
|
+
// Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
|
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) {
|
457
465
|
return {
|
466
|
+
requestCount: 0,
|
458
467
|
rateLimited: false
|
459
468
|
};
|
460
469
|
}
|
@@ -462,48 +471,54 @@ async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
|
462
471
|
apiKeyMeta,
|
463
472
|
accountMeta
|
464
473
|
} = authzResult;
|
465
|
-
const {
|
466
|
-
rateLimits
|
467
|
-
} = apiKeyMeta || accountMeta || {};
|
468
474
|
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
469
475
|
const {
|
470
476
|
serviceScope
|
471
477
|
} = serviceConfig;
|
472
|
-
|
478
|
+
const {
|
479
|
+
rateLimits
|
480
|
+
} = apiKeyMeta || accountMeta || {};
|
481
|
+
const limitPerSecond = rateLimits?.[serviceScope];
|
482
|
+
if (!limitPerSecond) {
|
473
483
|
// No rate limit is provided. Assume the request is not rate limited.
|
474
484
|
return {
|
485
|
+
requestCount: 0,
|
475
486
|
rateLimited: false
|
476
487
|
};
|
477
488
|
}
|
478
|
-
const limit = rateLimits[serviceScope];
|
479
489
|
|
480
|
-
//
|
481
|
-
const
|
482
|
-
const key =
|
483
|
-
|
484
|
-
|
490
|
+
// Gets the 10-second window for the current timestamp.
|
491
|
+
const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
|
492
|
+
const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
|
493
|
+
|
494
|
+
// Increment and get the current request count in this window.
|
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
|
+
}
|
485
500
|
|
486
|
-
// limit
|
487
|
-
const
|
488
|
-
if (
|
489
|
-
//
|
501
|
+
// Get the limit for this window accounting for the sample rate.
|
502
|
+
const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
|
503
|
+
if (requestCount > limitPerWindow) {
|
504
|
+
// Report rate limit hits.
|
490
505
|
if (apiKeyMeta?.id) {
|
491
506
|
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
492
507
|
}
|
493
508
|
|
494
|
-
//
|
495
|
-
if (
|
509
|
+
// Reject requests when they've exceeded 2x the rate limit.
|
510
|
+
if (requestCount > 2 * limitPerWindow) {
|
496
511
|
return {
|
512
|
+
requestCount,
|
497
513
|
rateLimited: true,
|
498
514
|
status: 429,
|
499
|
-
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${
|
515
|
+
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limitPerSecond} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
|
500
516
|
errorCode: "RATE_LIMIT_EXCEEDED"
|
501
517
|
};
|
502
518
|
}
|
503
|
-
} else {
|
504
|
-
await cacheOptions.put(key, current.toString());
|
505
519
|
}
|
506
520
|
return {
|
521
|
+
requestCount,
|
507
522
|
rateLimited: false
|
508
523
|
};
|
509
524
|
}
|
@@ -447,12 +447,21 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
447
447
|
};
|
448
448
|
}
|
449
449
|
|
450
|
-
const
|
451
|
-
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
450
|
+
const RATE_LIMIT_WINDOW_SECONDS = 10;
|
452
451
|
|
453
|
-
|
454
|
-
|
452
|
+
// Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
|
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) {
|
455
463
|
return {
|
464
|
+
requestCount: 0,
|
456
465
|
rateLimited: false
|
457
466
|
};
|
458
467
|
}
|
@@ -460,48 +469,54 @@ async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
|
460
469
|
apiKeyMeta,
|
461
470
|
accountMeta
|
462
471
|
} = authzResult;
|
463
|
-
const {
|
464
|
-
rateLimits
|
465
|
-
} = apiKeyMeta || accountMeta || {};
|
466
472
|
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
467
473
|
const {
|
468
474
|
serviceScope
|
469
475
|
} = serviceConfig;
|
470
|
-
|
476
|
+
const {
|
477
|
+
rateLimits
|
478
|
+
} = apiKeyMeta || accountMeta || {};
|
479
|
+
const limitPerSecond = rateLimits?.[serviceScope];
|
480
|
+
if (!limitPerSecond) {
|
471
481
|
// No rate limit is provided. Assume the request is not rate limited.
|
472
482
|
return {
|
483
|
+
requestCount: 0,
|
473
484
|
rateLimited: false
|
474
485
|
};
|
475
486
|
}
|
476
|
-
const limit = rateLimits[serviceScope];
|
477
487
|
|
478
|
-
//
|
479
|
-
const
|
480
|
-
const key =
|
481
|
-
|
482
|
-
|
488
|
+
// Gets the 10-second window for the current timestamp.
|
489
|
+
const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
|
490
|
+
const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
|
491
|
+
|
492
|
+
// Increment and get the current request count in this window.
|
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
|
+
}
|
483
498
|
|
484
|
-
// limit
|
485
|
-
const
|
486
|
-
if (
|
487
|
-
//
|
499
|
+
// Get the limit for this window accounting for the sample rate.
|
500
|
+
const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
|
501
|
+
if (requestCount > limitPerWindow) {
|
502
|
+
// Report rate limit hits.
|
488
503
|
if (apiKeyMeta?.id) {
|
489
504
|
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
490
505
|
}
|
491
506
|
|
492
|
-
//
|
493
|
-
if (
|
507
|
+
// Reject requests when they've exceeded 2x the rate limit.
|
508
|
+
if (requestCount > 2 * limitPerWindow) {
|
494
509
|
return {
|
510
|
+
requestCount,
|
495
511
|
rateLimited: true,
|
496
512
|
status: 429,
|
497
|
-
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${
|
513
|
+
errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limitPerSecond} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
|
498
514
|
errorCode: "RATE_LIMIT_EXCEEDED"
|
499
515
|
};
|
500
516
|
}
|
501
|
-
} else {
|
502
|
-
await cacheOptions.put(key, current.toString());
|
503
517
|
}
|
504
518
|
return {
|
519
|
+
requestCount,
|
505
520
|
rateLimited: false
|
506
521
|
};
|
507
522
|
}
|
@@ -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-6a597a58.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-30f0f58c.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-cd8f49a9.esm.js';
|
3
|
+
export { r as rateLimit, u as usageLimit } from '../../dist/index-cd8f49a9.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