@thirdweb-dev/service-utils 0.4.4 → 0.4.5-nightly-c54db48c-20230919012424
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 +42 -24
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +42 -24
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +41 -25
- package/dist/declarations/src/cf-worker/index.d.ts +6 -2
- package/dist/declarations/src/cf-worker/index.d.ts.map +1 -1
- package/dist/declarations/src/cf-worker/usage.d.ts +14 -11
- package/dist/declarations/src/cf-worker/usage.d.ts.map +1 -1
- package/dist/declarations/src/core/api.d.ts +17 -1
- package/dist/declarations/src/core/api.d.ts.map +1 -1
- package/dist/declarations/src/core/authorize/client.d.ts.map +1 -1
- package/dist/declarations/src/core/authorize/index.d.ts.map +1 -1
- package/dist/declarations/src/core/authorize/service.d.ts.map +1 -1
- package/dist/declarations/src/core/rateLimit/index.d.ts +10 -0
- package/dist/declarations/src/core/rateLimit/index.d.ts.map +1 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts +9 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts.map +1 -0
- package/dist/declarations/src/core/usageLimit/index.d.ts +5 -0
- package/dist/declarations/src/core/usageLimit/index.d.ts.map +1 -0
- package/dist/declarations/src/core/usageLimit/types.d.ts +9 -0
- package/dist/declarations/src/core/usageLimit/types.d.ts.map +1 -0
- package/dist/declarations/src/node/index.d.ts +2 -0
- package/dist/declarations/src/node/index.d.ts.map +1 -1
- package/dist/{index-6e0ecc5f.cjs.prod.js → index-807f6a60.cjs.dev.js} +132 -6
- package/dist/{index-ffddf746.esm.js → index-bcf68113.esm.js} +131 -7
- package/dist/{index-cd4f96ef.cjs.dev.js → index-cfc8027b.cjs.prod.js} +132 -6
- package/node/dist/thirdweb-dev-service-utils-node.cjs.dev.js +28 -22
- package/node/dist/thirdweb-dev-service-utils-node.cjs.prod.js +28 -22
- package/node/dist/thirdweb-dev-service-utils-node.esm.js +27 -22
- package/package.json +2 -1
@@ -6,7 +6,7 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
6
6
|
serviceScope,
|
7
7
|
serviceApiKey
|
8
8
|
} = config;
|
9
|
-
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}`;
|
9
|
+
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true`;
|
10
10
|
const response = await fetch(url, {
|
11
11
|
method: "GET",
|
12
12
|
headers: {
|
@@ -27,7 +27,7 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
27
27
|
apiUrl,
|
28
28
|
serviceApiKey
|
29
29
|
} = config;
|
30
|
-
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
|
30
|
+
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
|
31
31
|
const response = await fetch(url, {
|
32
32
|
method: "GET",
|
33
33
|
headers: {
|
@@ -44,6 +44,25 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
44
44
|
}
|
45
45
|
return json;
|
46
46
|
}
|
47
|
+
async function updateRateLimitedAt(apiKeyId, config) {
|
48
|
+
const {
|
49
|
+
apiUrl,
|
50
|
+
serviceScope: scope,
|
51
|
+
serviceApiKey
|
52
|
+
} = config;
|
53
|
+
const url = `${apiUrl}/usage/rateLimit`;
|
54
|
+
await fetch(url, {
|
55
|
+
method: "PUT",
|
56
|
+
headers: {
|
57
|
+
"x-service-api-key": serviceApiKey,
|
58
|
+
"content-type": "application/json"
|
59
|
+
},
|
60
|
+
body: JSON.stringify({
|
61
|
+
apiKeyId,
|
62
|
+
scope
|
63
|
+
})
|
64
|
+
});
|
65
|
+
}
|
47
66
|
|
48
67
|
function authorizeClient(authOptions, apiKeyMeta) {
|
49
68
|
const {
|
@@ -63,7 +82,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
|
|
63
82
|
id: apiKeyMeta.accountId,
|
64
83
|
// TODO update this later
|
65
84
|
name: "",
|
66
|
-
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
85
|
+
creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
|
86
|
+
limits: apiKeyMeta.limits,
|
87
|
+
rateLimits: apiKeyMeta.rateLimits,
|
88
|
+
usage: apiKeyMeta.usage
|
67
89
|
}
|
68
90
|
};
|
69
91
|
|
@@ -193,12 +215,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
|
|
193
215
|
}
|
194
216
|
return {
|
195
217
|
authorized: true,
|
218
|
+
apiKeyMeta: apiKeyMetadata,
|
196
219
|
accountMeta: {
|
197
220
|
id: apiKeyMetadata.accountId,
|
198
221
|
name: "",
|
199
|
-
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
|
200
|
-
|
201
|
-
|
222
|
+
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
|
223
|
+
limits: apiKeyMetadata.limits,
|
224
|
+
rateLimits: apiKeyMetadata.rateLimits,
|
225
|
+
usage: apiKeyMetadata.usage
|
226
|
+
}
|
202
227
|
};
|
203
228
|
}
|
204
229
|
|
@@ -416,9 +441,110 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
416
441
|
id: apiKeyMeta.accountId,
|
417
442
|
// TODO update this later
|
418
443
|
name: "",
|
444
|
+
limits: apiKeyMeta.limits,
|
445
|
+
rateLimits: apiKeyMeta.rateLimits,
|
446
|
+
usage: apiKeyMeta.usage,
|
419
447
|
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
420
448
|
}
|
421
449
|
};
|
422
450
|
}
|
423
451
|
|
452
|
+
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS = 10;
|
453
|
+
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
454
|
+
|
455
|
+
async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
456
|
+
if (!authzResult.authorized) {
|
457
|
+
return {
|
458
|
+
rateLimited: false
|
459
|
+
};
|
460
|
+
}
|
461
|
+
const {
|
462
|
+
apiKeyMeta,
|
463
|
+
accountMeta
|
464
|
+
} = authzResult;
|
465
|
+
const {
|
466
|
+
rateLimits
|
467
|
+
} = apiKeyMeta || accountMeta || {};
|
468
|
+
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
469
|
+
const {
|
470
|
+
serviceScope
|
471
|
+
} = serviceConfig;
|
472
|
+
if (!rateLimits || !(serviceScope in rateLimits)) {
|
473
|
+
// No rate limit is provided. Assume the request is not rate limited.
|
474
|
+
return {
|
475
|
+
rateLimited: false
|
476
|
+
};
|
477
|
+
}
|
478
|
+
const limit = rateLimits[serviceScope];
|
479
|
+
|
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;
|
485
|
+
|
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
|
490
|
+
if (apiKeyMeta?.id) {
|
491
|
+
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
492
|
+
}
|
493
|
+
|
494
|
+
// actually rate limit only when reached hard limit
|
495
|
+
if (current > limitWindow * HARD_LIMIT_MULTIPLE) {
|
496
|
+
return {
|
497
|
+
rateLimited: true,
|
498
|
+
status: 429,
|
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.`,
|
500
|
+
errorCode: "RATE_LIMIT_EXCEEDED"
|
501
|
+
};
|
502
|
+
}
|
503
|
+
} else {
|
504
|
+
await cacheOptions.put(key, current.toString());
|
505
|
+
}
|
506
|
+
return {
|
507
|
+
rateLimited: false
|
508
|
+
};
|
509
|
+
}
|
510
|
+
|
511
|
+
async function usageLimit(authzResult, serviceConfig) {
|
512
|
+
if (!authzResult.authorized) {
|
513
|
+
return {
|
514
|
+
usageLimited: false
|
515
|
+
};
|
516
|
+
}
|
517
|
+
const {
|
518
|
+
apiKeyMeta,
|
519
|
+
accountMeta
|
520
|
+
} = authzResult;
|
521
|
+
const {
|
522
|
+
limits,
|
523
|
+
usage
|
524
|
+
} = apiKeyMeta || accountMeta || {};
|
525
|
+
const {
|
526
|
+
serviceScope
|
527
|
+
} = serviceConfig;
|
528
|
+
if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
|
529
|
+
// No usage limit is provided. Assume the request is not limited.
|
530
|
+
return {
|
531
|
+
usageLimited: false
|
532
|
+
};
|
533
|
+
}
|
534
|
+
const limit = limits.storage;
|
535
|
+
if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes || 0) > limit) {
|
536
|
+
return {
|
537
|
+
usageLimited: true,
|
538
|
+
status: 403,
|
539
|
+
errorMessage: `You've used all of your total usage limit for Storage Pinning. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
|
540
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
541
|
+
};
|
542
|
+
}
|
543
|
+
return {
|
544
|
+
usageLimited: false
|
545
|
+
};
|
546
|
+
}
|
547
|
+
|
424
548
|
exports.authorize = authorize;
|
549
|
+
exports.rateLimit = rateLimit;
|
550
|
+
exports.usageLimit = usageLimit;
|
@@ -4,7 +4,7 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
4
4
|
serviceScope,
|
5
5
|
serviceApiKey
|
6
6
|
} = config;
|
7
|
-
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}`;
|
7
|
+
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true`;
|
8
8
|
const response = await fetch(url, {
|
9
9
|
method: "GET",
|
10
10
|
headers: {
|
@@ -25,7 +25,7 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
25
25
|
apiUrl,
|
26
26
|
serviceApiKey
|
27
27
|
} = config;
|
28
|
-
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
|
28
|
+
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
|
29
29
|
const response = await fetch(url, {
|
30
30
|
method: "GET",
|
31
31
|
headers: {
|
@@ -42,6 +42,25 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
42
42
|
}
|
43
43
|
return json;
|
44
44
|
}
|
45
|
+
async function updateRateLimitedAt(apiKeyId, config) {
|
46
|
+
const {
|
47
|
+
apiUrl,
|
48
|
+
serviceScope: scope,
|
49
|
+
serviceApiKey
|
50
|
+
} = config;
|
51
|
+
const url = `${apiUrl}/usage/rateLimit`;
|
52
|
+
await fetch(url, {
|
53
|
+
method: "PUT",
|
54
|
+
headers: {
|
55
|
+
"x-service-api-key": serviceApiKey,
|
56
|
+
"content-type": "application/json"
|
57
|
+
},
|
58
|
+
body: JSON.stringify({
|
59
|
+
apiKeyId,
|
60
|
+
scope
|
61
|
+
})
|
62
|
+
});
|
63
|
+
}
|
45
64
|
|
46
65
|
function authorizeClient(authOptions, apiKeyMeta) {
|
47
66
|
const {
|
@@ -61,7 +80,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
|
|
61
80
|
id: apiKeyMeta.accountId,
|
62
81
|
// TODO update this later
|
63
82
|
name: "",
|
64
|
-
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
83
|
+
creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
|
84
|
+
limits: apiKeyMeta.limits,
|
85
|
+
rateLimits: apiKeyMeta.rateLimits,
|
86
|
+
usage: apiKeyMeta.usage
|
65
87
|
}
|
66
88
|
};
|
67
89
|
|
@@ -191,12 +213,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
|
|
191
213
|
}
|
192
214
|
return {
|
193
215
|
authorized: true,
|
216
|
+
apiKeyMeta: apiKeyMetadata,
|
194
217
|
accountMeta: {
|
195
218
|
id: apiKeyMetadata.accountId,
|
196
219
|
name: "",
|
197
|
-
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
|
198
|
-
|
199
|
-
|
220
|
+
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
|
221
|
+
limits: apiKeyMetadata.limits,
|
222
|
+
rateLimits: apiKeyMetadata.rateLimits,
|
223
|
+
usage: apiKeyMetadata.usage
|
224
|
+
}
|
200
225
|
};
|
201
226
|
}
|
202
227
|
|
@@ -414,9 +439,108 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
414
439
|
id: apiKeyMeta.accountId,
|
415
440
|
// TODO update this later
|
416
441
|
name: "",
|
442
|
+
limits: apiKeyMeta.limits,
|
443
|
+
rateLimits: apiKeyMeta.rateLimits,
|
444
|
+
usage: apiKeyMeta.usage,
|
417
445
|
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
418
446
|
}
|
419
447
|
};
|
420
448
|
}
|
421
449
|
|
422
|
-
|
450
|
+
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS = 10;
|
451
|
+
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
452
|
+
|
453
|
+
async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
454
|
+
if (!authzResult.authorized) {
|
455
|
+
return {
|
456
|
+
rateLimited: false
|
457
|
+
};
|
458
|
+
}
|
459
|
+
const {
|
460
|
+
apiKeyMeta,
|
461
|
+
accountMeta
|
462
|
+
} = authzResult;
|
463
|
+
const {
|
464
|
+
rateLimits
|
465
|
+
} = apiKeyMeta || accountMeta || {};
|
466
|
+
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
467
|
+
const {
|
468
|
+
serviceScope
|
469
|
+
} = serviceConfig;
|
470
|
+
if (!rateLimits || !(serviceScope in rateLimits)) {
|
471
|
+
// No rate limit is provided. Assume the request is not rate limited.
|
472
|
+
return {
|
473
|
+
rateLimited: false
|
474
|
+
};
|
475
|
+
}
|
476
|
+
const limit = rateLimits[serviceScope];
|
477
|
+
|
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;
|
483
|
+
|
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
|
488
|
+
if (apiKeyMeta?.id) {
|
489
|
+
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
490
|
+
}
|
491
|
+
|
492
|
+
// actually rate limit only when reached hard limit
|
493
|
+
if (current > limitWindow * HARD_LIMIT_MULTIPLE) {
|
494
|
+
return {
|
495
|
+
rateLimited: true,
|
496
|
+
status: 429,
|
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.`,
|
498
|
+
errorCode: "RATE_LIMIT_EXCEEDED"
|
499
|
+
};
|
500
|
+
}
|
501
|
+
} else {
|
502
|
+
await cacheOptions.put(key, current.toString());
|
503
|
+
}
|
504
|
+
return {
|
505
|
+
rateLimited: false
|
506
|
+
};
|
507
|
+
}
|
508
|
+
|
509
|
+
async function usageLimit(authzResult, serviceConfig) {
|
510
|
+
if (!authzResult.authorized) {
|
511
|
+
return {
|
512
|
+
usageLimited: false
|
513
|
+
};
|
514
|
+
}
|
515
|
+
const {
|
516
|
+
apiKeyMeta,
|
517
|
+
accountMeta
|
518
|
+
} = authzResult;
|
519
|
+
const {
|
520
|
+
limits,
|
521
|
+
usage
|
522
|
+
} = apiKeyMeta || accountMeta || {};
|
523
|
+
const {
|
524
|
+
serviceScope
|
525
|
+
} = serviceConfig;
|
526
|
+
if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
|
527
|
+
// No usage limit is provided. Assume the request is not limited.
|
528
|
+
return {
|
529
|
+
usageLimited: false
|
530
|
+
};
|
531
|
+
}
|
532
|
+
const limit = limits.storage;
|
533
|
+
if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes || 0) > limit) {
|
534
|
+
return {
|
535
|
+
usageLimited: true,
|
536
|
+
status: 403,
|
537
|
+
errorMessage: `You've used all of your total usage limit for Storage Pinning. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
|
538
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
539
|
+
};
|
540
|
+
}
|
541
|
+
return {
|
542
|
+
usageLimited: false
|
543
|
+
};
|
544
|
+
}
|
545
|
+
|
546
|
+
export { authorize as a, rateLimit as r, usageLimit as u };
|
@@ -6,7 +6,7 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
6
6
|
serviceScope,
|
7
7
|
serviceApiKey
|
8
8
|
} = config;
|
9
|
-
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}`;
|
9
|
+
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true`;
|
10
10
|
const response = await fetch(url, {
|
11
11
|
method: "GET",
|
12
12
|
headers: {
|
@@ -27,7 +27,7 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
27
27
|
apiUrl,
|
28
28
|
serviceApiKey
|
29
29
|
} = config;
|
30
|
-
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
|
30
|
+
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
|
31
31
|
const response = await fetch(url, {
|
32
32
|
method: "GET",
|
33
33
|
headers: {
|
@@ -44,6 +44,25 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
44
44
|
}
|
45
45
|
return json;
|
46
46
|
}
|
47
|
+
async function updateRateLimitedAt(apiKeyId, config) {
|
48
|
+
const {
|
49
|
+
apiUrl,
|
50
|
+
serviceScope: scope,
|
51
|
+
serviceApiKey
|
52
|
+
} = config;
|
53
|
+
const url = `${apiUrl}/usage/rateLimit`;
|
54
|
+
await fetch(url, {
|
55
|
+
method: "PUT",
|
56
|
+
headers: {
|
57
|
+
"x-service-api-key": serviceApiKey,
|
58
|
+
"content-type": "application/json"
|
59
|
+
},
|
60
|
+
body: JSON.stringify({
|
61
|
+
apiKeyId,
|
62
|
+
scope
|
63
|
+
})
|
64
|
+
});
|
65
|
+
}
|
47
66
|
|
48
67
|
function authorizeClient(authOptions, apiKeyMeta) {
|
49
68
|
const {
|
@@ -63,7 +82,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
|
|
63
82
|
id: apiKeyMeta.accountId,
|
64
83
|
// TODO update this later
|
65
84
|
name: "",
|
66
|
-
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
85
|
+
creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
|
86
|
+
limits: apiKeyMeta.limits,
|
87
|
+
rateLimits: apiKeyMeta.rateLimits,
|
88
|
+
usage: apiKeyMeta.usage
|
67
89
|
}
|
68
90
|
};
|
69
91
|
|
@@ -193,12 +215,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
|
|
193
215
|
}
|
194
216
|
return {
|
195
217
|
authorized: true,
|
218
|
+
apiKeyMeta: apiKeyMetadata,
|
196
219
|
accountMeta: {
|
197
220
|
id: apiKeyMetadata.accountId,
|
198
221
|
name: "",
|
199
|
-
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
|
200
|
-
|
201
|
-
|
222
|
+
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
|
223
|
+
limits: apiKeyMetadata.limits,
|
224
|
+
rateLimits: apiKeyMetadata.rateLimits,
|
225
|
+
usage: apiKeyMetadata.usage
|
226
|
+
}
|
202
227
|
};
|
203
228
|
}
|
204
229
|
|
@@ -416,9 +441,110 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
416
441
|
id: apiKeyMeta.accountId,
|
417
442
|
// TODO update this later
|
418
443
|
name: "",
|
444
|
+
limits: apiKeyMeta.limits,
|
445
|
+
rateLimits: apiKeyMeta.rateLimits,
|
446
|
+
usage: apiKeyMeta.usage,
|
419
447
|
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
420
448
|
}
|
421
449
|
};
|
422
450
|
}
|
423
451
|
|
452
|
+
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS = 10;
|
453
|
+
const HARD_LIMIT_MULTIPLE = 2; // 2x of allowed limit
|
454
|
+
|
455
|
+
async function rateLimit(authzResult, serviceConfig, cacheOptions) {
|
456
|
+
if (!authzResult.authorized) {
|
457
|
+
return {
|
458
|
+
rateLimited: false
|
459
|
+
};
|
460
|
+
}
|
461
|
+
const {
|
462
|
+
apiKeyMeta,
|
463
|
+
accountMeta
|
464
|
+
} = authzResult;
|
465
|
+
const {
|
466
|
+
rateLimits
|
467
|
+
} = apiKeyMeta || accountMeta || {};
|
468
|
+
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
469
|
+
const {
|
470
|
+
serviceScope
|
471
|
+
} = serviceConfig;
|
472
|
+
if (!rateLimits || !(serviceScope in rateLimits)) {
|
473
|
+
// No rate limit is provided. Assume the request is not rate limited.
|
474
|
+
return {
|
475
|
+
rateLimited: false
|
476
|
+
};
|
477
|
+
}
|
478
|
+
const limit = rateLimits[serviceScope];
|
479
|
+
|
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;
|
485
|
+
|
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
|
490
|
+
if (apiKeyMeta?.id) {
|
491
|
+
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
492
|
+
}
|
493
|
+
|
494
|
+
// actually rate limit only when reached hard limit
|
495
|
+
if (current > limitWindow * HARD_LIMIT_MULTIPLE) {
|
496
|
+
return {
|
497
|
+
rateLimited: true,
|
498
|
+
status: 429,
|
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.`,
|
500
|
+
errorCode: "RATE_LIMIT_EXCEEDED"
|
501
|
+
};
|
502
|
+
}
|
503
|
+
} else {
|
504
|
+
await cacheOptions.put(key, current.toString());
|
505
|
+
}
|
506
|
+
return {
|
507
|
+
rateLimited: false
|
508
|
+
};
|
509
|
+
}
|
510
|
+
|
511
|
+
async function usageLimit(authzResult, serviceConfig) {
|
512
|
+
if (!authzResult.authorized) {
|
513
|
+
return {
|
514
|
+
usageLimited: false
|
515
|
+
};
|
516
|
+
}
|
517
|
+
const {
|
518
|
+
apiKeyMeta,
|
519
|
+
accountMeta
|
520
|
+
} = authzResult;
|
521
|
+
const {
|
522
|
+
limits,
|
523
|
+
usage
|
524
|
+
} = apiKeyMeta || accountMeta || {};
|
525
|
+
const {
|
526
|
+
serviceScope
|
527
|
+
} = serviceConfig;
|
528
|
+
if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
|
529
|
+
// No usage limit is provided. Assume the request is not limited.
|
530
|
+
return {
|
531
|
+
usageLimited: false
|
532
|
+
};
|
533
|
+
}
|
534
|
+
const limit = limits.storage;
|
535
|
+
if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes || 0) > limit) {
|
536
|
+
return {
|
537
|
+
usageLimited: true,
|
538
|
+
status: 403,
|
539
|
+
errorMessage: `You've used all of your total usage limit for Storage Pinning. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
|
540
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
541
|
+
};
|
542
|
+
}
|
543
|
+
return {
|
544
|
+
usageLimited: false
|
545
|
+
};
|
546
|
+
}
|
547
|
+
|
424
548
|
exports.authorize = authorize;
|
549
|
+
exports.rateLimit = rateLimit;
|
550
|
+
exports.usageLimit = usageLimit;
|
@@ -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-a3f36057.cjs.dev.js');
|
8
8
|
|
9
9
|
/**
|
@@ -150,29 +150,35 @@ function logHttpRequest(_ref) {
|
|
150
150
|
isAuthed,
|
151
151
|
statusMessage
|
152
152
|
} = _ref;
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
153
|
+
try {
|
154
|
+
const authorizationData = extractAuthorizationData({
|
155
|
+
req,
|
156
|
+
clientId
|
157
|
+
});
|
158
|
+
const headers = req.headers;
|
159
|
+
const _statusMessage = statusMessage ?? res.statusMessage;
|
160
|
+
console.log(JSON.stringify({
|
161
|
+
source,
|
162
|
+
pathname: req.url,
|
163
|
+
hasSecretKey: !!authorizationData.secretKey,
|
164
|
+
hasClientId: !!authorizationData.clientId,
|
165
|
+
hasJwt: !!authorizationData.jwt,
|
166
|
+
clientId: authorizationData.clientId,
|
167
|
+
isAuthed: !!isAuthed ?? null,
|
168
|
+
status: res.statusCode,
|
169
|
+
statusMessage: _statusMessage,
|
170
|
+
sdkName: headers["x-sdk-name"] ?? "unknown",
|
171
|
+
sdkVersion: headers["x-sdk-version"] ?? "unknown",
|
172
|
+
platform: headers["x-sdk-platform"] ?? "unknown"
|
173
|
+
}));
|
174
|
+
console.log(`statusMessage=${_statusMessage}`);
|
175
|
+
} catch (err) {
|
176
|
+
console.error("Failed to log HTTP request:", err);
|
177
|
+
}
|
174
178
|
}
|
175
179
|
|
180
|
+
exports.rateLimit = index.rateLimit;
|
181
|
+
exports.usageLimit = index.usageLimit;
|
176
182
|
exports.SERVICES = services.SERVICES;
|
177
183
|
exports.SERVICE_DEFINITIONS = services.SERVICE_DEFINITIONS;
|
178
184
|
exports.SERVICE_NAMES = services.SERVICE_NAMES;
|
@@ -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-9e185105.cjs.prod.js');
|
8
8
|
|
9
9
|
/**
|
@@ -150,29 +150,35 @@ function logHttpRequest(_ref) {
|
|
150
150
|
isAuthed,
|
151
151
|
statusMessage
|
152
152
|
} = _ref;
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
153
|
+
try {
|
154
|
+
const authorizationData = extractAuthorizationData({
|
155
|
+
req,
|
156
|
+
clientId
|
157
|
+
});
|
158
|
+
const headers = req.headers;
|
159
|
+
const _statusMessage = statusMessage ?? res.statusMessage;
|
160
|
+
console.log(JSON.stringify({
|
161
|
+
source,
|
162
|
+
pathname: req.url,
|
163
|
+
hasSecretKey: !!authorizationData.secretKey,
|
164
|
+
hasClientId: !!authorizationData.clientId,
|
165
|
+
hasJwt: !!authorizationData.jwt,
|
166
|
+
clientId: authorizationData.clientId,
|
167
|
+
isAuthed: !!isAuthed ?? null,
|
168
|
+
status: res.statusCode,
|
169
|
+
statusMessage: _statusMessage,
|
170
|
+
sdkName: headers["x-sdk-name"] ?? "unknown",
|
171
|
+
sdkVersion: headers["x-sdk-version"] ?? "unknown",
|
172
|
+
platform: headers["x-sdk-platform"] ?? "unknown"
|
173
|
+
}));
|
174
|
+
console.log(`statusMessage=${_statusMessage}`);
|
175
|
+
} catch (err) {
|
176
|
+
console.error("Failed to log HTTP request:", err);
|
177
|
+
}
|
174
178
|
}
|
175
179
|
|
180
|
+
exports.rateLimit = index.rateLimit;
|
181
|
+
exports.usageLimit = index.usageLimit;
|
176
182
|
exports.SERVICES = services.SERVICES;
|
177
183
|
exports.SERVICE_DEFINITIONS = services.SERVICE_DEFINITIONS;
|
178
184
|
exports.SERVICE_NAMES = services.SERVICE_NAMES;
|