@thirdweb-dev/service-utils 0.5.0-nightly-0f027069-20230828164852 → 0.5.0-nightly-6cf298a29-20240308012322
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 +59 -27
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +59 -27
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +58 -27
- package/dist/declarations/src/cf-worker/index.d.ts +6 -3
- package/dist/declarations/src/cf-worker/index.d.ts.map +1 -1
- package/dist/declarations/src/cf-worker/usage.d.ts +75 -12
- package/dist/declarations/src/cf-worker/usage.d.ts.map +1 -1
- package/dist/declarations/src/core/api.d.ts +37 -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 +20 -0
- package/dist/declarations/src/core/rateLimit/index.d.ts.map +1 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts +13 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts.map +1 -0
- package/dist/declarations/src/core/services.d.ts +49 -1
- package/dist/declarations/src/core/services.d.ts.map +1 -1
- 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 +23 -3
- package/dist/declarations/src/node/index.d.ts.map +1 -1
- package/dist/{index-ffddf746.esm.js → index-3b9a0743.esm.js} +170 -20
- package/dist/{index-6e0ecc5f.cjs.prod.js → index-62b88cac.cjs.dev.js} +171 -19
- package/dist/{index-cd4f96ef.cjs.dev.js → index-aa324361.cjs.prod.js} +171 -19
- package/dist/{services-86283509.esm.js → services-2aecbda8.esm.js} +21 -0
- package/dist/{services-9e185105.cjs.prod.js → services-508322f3.cjs.dev.js} +21 -0
- package/dist/{services-a3f36057.cjs.dev.js → services-5c4d6977.cjs.prod.js} +21 -0
- package/dist/thirdweb-dev-service-utils.cjs.dev.js +1 -1
- package/dist/thirdweb-dev-service-utils.cjs.prod.js +1 -1
- package/dist/thirdweb-dev-service-utils.esm.js +1 -1
- package/node/dist/thirdweb-dev-service-utils-node.cjs.dev.js +50 -24
- package/node/dist/thirdweb-dev-service-utils-node.cjs.prod.js +50 -24
- package/node/dist/thirdweb-dev-service-utils-node.esm.js +49 -24
- package/package.json +9 -8
@@ -0,0 +1,20 @@
|
|
1
|
+
import { CoreServiceConfig } from "../api";
|
2
|
+
import { AuthorizationResult } from "../authorize/types";
|
3
|
+
import { RateLimitResult } from "./types";
|
4
|
+
type IRedis = {
|
5
|
+
incr: (key: string) => Promise<number>;
|
6
|
+
expire: (key: string, ttlSeconds: number) => Promise<0 | 1>;
|
7
|
+
};
|
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>;
|
19
|
+
export {};
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +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;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,CAsE3B"}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
export type RateLimitResult = {
|
2
|
+
rateLimited: false;
|
3
|
+
requestCount: number;
|
4
|
+
rateLimit: number;
|
5
|
+
} | {
|
6
|
+
rateLimited: true;
|
7
|
+
requestCount: number;
|
8
|
+
rateLimit: number;
|
9
|
+
status: number;
|
10
|
+
errorMessage: string;
|
11
|
+
errorCode: string;
|
12
|
+
};
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
@@ -0,0 +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;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IACE,WAAW,EAAE,IAAI,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
|
@@ -31,8 +31,26 @@ export declare const SERVICE_DEFINITIONS: {
|
|
31
31
|
readonly description: "Enable gasless transactions";
|
32
32
|
readonly actions: readonly [];
|
33
33
|
};
|
34
|
+
readonly embeddedWallets: {
|
35
|
+
readonly name: "embeddedWallets";
|
36
|
+
readonly title: "Embedded Wallets";
|
37
|
+
readonly description: "E-mail and social login wallets for easy web3 onboarding";
|
38
|
+
readonly actions: readonly [];
|
39
|
+
};
|
40
|
+
readonly checkout: {
|
41
|
+
readonly name: "checkout";
|
42
|
+
readonly title: "Checkouts";
|
43
|
+
readonly description: "NFT Checkouts for easy web3 onboarding";
|
44
|
+
readonly actions: readonly [];
|
45
|
+
};
|
46
|
+
readonly pay: {
|
47
|
+
readonly name: "pay";
|
48
|
+
readonly title: "Pay";
|
49
|
+
readonly description: "Pay for a blockchain transaction with any currency";
|
50
|
+
readonly actions: readonly [];
|
51
|
+
};
|
34
52
|
};
|
35
|
-
export declare const SERVICE_NAMES: ("storage" | "rpc" | "bundler" | "relayer")[];
|
53
|
+
export declare const SERVICE_NAMES: ("storage" | "rpc" | "bundler" | "relayer" | "embeddedWallets" | "checkout" | "pay")[];
|
36
54
|
export declare const SERVICES: ({
|
37
55
|
readonly name: "storage";
|
38
56
|
readonly title: "Storage";
|
@@ -61,6 +79,21 @@ export declare const SERVICES: ({
|
|
61
79
|
readonly title: "Gasless Relayer";
|
62
80
|
readonly description: "Enable gasless transactions";
|
63
81
|
readonly actions: readonly [];
|
82
|
+
} | {
|
83
|
+
readonly name: "embeddedWallets";
|
84
|
+
readonly title: "Embedded Wallets";
|
85
|
+
readonly description: "E-mail and social login wallets for easy web3 onboarding";
|
86
|
+
readonly actions: readonly [];
|
87
|
+
} | {
|
88
|
+
readonly name: "checkout";
|
89
|
+
readonly title: "Checkouts";
|
90
|
+
readonly description: "NFT Checkouts for easy web3 onboarding";
|
91
|
+
readonly actions: readonly [];
|
92
|
+
} | {
|
93
|
+
readonly name: "pay";
|
94
|
+
readonly title: "Pay";
|
95
|
+
readonly description: "Pay for a blockchain transaction with any currency";
|
96
|
+
readonly actions: readonly [];
|
64
97
|
})[];
|
65
98
|
export type ServiceName = (typeof SERVICE_NAMES)[number];
|
66
99
|
export type ServiceAction = {
|
@@ -97,5 +130,20 @@ export declare function getServiceByName(name: ServiceName): {
|
|
97
130
|
readonly title: "Gasless Relayer";
|
98
131
|
readonly description: "Enable gasless transactions";
|
99
132
|
readonly actions: readonly [];
|
133
|
+
} | {
|
134
|
+
readonly name: "embeddedWallets";
|
135
|
+
readonly title: "Embedded Wallets";
|
136
|
+
readonly description: "E-mail and social login wallets for easy web3 onboarding";
|
137
|
+
readonly actions: readonly [];
|
138
|
+
} | {
|
139
|
+
readonly name: "checkout";
|
140
|
+
readonly title: "Checkouts";
|
141
|
+
readonly description: "NFT Checkouts for easy web3 onboarding";
|
142
|
+
readonly actions: readonly [];
|
143
|
+
} | {
|
144
|
+
readonly name: "pay";
|
145
|
+
readonly title: "Pay";
|
146
|
+
readonly description: "Pay for a blockchain transaction with any currency";
|
147
|
+
readonly actions: readonly [];
|
100
148
|
};
|
101
149
|
//# sourceMappingURL=services.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"services.d.ts","sourceRoot":"../../../../src/core","sources":["services.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"../../../../src/core","sources":["services.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4DtB,CAAC;AAEX,eAAO,MAAM,aAAa,wFAEe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAqC,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GACjB,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEjE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEjD"}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
import { CoreServiceConfig } from "../api";
|
2
|
+
import { AuthorizationResult } from "../authorize/types";
|
3
|
+
import { UsageLimitResult } from "./types";
|
4
|
+
export declare function usageLimit(authzResult: AuthorizationResult, serviceConfig: CoreServiceConfig): Promise<UsageLimitResult>;
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../../src/core/usageLimit","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAsB,UAAU,CAC9B,WAAW,EAAE,mBAAmB,EAChC,aAAa,EAAE,iBAAiB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAmD3B"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"../../../../../src/core/usageLimit","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB;IACE,YAAY,EAAE,KAAK,CAAC;CACrB,GACD;IACE,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
|
@@ -1,23 +1,43 @@
|
|
1
1
|
/// <reference types="node" />
|
2
|
+
import type { ServerResponse } from "http";
|
2
3
|
import type { IncomingMessage } from "node:http";
|
3
|
-
import type { AuthorizationInput } from "../core/authorize";
|
4
4
|
import type { CoreServiceConfig } from "../core/api";
|
5
|
+
import type { AuthorizationInput } from "../core/authorize";
|
5
6
|
import type { AuthorizationResult } from "../core/authorize/types";
|
6
7
|
import type { CoreAuthInput } from "../core/types";
|
7
|
-
import type { ServerResponse } from "http";
|
8
8
|
export * from "../core/services";
|
9
|
+
export * from "../core/rateLimit";
|
10
|
+
export * from "../core/usageLimit";
|
9
11
|
type NodeServiceConfig = CoreServiceConfig;
|
10
12
|
export type AuthInput = CoreAuthInput & {
|
11
13
|
req: IncomingMessage;
|
12
14
|
};
|
15
|
+
/**
|
16
|
+
*
|
17
|
+
* @param {AuthInput['req']} authInput.req - The incoming request from which information will be pulled from. These information includes (checks are in order and terminates on first match):
|
18
|
+
* - clientId: Checks header `x-client-id`, search param `clientId`
|
19
|
+
* - bundleId: Checks header `x-bundle-id`, search param `bundleId`
|
20
|
+
* - secretKey: Checks header `x-secret-key`
|
21
|
+
* - origin (the requesting domain): Checks header `origin`, `referer`
|
22
|
+
* @param {AuthInput['clientId']} authInput.clientId - Overrides any clientId found on the `req` object
|
23
|
+
* @param {AuthInput['targetAddress']} authInput.targetAddress - Only used in smart wallets to determine if the request is authorized to interact with the target address.
|
24
|
+
* @param {NodeServiceConfig['enforceAuth']} serviceConfig - Always `true` unless you need to turn auth off. Tells the service whether or not to enforce auth.
|
25
|
+
* @param {NodeServiceConfig['apiUrl']} serviceConfig.apiUrl - The url of the api server to fetch information for verification. `https://api.thirdweb.com` for production and `https://api.staging.thirdweb.com` for staging
|
26
|
+
* @param {NodeServiceConfig['serviceApiKey']} serviceConfig.serviceApiKey - secret key to be used authenticate the caller of the api-server. Check the api-server's env variable for the keys.
|
27
|
+
* @param {NodeServiceConfig['serviceScope']} serviceConfig.serviceScope - The service that we are requesting authorization for. E.g. `relayer`, `rpc`, 'bundler', 'storage' etc.
|
28
|
+
* @param {NodeServiceConfig['serviceAction']} serviceConfig.serviceAction - Needed when the `serviceScope` is `storage`. Can be either `read` or `write`.
|
29
|
+
* @param {NodeServiceConfig['useWalletAuth']} serviceConfig.useWalletAuth - If true it pings the `wallet/me` or else, `account/me`. You most likely can leave this as false.
|
30
|
+
* @returns {AuthorizationResult} authorizationResult - contains if the request is authorized, and information about the account if it is authorized. Otherwise, it contains the error message and status code.
|
31
|
+
*/
|
13
32
|
export declare function authorizeNode(authInput: AuthInput, serviceConfig: NodeServiceConfig): Promise<AuthorizationResult>;
|
14
33
|
export declare function extractAuthorizationData(authInput: AuthInput): AuthorizationInput;
|
15
34
|
export declare function hashSecretKey(secretKey: string): string;
|
16
35
|
export declare function deriveClientIdFromSecretKeyHash(secretKeyHash: string): string;
|
17
|
-
export declare function logHttpRequest({ source, clientId, req, res, isAuthed, statusMessage, }: AuthInput & {
|
36
|
+
export declare function logHttpRequest({ source, clientId, req, res, isAuthed, statusMessage, latencyMs, }: AuthInput & {
|
18
37
|
source: string;
|
19
38
|
res: ServerResponse;
|
20
39
|
isAuthed?: boolean;
|
21
40
|
statusMessage?: Error | string;
|
41
|
+
latencyMs?: number;
|
22
42
|
}): void;
|
23
43
|
//# sourceMappingURL=index.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/node","sources":["index.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/node","sources":["index.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,KAAK,EAAuB,eAAe,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,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,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,KAAK,iBAAiB,GAAG,iBAAiB,CAAC;AAE3C,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG;IACtC,GAAG,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,iBAAiB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAsB9B;AAaD,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GACnB,kBAAkB,CA2FpB;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,UAE9C;AAED,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,UAEpE;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,EACR,GAAG,EACH,GAAG,EACH,QAAQ,EACR,aAAa,EACb,SAAS,GACV,EAAE,SAAS,GAAG;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,cAAc,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,QA4BA"}
|
@@ -2,9 +2,12 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
2
2
|
const {
|
3
3
|
apiUrl,
|
4
4
|
serviceScope,
|
5
|
-
serviceApiKey
|
5
|
+
serviceApiKey,
|
6
|
+
checkPolicy,
|
7
|
+
policyMetadata
|
6
8
|
} = config;
|
7
|
-
const
|
9
|
+
const policyQuery = checkPolicy && policyMetadata ? `&checkPolicy=true&policyMetadata=${encodeURIComponent(JSON.stringify(policyMetadata))}` : "";
|
10
|
+
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true${policyQuery}`;
|
8
11
|
const response = await fetch(url, {
|
9
12
|
method: "GET",
|
10
13
|
headers: {
|
@@ -12,20 +15,20 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
12
15
|
"content-type": "application/json"
|
13
16
|
}
|
14
17
|
});
|
15
|
-
let
|
18
|
+
let text = "";
|
16
19
|
try {
|
17
|
-
|
20
|
+
text = await response.text();
|
21
|
+
return JSON.parse(text);
|
18
22
|
} catch (e) {
|
19
|
-
throw new Error(`Error fetching key metadata from API: ${response.status} - ${
|
23
|
+
throw new Error(`Error fetching key metadata from API: ${response.status} - ${text}`);
|
20
24
|
}
|
21
|
-
return json;
|
22
25
|
}
|
23
26
|
async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
24
27
|
const {
|
25
28
|
apiUrl,
|
26
29
|
serviceApiKey
|
27
30
|
} = config;
|
28
|
-
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
|
31
|
+
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
|
29
32
|
const response = await fetch(url, {
|
30
33
|
method: "GET",
|
31
34
|
headers: {
|
@@ -34,13 +37,32 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
34
37
|
authorization: `Bearer ${jwt}`
|
35
38
|
}
|
36
39
|
});
|
37
|
-
let
|
40
|
+
let text = "";
|
38
41
|
try {
|
39
|
-
|
42
|
+
text = await response.text();
|
43
|
+
return JSON.parse(text);
|
40
44
|
} catch (e) {
|
41
|
-
throw new Error(`Error fetching account from API: ${response.status} - ${
|
45
|
+
throw new Error(`Error fetching account from API: ${response.status} - ${text}`);
|
42
46
|
}
|
43
|
-
|
47
|
+
}
|
48
|
+
async function updateRateLimitedAt(apiKeyId, config) {
|
49
|
+
const {
|
50
|
+
apiUrl,
|
51
|
+
serviceScope: scope,
|
52
|
+
serviceApiKey
|
53
|
+
} = config;
|
54
|
+
const url = `${apiUrl}/usage/rateLimit`;
|
55
|
+
await fetch(url, {
|
56
|
+
method: "PUT",
|
57
|
+
headers: {
|
58
|
+
"x-service-api-key": serviceApiKey,
|
59
|
+
"content-type": "application/json"
|
60
|
+
},
|
61
|
+
body: JSON.stringify({
|
62
|
+
apiKeyId,
|
63
|
+
scope
|
64
|
+
})
|
65
|
+
});
|
44
66
|
}
|
45
67
|
|
46
68
|
function authorizeClient(authOptions, apiKeyMeta) {
|
@@ -61,7 +83,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
|
|
61
83
|
id: apiKeyMeta.accountId,
|
62
84
|
// TODO update this later
|
63
85
|
name: "",
|
64
|
-
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
86
|
+
creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
|
87
|
+
limits: apiKeyMeta.limits,
|
88
|
+
rateLimits: apiKeyMeta.rateLimits,
|
89
|
+
usage: apiKeyMeta.usage
|
65
90
|
}
|
66
91
|
};
|
67
92
|
|
@@ -191,12 +216,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
|
|
191
216
|
}
|
192
217
|
return {
|
193
218
|
authorized: true,
|
219
|
+
apiKeyMeta: apiKeyMetadata,
|
194
220
|
accountMeta: {
|
195
221
|
id: apiKeyMetadata.accountId,
|
196
222
|
name: "",
|
197
|
-
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
|
198
|
-
|
199
|
-
|
223
|
+
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
|
224
|
+
limits: apiKeyMetadata.limits,
|
225
|
+
rateLimits: apiKeyMetadata.rateLimits,
|
226
|
+
usage: apiKeyMetadata.usage
|
227
|
+
}
|
200
228
|
};
|
201
229
|
}
|
202
230
|
|
@@ -234,9 +262,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
234
262
|
// if the difference is greater than the cacheTtl we want to ignore the cached data
|
235
263
|
const now = Date.now();
|
236
264
|
const diff = now - parsed.updatedAt;
|
237
|
-
const
|
265
|
+
const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
|
238
266
|
// only if the diff is less than the cacheTtl do we want to use the cached key
|
239
|
-
if (diff <
|
267
|
+
if (diff < cacheTtlMs) {
|
240
268
|
accountMeta = parsed.apiKeyMeta;
|
241
269
|
}
|
242
270
|
} else {
|
@@ -320,9 +348,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
320
348
|
// if the difference is greater than the cacheTtl we want to ignore the cached data
|
321
349
|
const now = Date.now();
|
322
350
|
const diff = now - parsed.updatedAt;
|
323
|
-
const
|
351
|
+
const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
|
324
352
|
// only if the diff is less than the cacheTtl do we want to use the cached key
|
325
|
-
if (diff <
|
353
|
+
if (diff < cacheTtlMs) {
|
326
354
|
apiKeyMeta = parsed.apiKeyMeta;
|
327
355
|
}
|
328
356
|
} else {
|
@@ -414,9 +442,131 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
414
442
|
id: apiKeyMeta.accountId,
|
415
443
|
// TODO update this later
|
416
444
|
name: "",
|
445
|
+
limits: apiKeyMeta.limits,
|
446
|
+
rateLimits: apiKeyMeta.rateLimits,
|
447
|
+
usage: apiKeyMeta.usage,
|
417
448
|
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
418
449
|
}
|
419
450
|
};
|
420
451
|
}
|
421
452
|
|
422
|
-
|
453
|
+
const RATE_LIMIT_WINDOW_SECONDS = 10;
|
454
|
+
|
455
|
+
// Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
|
456
|
+
|
457
|
+
async function rateLimit(args) {
|
458
|
+
const {
|
459
|
+
authzResult,
|
460
|
+
serviceConfig,
|
461
|
+
redis,
|
462
|
+
sampleRate = 1.0
|
463
|
+
} = args;
|
464
|
+
const shouldSampleRequest = Math.random() < sampleRate;
|
465
|
+
if (!shouldSampleRequest || !authzResult.authorized) {
|
466
|
+
return {
|
467
|
+
rateLimited: false,
|
468
|
+
requestCount: 0,
|
469
|
+
rateLimit: 0
|
470
|
+
};
|
471
|
+
}
|
472
|
+
const {
|
473
|
+
apiKeyMeta,
|
474
|
+
accountMeta
|
475
|
+
} = authzResult;
|
476
|
+
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
477
|
+
const {
|
478
|
+
serviceScope
|
479
|
+
} = serviceConfig;
|
480
|
+
const limitPerSecond = apiKeyMeta?.rateLimits?.[serviceScope] ?? accountMeta?.rateLimits?.[serviceScope];
|
481
|
+
if (!limitPerSecond) {
|
482
|
+
// No rate limit is provided. Assume the request is not rate limited.
|
483
|
+
return {
|
484
|
+
rateLimited: false,
|
485
|
+
requestCount: 0,
|
486
|
+
rateLimit: 0
|
487
|
+
};
|
488
|
+
}
|
489
|
+
|
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
|
+
}
|
500
|
+
|
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.
|
505
|
+
if (apiKeyMeta?.id) {
|
506
|
+
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
507
|
+
}
|
508
|
+
|
509
|
+
// Reject requests when they've exceeded 2x the rate limit.
|
510
|
+
if (requestCount > 2 * limitPerWindow) {
|
511
|
+
return {
|
512
|
+
rateLimited: true,
|
513
|
+
requestCount,
|
514
|
+
rateLimit: limitPerWindow,
|
515
|
+
status: 429,
|
516
|
+
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.`,
|
517
|
+
errorCode: "RATE_LIMIT_EXCEEDED"
|
518
|
+
};
|
519
|
+
}
|
520
|
+
}
|
521
|
+
return {
|
522
|
+
rateLimited: false,
|
523
|
+
requestCount,
|
524
|
+
rateLimit: limitPerWindow
|
525
|
+
};
|
526
|
+
}
|
527
|
+
|
528
|
+
async function usageLimit(authzResult, serviceConfig) {
|
529
|
+
if (!authzResult.authorized) {
|
530
|
+
return {
|
531
|
+
usageLimited: false
|
532
|
+
};
|
533
|
+
}
|
534
|
+
const {
|
535
|
+
apiKeyMeta,
|
536
|
+
accountMeta
|
537
|
+
} = authzResult;
|
538
|
+
const {
|
539
|
+
limits,
|
540
|
+
usage
|
541
|
+
} = apiKeyMeta || accountMeta || {};
|
542
|
+
const {
|
543
|
+
serviceScope
|
544
|
+
} = serviceConfig;
|
545
|
+
if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
|
546
|
+
// No usage limit is provided. Assume the request is not limited.
|
547
|
+
return {
|
548
|
+
usageLimited: false
|
549
|
+
};
|
550
|
+
}
|
551
|
+
if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes ?? 0) > (limits.storage ?? 0)) {
|
552
|
+
return {
|
553
|
+
usageLimited: true,
|
554
|
+
status: 403,
|
555
|
+
errorMessage: `You've used all of your total usage credits for Storage Pinning. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
|
556
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
557
|
+
};
|
558
|
+
}
|
559
|
+
if (serviceScope === "embeddedWallets" && (usage.embeddedWallets?.countWalletAddresses ?? 0) > (limits.embeddedWallets ?? 0)) {
|
560
|
+
return {
|
561
|
+
usageLimited: true,
|
562
|
+
status: 403,
|
563
|
+
errorMessage: `You've used all of your total usage credits for Embedded Wallets. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
|
564
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
565
|
+
};
|
566
|
+
}
|
567
|
+
return {
|
568
|
+
usageLimited: false
|
569
|
+
};
|
570
|
+
}
|
571
|
+
|
572
|
+
export { authorize as a, rateLimit as r, usageLimit as u };
|