@thirdweb-dev/service-utils 0.5.0-nightly-6cf298a29-20240308012322 → 0.5.1

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.
Files changed (142) hide show
  1. package/dist/cjs/cf-worker/index.js +152 -0
  2. package/dist/cjs/cf-worker/index.js.map +1 -0
  3. package/dist/cjs/cf-worker/usage.js +55 -0
  4. package/dist/cjs/cf-worker/usage.js.map +1 -0
  5. package/dist/cjs/core/api.js +42 -0
  6. package/dist/cjs/core/api.js.map +1 -0
  7. package/dist/cjs/core/authorize/client.js +104 -0
  8. package/dist/cjs/core/authorize/client.js.map +1 -0
  9. package/dist/cjs/core/authorize/index.js +110 -0
  10. package/dist/cjs/core/authorize/index.js.map +1 -0
  11. package/dist/cjs/core/authorize/service.js +60 -0
  12. package/dist/cjs/core/authorize/service.js.map +1 -0
  13. package/dist/cjs/core/authorize/types.js +3 -0
  14. package/dist/cjs/core/authorize/types.js.map +1 -0
  15. package/dist/cjs/core/rateLimit/index.js +60 -0
  16. package/dist/cjs/core/rateLimit/index.js.map +1 -0
  17. package/dist/cjs/core/rateLimit/types.js +3 -0
  18. package/dist/cjs/core/rateLimit/types.js.map +1 -0
  19. package/dist/cjs/core/services.js +85 -0
  20. package/dist/cjs/core/services.js.map +1 -0
  21. package/dist/cjs/core/types.js +3 -0
  22. package/dist/cjs/core/types.js.map +1 -0
  23. package/dist/cjs/core/usage.js +85 -0
  24. package/dist/cjs/core/usage.js.map +1 -0
  25. package/dist/cjs/index.js +13 -0
  26. package/dist/cjs/index.js.map +1 -0
  27. package/dist/cjs/mocks.js +61 -0
  28. package/dist/cjs/mocks.js.map +1 -0
  29. package/dist/cjs/node/index.js +156 -0
  30. package/dist/cjs/node/index.js.map +1 -0
  31. package/dist/cjs/package.json +1 -0
  32. package/dist/esm/cf-worker/index.js +145 -0
  33. package/dist/esm/cf-worker/index.js.map +1 -0
  34. package/dist/esm/cf-worker/usage.js +53 -0
  35. package/dist/esm/cf-worker/usage.js.map +1 -0
  36. package/dist/esm/core/api.js +38 -0
  37. package/dist/esm/core/api.js.map +1 -0
  38. package/dist/esm/core/authorize/client.js +99 -0
  39. package/dist/esm/core/authorize/client.js.map +1 -0
  40. package/dist/esm/core/authorize/index.js +107 -0
  41. package/dist/esm/core/authorize/index.js.map +1 -0
  42. package/dist/esm/core/authorize/service.js +57 -0
  43. package/dist/esm/core/authorize/service.js.map +1 -0
  44. package/dist/esm/core/authorize/types.js +2 -0
  45. package/dist/esm/core/authorize/types.js.map +1 -0
  46. package/dist/esm/core/rateLimit/index.js +57 -0
  47. package/dist/esm/core/rateLimit/index.js.map +1 -0
  48. package/dist/esm/core/rateLimit/types.js +2 -0
  49. package/dist/esm/core/rateLimit/types.js.map +1 -0
  50. package/dist/esm/core/services.js +81 -0
  51. package/dist/esm/core/services.js.map +1 -0
  52. package/dist/esm/core/types.js +2 -0
  53. package/dist/esm/core/types.js.map +1 -0
  54. package/dist/esm/core/usage.js +82 -0
  55. package/dist/esm/core/usage.js.map +1 -0
  56. package/dist/esm/index.js +5 -0
  57. package/dist/esm/index.js.map +1 -0
  58. package/dist/esm/mocks.js +58 -0
  59. package/dist/esm/mocks.js.map +1 -0
  60. package/dist/esm/node/index.js +149 -0
  61. package/dist/esm/node/index.js.map +1 -0
  62. package/dist/esm/package.json +1 -0
  63. package/dist/{declarations/src → types}/cf-worker/index.d.ts +8 -10
  64. package/dist/types/cf-worker/index.d.ts.map +1 -0
  65. package/dist/types/cf-worker/usage.d.ts +24 -0
  66. package/dist/types/cf-worker/usage.d.ts.map +1 -0
  67. package/dist/types/core/api.d.ts +121 -0
  68. package/dist/types/core/api.d.ts.map +1 -0
  69. package/dist/types/core/authorize/client.d.ts +17 -0
  70. package/dist/types/core/authorize/client.d.ts.map +1 -0
  71. package/dist/{declarations/src → types}/core/authorize/index.d.ts +6 -5
  72. package/dist/types/core/authorize/index.d.ts.map +1 -0
  73. package/dist/types/core/authorize/service.d.ts +4 -0
  74. package/dist/types/core/authorize/service.d.ts.map +1 -0
  75. package/dist/types/core/authorize/types.d.ts +10 -0
  76. package/dist/types/core/authorize/types.d.ts.map +1 -0
  77. package/dist/{declarations/src → types}/core/rateLimit/index.d.ts +4 -4
  78. package/dist/types/core/rateLimit/index.d.ts.map +1 -0
  79. package/dist/types/core/rateLimit/types.d.ts.map +1 -0
  80. package/dist/{declarations/src → types}/core/services.d.ts +55 -23
  81. package/dist/types/core/services.d.ts.map +1 -0
  82. package/dist/types/core/types.d.ts.map +1 -0
  83. package/dist/{declarations/src/cf-worker → types/core}/usage.d.ts +84 -30
  84. package/dist/types/core/usage.d.ts.map +1 -0
  85. package/dist/types/index.d.ts +5 -0
  86. package/dist/types/index.d.ts.map +1 -0
  87. package/dist/types/mocks.d.ts +7 -0
  88. package/dist/types/mocks.d.ts.map +1 -0
  89. package/dist/types/node/index.d.ts +23 -0
  90. package/dist/types/node/index.d.ts.map +1 -0
  91. package/package.json +42 -45
  92. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.d.ts +0 -2
  93. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.d.ts.map +0 -1
  94. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.dev.js +0 -272
  95. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.js +0 -7
  96. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +0 -272
  97. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +0 -258
  98. package/cf-worker/package.json +0 -4
  99. package/dist/declarations/src/cf-worker/index.d.ts.map +0 -1
  100. package/dist/declarations/src/cf-worker/usage.d.ts.map +0 -1
  101. package/dist/declarations/src/core/api.d.ts +0 -84
  102. package/dist/declarations/src/core/api.d.ts.map +0 -1
  103. package/dist/declarations/src/core/authorize/client.d.ts +0 -9
  104. package/dist/declarations/src/core/authorize/client.d.ts.map +0 -1
  105. package/dist/declarations/src/core/authorize/index.d.ts.map +0 -1
  106. package/dist/declarations/src/core/authorize/service.d.ts +0 -7
  107. package/dist/declarations/src/core/authorize/service.d.ts.map +0 -1
  108. package/dist/declarations/src/core/authorize/types.d.ts +0 -12
  109. package/dist/declarations/src/core/authorize/types.d.ts.map +0 -1
  110. package/dist/declarations/src/core/rateLimit/index.d.ts.map +0 -1
  111. package/dist/declarations/src/core/rateLimit/types.d.ts.map +0 -1
  112. package/dist/declarations/src/core/services.d.ts.map +0 -1
  113. package/dist/declarations/src/core/types.d.ts.map +0 -1
  114. package/dist/declarations/src/core/usageLimit/index.d.ts +0 -5
  115. package/dist/declarations/src/core/usageLimit/index.d.ts.map +0 -1
  116. package/dist/declarations/src/core/usageLimit/types.d.ts +0 -9
  117. package/dist/declarations/src/core/usageLimit/types.d.ts.map +0 -1
  118. package/dist/declarations/src/index.d.ts +0 -2
  119. package/dist/declarations/src/index.d.ts.map +0 -1
  120. package/dist/declarations/src/node/index.d.ts +0 -43
  121. package/dist/declarations/src/node/index.d.ts.map +0 -1
  122. package/dist/index-3b9a0743.esm.js +0 -572
  123. package/dist/index-62b88cac.cjs.dev.js +0 -576
  124. package/dist/index-aa324361.cjs.prod.js +0 -576
  125. package/dist/services-2aecbda8.esm.js +0 -65
  126. package/dist/services-508322f3.cjs.dev.js +0 -70
  127. package/dist/services-5c4d6977.cjs.prod.js +0 -70
  128. package/dist/thirdweb-dev-service-utils.cjs.d.ts +0 -2
  129. package/dist/thirdweb-dev-service-utils.cjs.d.ts.map +0 -1
  130. package/dist/thirdweb-dev-service-utils.cjs.dev.js +0 -12
  131. package/dist/thirdweb-dev-service-utils.cjs.js +0 -7
  132. package/dist/thirdweb-dev-service-utils.cjs.prod.js +0 -12
  133. package/dist/thirdweb-dev-service-utils.esm.js +0 -1
  134. package/node/dist/thirdweb-dev-service-utils-node.cjs.d.ts +0 -2
  135. package/node/dist/thirdweb-dev-service-utils-node.cjs.d.ts.map +0 -1
  136. package/node/dist/thirdweb-dev-service-utils-node.cjs.dev.js +0 -193
  137. package/node/dist/thirdweb-dev-service-utils-node.cjs.js +0 -7
  138. package/node/dist/thirdweb-dev-service-utils-node.cjs.prod.js +0 -193
  139. package/node/dist/thirdweb-dev-service-utils-node.esm.js +0 -180
  140. package/node/package.json +0 -4
  141. /package/dist/{declarations/src → types}/core/rateLimit/types.d.ts +0 -0
  142. /package/dist/{declarations/src → types}/core/types.d.ts +0 -0
@@ -1,576 +0,0 @@
1
- 'use strict';
2
-
3
- async function fetchKeyMetadataFromApi(clientId, config) {
4
- const {
5
- apiUrl,
6
- serviceScope,
7
- serviceApiKey,
8
- checkPolicy,
9
- policyMetadata
10
- } = config;
11
- const policyQuery = checkPolicy && policyMetadata ? `&checkPolicy=true&policyMetadata=${encodeURIComponent(JSON.stringify(policyMetadata))}` : "";
12
- const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true${policyQuery}`;
13
- const response = await fetch(url, {
14
- method: "GET",
15
- headers: {
16
- "x-service-api-key": serviceApiKey,
17
- "content-type": "application/json"
18
- }
19
- });
20
- let text = "";
21
- try {
22
- text = await response.text();
23
- return JSON.parse(text);
24
- } catch (e) {
25
- throw new Error(`Error fetching key metadata from API: ${response.status} - ${text}`);
26
- }
27
- }
28
- async function fetchAccountFromApi(jwt, config, useWalletAuth) {
29
- const {
30
- apiUrl,
31
- serviceApiKey
32
- } = config;
33
- const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
34
- const response = await fetch(url, {
35
- method: "GET",
36
- headers: {
37
- "x-service-api-key": serviceApiKey,
38
- "content-type": "application/json",
39
- authorization: `Bearer ${jwt}`
40
- }
41
- });
42
- let text = "";
43
- try {
44
- text = await response.text();
45
- return JSON.parse(text);
46
- } catch (e) {
47
- throw new Error(`Error fetching account from API: ${response.status} - ${text}`);
48
- }
49
- }
50
- async function updateRateLimitedAt(apiKeyId, config) {
51
- const {
52
- apiUrl,
53
- serviceScope: scope,
54
- serviceApiKey
55
- } = config;
56
- const url = `${apiUrl}/usage/rateLimit`;
57
- await fetch(url, {
58
- method: "PUT",
59
- headers: {
60
- "x-service-api-key": serviceApiKey,
61
- "content-type": "application/json"
62
- },
63
- body: JSON.stringify({
64
- apiKeyId,
65
- scope
66
- })
67
- });
68
- }
69
-
70
- function authorizeClient(authOptions, apiKeyMeta) {
71
- const {
72
- origin,
73
- bundleId,
74
- secretKeyHash: providedSecretHash
75
- } = authOptions;
76
- const {
77
- domains,
78
- bundleIds,
79
- secretHash
80
- } = apiKeyMeta;
81
- const authResult = {
82
- authorized: true,
83
- apiKeyMeta,
84
- accountMeta: {
85
- id: apiKeyMeta.accountId,
86
- // TODO update this later
87
- name: "",
88
- creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
89
- limits: apiKeyMeta.limits,
90
- rateLimits: apiKeyMeta.rateLimits,
91
- usage: apiKeyMeta.usage
92
- }
93
- };
94
-
95
- // check for public restrictions
96
- if (domains.includes("*")) {
97
- return authResult;
98
- }
99
-
100
- // check for secretHash
101
- if (providedSecretHash) {
102
- if (secretHash !== providedSecretHash) {
103
- return {
104
- authorized: false,
105
- errorMessage: "Incorrect key provided. You can view your active API keys at https://thirdweb.com/dashboard/settings",
106
- errorCode: "SECRET_INVALID",
107
- status: 401
108
- };
109
- }
110
- return authResult;
111
- }
112
-
113
- // validate domains
114
- if (origin) {
115
- if (
116
- // find matching domain, or if all domains allowed
117
- domains.find(d => {
118
- // if any domain is allowed, we'll return true
119
- if (d === "*") {
120
- return true;
121
- }
122
-
123
- // special rule for `localhost`
124
- // if the domain is localhost, we'll allow any origin that starts with localhost
125
- if (d === "localhost" && origin.startsWith("localhost")) {
126
- return true;
127
- }
128
-
129
- // If the allowedDomain has a wildcard,
130
- // we'll check that the ending of our domain matches the wildcard
131
- if (d.startsWith("*.")) {
132
- // get rid of the * and check if it ends with the `.<domain>.<tld>`
133
- const domainRoot = d.slice(1);
134
- return origin.endsWith(domainRoot);
135
- }
136
-
137
- // If there's no wildcard, we'll check for an exact match
138
- return d === origin;
139
- })) {
140
- return authResult;
141
- }
142
- return {
143
- authorized: false,
144
- errorMessage: `Invalid request: Unauthorized domain: ${origin}. You can view the restrictions on this API key at https://thirdweb.com/create-api-key`,
145
- errorCode: "ORIGIN_UNAUTHORIZED",
146
- status: 401
147
- };
148
- }
149
-
150
- // validate bundleId
151
- if (bundleId) {
152
- if (
153
- // find matching bundle id, or if all bundles allowed
154
- bundleIds.find(b => {
155
- if (b === "*") {
156
- return true;
157
- }
158
- return b === bundleId;
159
- })) {
160
- return authResult;
161
- }
162
- return {
163
- authorized: false,
164
- errorMessage: `Invalid request: Unauthorized Bundle ID: ${bundleId}. You can view the restrictions on this API key at https://thirdweb.com/create-api-key`,
165
- errorCode: "BUNDLE_UNAUTHORIZED",
166
- status: 401
167
- };
168
- }
169
- return {
170
- authorized: false,
171
- errorMessage: "The keys are invalid. Please check the secret-key/clientId and try again.",
172
- errorCode: "UNAUTHORIZED",
173
- status: 401
174
- };
175
- }
176
-
177
- function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
178
- const {
179
- services
180
- } = apiKeyMetadata;
181
- // validate services
182
- const service = services.find(srv => srv.name === serviceConfig.serviceScope);
183
- if (!service) {
184
- return {
185
- authorized: false,
186
- errorMessage: `Invalid request: Unauthorized service: ${serviceConfig.serviceScope}. You can view the restrictions on this API key in your dashboard: https://thirdweb.com/create-api-key`,
187
- errorCode: "SERVICE_UNAUTHORIZED",
188
- status: 403
189
- };
190
- }
191
-
192
- // validate service actions
193
- if (serviceConfig.serviceAction) {
194
- const isActionAllowed = service.actions.includes(serviceConfig.serviceAction);
195
- if (!isActionAllowed) {
196
- return {
197
- authorized: false,
198
- errorMessage: `Invalid request: Unauthorized action: ${serviceConfig.serviceScope} ${serviceConfig.serviceAction}. You can view the restrictions on this API key in your dashboard: https://thirdweb.com/create-api-key`,
199
- errorCode: "SERVICE_ACTION_UNAUTHORIZED",
200
- status: 403
201
- };
202
- }
203
- }
204
-
205
- // validate service target addresses
206
- // the service has to pass in the target address for this to be validated
207
- if (authorizationPayload?.targetAddress) {
208
- const checkedAddresses = Array.isArray(authorizationPayload.targetAddress) ? authorizationPayload.targetAddress : [authorizationPayload.targetAddress];
209
- const allAllowed = service.targetAddresses.includes("*");
210
- if (!allAllowed && checkedAddresses.some(ta => !service.targetAddresses.includes(ta))) {
211
- return {
212
- authorized: false,
213
- errorMessage: `Invalid request: Unauthorized address: ${serviceConfig.serviceScope} ${checkedAddresses}. You can view the restrictions on this API key in your dashboard: https://thirdweb.com/create-api-key`,
214
- errorCode: "SERVICE_TARGET_ADDRESS_UNAUTHORIZED",
215
- status: 403
216
- };
217
- }
218
- }
219
- return {
220
- authorized: true,
221
- apiKeyMeta: apiKeyMetadata,
222
- accountMeta: {
223
- id: apiKeyMetadata.accountId,
224
- name: "",
225
- creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
226
- limits: apiKeyMetadata.limits,
227
- rateLimits: apiKeyMetadata.rateLimits,
228
- usage: apiKeyMetadata.usage
229
- }
230
- };
231
- }
232
-
233
- async function authorize(authData, serviceConfig, cacheOptions) {
234
- const {
235
- clientId,
236
- targetAddress,
237
- secretKeyHash,
238
- jwt,
239
- hashedJWT,
240
- useWalletAuth
241
- } = authData;
242
- const {
243
- enforceAuth
244
- } = serviceConfig;
245
-
246
- // BACKWARDS COMPAT: if auth not enforced and we don't have auth credentials bypass
247
- if (!enforceAuth && !clientId && !secretKeyHash) {
248
- return {
249
- authorized: true,
250
- apiKeyMeta: null,
251
- accountMeta: null
252
- };
253
- }
254
- // if we come in with a JWT then we only check the account is valid
255
- if (jwt && hashedJWT) {
256
- let accountMeta = null;
257
- if (cacheOptions) {
258
- try {
259
- const cachedAccountInfo = await cacheOptions.get(hashedJWT);
260
- if (cachedAccountInfo) {
261
- const parsed = JSON.parse(cachedAccountInfo);
262
- if ("updatedAt" in parsed) {
263
- // we want to compare the updatedAt time to the current time
264
- // if the difference is greater than the cacheTtl we want to ignore the cached data
265
- const now = Date.now();
266
- const diff = now - parsed.updatedAt;
267
- const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
268
- // only if the diff is less than the cacheTtl do we want to use the cached key
269
- if (diff < cacheTtlMs) {
270
- accountMeta = parsed.apiKeyMeta;
271
- }
272
- } else {
273
- accountMeta = parsed;
274
- }
275
- }
276
- } catch (err) {
277
- // ignore errors, proceed as if not in cache
278
- }
279
- }
280
- if (!accountMeta) {
281
- try {
282
- const {
283
- data,
284
- error
285
- } = await fetchAccountFromApi(jwt, serviceConfig, useWalletAuth?.toLowerCase() === "true");
286
- if (error) {
287
- return {
288
- authorized: false,
289
- errorCode: error.code,
290
- errorMessage: error.message,
291
- status: error.statusCode
292
- };
293
- } else if (!data) {
294
- return {
295
- authorized: false,
296
- errorCode: "NO_ACCOUNT",
297
- errorMessage: "No error but also no account returned.",
298
- status: 500
299
- };
300
- }
301
- accountMeta = data;
302
- if (cacheOptions) {
303
- await cacheOptions.put(hashedJWT, accountMeta);
304
- }
305
- } catch (err) {
306
- console.warn("failed to fetch account from api", err);
307
- return {
308
- authorized: false,
309
- status: 500,
310
- errorMessage: "Failed to get account information.",
311
- errorCode: "FAILED_TO_LOAD_ACCOUNT"
312
- };
313
- }
314
- }
315
- // if we still don't have an accountMeta at this point we can't authorize
316
- if (!accountMeta) {
317
- return {
318
- authorized: false,
319
- status: 401,
320
- errorMessage: "Missing account information.",
321
- errorCode: "MISSING_ACCOUNT"
322
- };
323
- }
324
- // otherwise we want to return early with the accountMeta
325
- return {
326
- authorized: true,
327
- apiKeyMeta: null,
328
- accountMeta
329
- };
330
- }
331
-
332
- // if we don't have a client id at this point we can't authorize
333
- if (!clientId) {
334
- return {
335
- authorized: false,
336
- status: 401,
337
- errorMessage: "Missing clientId or secretKey.",
338
- errorCode: "MISSING_KEY"
339
- };
340
- }
341
- let apiKeyMeta = null;
342
- // if we have cache options we want to check the cache first
343
- if (cacheOptions) {
344
- try {
345
- const cachedKey = await cacheOptions.get(clientId);
346
- if (cachedKey) {
347
- const parsed = JSON.parse(cachedKey);
348
- if ("updatedAt" in parsed) {
349
- // we want to compare the updatedAt time to the current time
350
- // if the difference is greater than the cacheTtl we want to ignore the cached data
351
- const now = Date.now();
352
- const diff = now - parsed.updatedAt;
353
- const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
354
- // only if the diff is less than the cacheTtl do we want to use the cached key
355
- if (diff < cacheTtlMs) {
356
- apiKeyMeta = parsed.apiKeyMeta;
357
- }
358
- } else {
359
- apiKeyMeta = parsed;
360
- }
361
- }
362
- } catch (err) {
363
- // ignore errors, proceed as if not in cache
364
- }
365
- }
366
-
367
- // if we don't have a cached key, fetch from the API
368
- if (!apiKeyMeta) {
369
- try {
370
- const {
371
- data,
372
- error
373
- } = await fetchKeyMetadataFromApi(clientId, serviceConfig);
374
- if (error) {
375
- return {
376
- authorized: false,
377
- errorCode: error.code,
378
- errorMessage: error.message,
379
- status: error.statusCode
380
- };
381
- } else if (!data) {
382
- return {
383
- authorized: false,
384
- errorCode: "NO_KEY",
385
- errorMessage: "No error but also no key returned.",
386
- status: 500
387
- };
388
- }
389
- // if we have a key for sure then assign it
390
- apiKeyMeta = data;
391
-
392
- // cache the retrieved key if we have cache options
393
- if (cacheOptions) {
394
- // we await this always because it can be a promise or not
395
- await cacheOptions.put(clientId, data);
396
- }
397
- } catch (err) {
398
- console.warn("failed to fetch key metadata from api", err);
399
- return {
400
- authorized: false,
401
- status: 500,
402
- errorMessage: "Failed to fetch key metadata. Please check your secret-key/clientId.",
403
- errorCode: "FAILED_TO_FETCH_KEY"
404
- };
405
- }
406
- }
407
- if (!apiKeyMeta) {
408
- return {
409
- authorized: false,
410
- status: 401,
411
- errorMessage: "Key is invalid. Please check your secret-key/clientId.",
412
- errorCode: "INVALID_KEY"
413
- };
414
- }
415
- // now we can validate the key itself
416
- const clientAuth = authorizeClient(authData, apiKeyMeta);
417
- if (!clientAuth.authorized) {
418
- return {
419
- errorCode: clientAuth.errorCode,
420
- authorized: false,
421
- status: 401,
422
- errorMessage: clientAuth.errorMessage
423
- };
424
- }
425
-
426
- // if we've made it this far we need to check service specific authorization
427
- const serviceAuth = authorizeService(apiKeyMeta, serviceConfig, {
428
- targetAddress
429
- });
430
- if (!serviceAuth.authorized) {
431
- return {
432
- errorCode: serviceAuth.errorCode,
433
- authorized: false,
434
- status: 403,
435
- errorMessage: serviceAuth.errorMessage
436
- };
437
- }
438
-
439
- // if we reach this point we are authorized!
440
- return {
441
- authorized: true,
442
- apiKeyMeta,
443
- accountMeta: {
444
- id: apiKeyMeta.accountId,
445
- // TODO update this later
446
- name: "",
447
- limits: apiKeyMeta.limits,
448
- rateLimits: apiKeyMeta.rateLimits,
449
- usage: apiKeyMeta.usage,
450
- creatorWalletAddress: apiKeyMeta.creatorWalletAddress
451
- }
452
- };
453
- }
454
-
455
- const RATE_LIMIT_WINDOW_SECONDS = 10;
456
-
457
- // Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
458
-
459
- async function rateLimit(args) {
460
- const {
461
- authzResult,
462
- serviceConfig,
463
- redis,
464
- sampleRate = 1.0
465
- } = args;
466
- const shouldSampleRequest = Math.random() < sampleRate;
467
- if (!shouldSampleRequest || !authzResult.authorized) {
468
- return {
469
- rateLimited: false,
470
- requestCount: 0,
471
- rateLimit: 0
472
- };
473
- }
474
- const {
475
- apiKeyMeta,
476
- accountMeta
477
- } = authzResult;
478
- const accountId = apiKeyMeta?.accountId || accountMeta?.id;
479
- const {
480
- serviceScope
481
- } = serviceConfig;
482
- const limitPerSecond = apiKeyMeta?.rateLimits?.[serviceScope] ?? accountMeta?.rateLimits?.[serviceScope];
483
- if (!limitPerSecond) {
484
- // No rate limit is provided. Assume the request is not rate limited.
485
- return {
486
- rateLimited: false,
487
- requestCount: 0,
488
- rateLimit: 0
489
- };
490
- }
491
-
492
- // Gets the 10-second window for the current timestamp.
493
- const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
494
- const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
495
-
496
- // Increment and get the current request count in this window.
497
- const requestCount = await redis.incr(key);
498
- if (requestCount === 1) {
499
- // For the first increment, set an expiration to clean up this key.
500
- await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
501
- }
502
-
503
- // Get the limit for this window accounting for the sample rate.
504
- const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
505
- if (requestCount > limitPerWindow) {
506
- // Report rate limit hits.
507
- if (apiKeyMeta?.id) {
508
- await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
509
- }
510
-
511
- // Reject requests when they've exceeded 2x the rate limit.
512
- if (requestCount > 2 * limitPerWindow) {
513
- return {
514
- rateLimited: true,
515
- requestCount,
516
- rateLimit: limitPerWindow,
517
- status: 429,
518
- 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.`,
519
- errorCode: "RATE_LIMIT_EXCEEDED"
520
- };
521
- }
522
- }
523
- return {
524
- rateLimited: false,
525
- requestCount,
526
- rateLimit: limitPerWindow
527
- };
528
- }
529
-
530
- async function usageLimit(authzResult, serviceConfig) {
531
- if (!authzResult.authorized) {
532
- return {
533
- usageLimited: false
534
- };
535
- }
536
- const {
537
- apiKeyMeta,
538
- accountMeta
539
- } = authzResult;
540
- const {
541
- limits,
542
- usage
543
- } = apiKeyMeta || accountMeta || {};
544
- const {
545
- serviceScope
546
- } = serviceConfig;
547
- if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
548
- // No usage limit is provided. Assume the request is not limited.
549
- return {
550
- usageLimited: false
551
- };
552
- }
553
- if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes ?? 0) > (limits.storage ?? 0)) {
554
- return {
555
- usageLimited: true,
556
- status: 403,
557
- 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.`,
558
- errorCode: "PAYMENT_METHOD_REQUIRED"
559
- };
560
- }
561
- if (serviceScope === "embeddedWallets" && (usage.embeddedWallets?.countWalletAddresses ?? 0) > (limits.embeddedWallets ?? 0)) {
562
- return {
563
- usageLimited: true,
564
- status: 403,
565
- 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.`,
566
- errorCode: "PAYMENT_METHOD_REQUIRED"
567
- };
568
- }
569
- return {
570
- usageLimited: false
571
- };
572
- }
573
-
574
- exports.authorize = authorize;
575
- exports.rateLimit = rateLimit;
576
- exports.usageLimit = usageLimit;