Skip to content

Commit 3ec808d

Browse files
Update /use and types to v2 (#5708)
## Problem solved CNCT-2640 <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring the `service-utils` package to remove the `usageLimit` module, update types and endpoints to version 2, and enhance authorization logic by integrating team and project responses. ### Detailed summary - Deleted `usageLimit` module files. - Updated API endpoint and types to version 2. - Modified `authorizeClient` and `authorizeService` functions to use `TeamAndProjectResponse`. - Adjusted authorization tests to reflect new structure. - Revised caching logic for keys. - Enhanced rate limiting to use project IDs instead of account IDs. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 7d25479 commit 3ec808d

File tree

18 files changed

+294
-662
lines changed

18 files changed

+294
-662
lines changed

.changeset/odd-flies-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@thirdweb-dev/service-utils": minor
3+
---
4+
5+
Update /use endpoint and types to v2

packages/service-utils/src/cf-worker/index.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import type {
44
Response,
55
} from "@cloudflare/workers-types";
66
import type { Request } from "@cloudflare/workers-types";
7-
import type {
8-
AccountMetadata,
9-
ApiKeyMetadata,
10-
CoreServiceConfig,
11-
} from "../core/api.js";
7+
import type { CoreServiceConfig, TeamAndProjectResponse } from "../core/api.js";
128
import { authorize } from "../core/authorize/index.js";
139
import type { AuthorizationInput } from "../core/authorize/index.js";
1410
import type { AuthorizationResult } from "../core/authorize/types.js";
@@ -17,7 +13,6 @@ import type { CoreAuthInput } from "../core/types.js";
1713
export * from "./usage.js";
1814
export * from "../core/services.js";
1915
export * from "../core/rateLimit/index.js";
20-
export * from "../core/usageLimit/index.js";
2116

2217
export type WorkerServiceConfig = CoreServiceConfig & {
2318
kvStore: KVNamespace;
@@ -57,13 +52,13 @@ export async function authorizeWorker(
5752

5853
return await authorize(authData, serviceConfig, {
5954
get: async (clientId: string) => serviceConfig.kvStore.get(clientId),
60-
put: (clientId: string, apiKeyMeta: ApiKeyMetadata | AccountMetadata) =>
55+
put: (clientId: string, data: TeamAndProjectResponse) =>
6156
serviceConfig.ctx.waitUntil(
6257
serviceConfig.kvStore.put(
6358
clientId,
6459
JSON.stringify({
6560
updatedAt: Date.now(),
66-
apiKeyMeta,
61+
data,
6762
}),
6863
{
6964
expirationTtl:

packages/service-utils/src/core/api.ts

Lines changed: 107 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { AuthorizationInput } from "./authorize/index.js";
12
import type { ServiceName } from "./services.js";
23

34
export type UserOpData = {
@@ -14,7 +15,6 @@ export type PolicyResult = {
1415
};
1516

1617
export type CoreServiceConfig = {
17-
enforceAuth?: boolean;
1818
apiUrl: string;
1919
serviceScope: ServiceName;
2020
serviceApiKey: string;
@@ -23,105 +23,131 @@ export type CoreServiceConfig = {
2323
includeUsage?: boolean;
2424
};
2525

26-
type Usage = {
27-
storage?: {
28-
sumFileSizeBytes: number;
29-
};
30-
embeddedWallets?: {
31-
countWalletAddresses: number;
26+
export type TeamAndProjectResponse = {
27+
team: TeamResponse;
28+
project?: ProjectResponse | null;
29+
};
30+
31+
export type ApiResponse = {
32+
data: TeamAndProjectResponse | null;
33+
error: {
34+
code: string;
35+
statusCode: number;
36+
message: string;
3237
};
3338
};
3439

35-
export type ApiKeyMetadata = {
40+
export type TeamResponse = {
3641
id: string;
37-
key: string;
38-
accountId: string;
39-
accountStatus: "noCustomer" | "noPayment" | "validPayment" | "invalidPayment";
40-
accountPlan: "free" | "growth" | "pro" | "enterprise";
41-
creatorWalletAddress: string;
42-
secretHash: string;
43-
walletAddresses: string[];
44-
domains: string[];
45-
bundleIds: string[];
46-
redirectUrls: string[];
47-
services: {
48-
name: string;
49-
targetAddresses: string[];
50-
actions: string[];
51-
}[];
52-
usage?: Usage;
53-
limits: Partial<Record<ServiceName, number>>;
54-
rateLimits: Partial<Record<ServiceName, number>>;
55-
policyResult?: PolicyResult;
42+
name: string;
43+
slug: string;
44+
image: string | null;
45+
billingPlan: string;
46+
createdAt: Date;
47+
updatedAt: Date | null;
48+
billingEmail: string | null;
49+
billingStatus: string | null;
50+
growthTrialEligible: boolean | null;
51+
enabledScopes: ServiceName[];
5652
};
5753

58-
export type AccountMetadata = {
54+
export type ProjectResponse = {
5955
id: string;
56+
teamId: string;
57+
createdAt: Date;
58+
updatedAt: Date | null;
59+
publishableKey: string;
6060
name: string;
61-
creatorWalletAddress: string;
62-
usage?: Usage;
63-
limits: Partial<Record<ServiceName, number>>;
64-
rateLimits: Partial<Record<ServiceName, number>>;
61+
slug: string;
62+
image: string | null;
63+
domains: string[];
64+
bundleIds: string[];
65+
services: (
66+
| {
67+
name: "pay";
68+
actions: never[];
69+
payoutAddress: string | null;
70+
}
71+
| {
72+
name: "storage";
73+
actions: ("read" | "write")[];
74+
}
75+
| {
76+
name: "rpc";
77+
actions: never[];
78+
}
79+
| {
80+
name: "insight";
81+
actions: never[];
82+
}
83+
| {
84+
name: "nebula";
85+
actions: never[];
86+
}
87+
| {
88+
name: "bundler";
89+
actions: never[];
90+
allowedChainIds?: number[] | null;
91+
allowedContractAddresses?: string[] | null;
92+
allowedWallets?: string[] | null;
93+
blockedWallets?: string[] | null;
94+
bypassWallets?: string[] | null;
95+
limits?: {
96+
global?: {
97+
maxSpend: string;
98+
maxSpendUnit: "usd" | "native";
99+
} | null;
100+
} | null;
101+
serverVerifier?: {
102+
url: string;
103+
headers?: {
104+
key: string;
105+
value: string;
106+
}[];
107+
} | null;
108+
}
109+
| {
110+
name: "embeddedWallets";
111+
actions: never[];
112+
redirectUrls?: string[] | null;
113+
applicationName?: string | null;
114+
applicationImageUrl?: string | null;
115+
recoveryShareManagement?: string | null;
116+
customAuthentication?: CustomAuthenticationServiceSchema | null;
117+
customAuthEndpoint?: CustomAuthEndpointServiceSchema | null;
118+
}
119+
)[];
120+
walletAddresses: string[];
65121
};
66122

67-
export type ApiResponse = {
68-
data: ApiKeyMetadata | null;
69-
error: {
70-
code: string;
71-
statusCode: number;
72-
message: string;
73-
};
123+
type CustomAuthenticationServiceSchema = {
124+
jwksUri: string;
125+
aud: string;
74126
};
75127

76-
export type ApiAccountResponse = {
77-
data: AccountMetadata | null;
78-
error: {
79-
code: string;
80-
statusCode: number;
81-
message: string;
82-
};
128+
type CustomAuthEndpointServiceSchema = {
129+
authEndpoint: string;
130+
customHeaders: {
131+
key: string;
132+
value: string;
133+
}[];
83134
};
84135

85-
export async function fetchKeyMetadataFromApi(
86-
clientId: string,
136+
export async function fetchTeamAndProject(
137+
authData: AuthorizationInput,
87138
config: CoreServiceConfig,
88139
): Promise<ApiResponse> {
89-
const { apiUrl, serviceScope, serviceApiKey, includeUsage = true } = config;
90-
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=${includeUsage}`;
91-
const response = await fetch(url, {
92-
method: "GET",
93-
headers: {
94-
"x-service-api-key": serviceApiKey,
95-
"content-type": "application/json",
96-
},
97-
});
98-
99-
let text = "";
100-
try {
101-
text = await response.text();
102-
return JSON.parse(text);
103-
} catch {
104-
throw new Error(
105-
`Error fetching key metadata from API: ${response.status} - ${text}`,
106-
);
107-
}
108-
}
140+
const { apiUrl, serviceApiKey } = config;
109141

110-
export async function fetchAccountFromApi(
111-
jwt: string,
112-
config: CoreServiceConfig,
113-
useWalletAuth: boolean,
114-
): Promise<ApiAccountResponse> {
115-
const { apiUrl, serviceApiKey, includeUsage = true } = config;
116-
const url = useWalletAuth
117-
? `${apiUrl}/v1/wallet/me?includeUsage=${includeUsage}`
118-
: `${apiUrl}/v1/account/me?includeUsage=${includeUsage}`;
142+
const clientId = authData.clientId;
143+
const url = `${apiUrl}/v2/keys/use${clientId ? `?clientId=${clientId}` : ""}`;
119144
const response = await fetch(url, {
120145
method: "GET",
121146
headers: {
147+
...(authData.secretKey ? { "x-secret-key": authData.secretKey } : {}),
148+
...(authData.jwt ? { Authorization: `Bearer ${authData.jwt}` } : {}),
122149
"x-service-api-key": serviceApiKey,
123150
"content-type": "application/json",
124-
authorization: `Bearer ${jwt}`,
125151
},
126152
});
127153

@@ -131,13 +157,13 @@ export async function fetchAccountFromApi(
131157
return JSON.parse(text);
132158
} catch {
133159
throw new Error(
134-
`Error fetching account from API: ${response.status} - ${text}`,
160+
`Error fetching key metadata from API: ${response.status} - ${text}`,
135161
);
136162
}
137163
}
138164

139165
export async function updateRateLimitedAt(
140-
apiKeyId: string,
166+
projectId: string,
141167
config: CoreServiceConfig,
142168
): Promise<void> {
143169
const { apiUrl, serviceScope: scope, serviceApiKey } = config;
@@ -151,7 +177,7 @@ export async function updateRateLimitedAt(
151177
"content-type": "application/json",
152178
},
153179
body: JSON.stringify({
154-
apiKeyId,
180+
apiKeyId: projectId, // projectId is the apiKeyId
155181
scope,
156182
}),
157183
});

packages/service-utils/src/core/authorize/authorize.test.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ const validServiceConfig: CoreServiceConfig = {
66
apiUrl: "https://api.example.com",
77
serviceScope: "storage",
88
serviceApiKey: "service key",
9-
enforceAuth: false,
109
};
1110

1211
describe("authorizeClient", () => {
13-
it("should skip authorization if auth not enforced and no credentials", async () => {
12+
it("should not authorize if auth not enforced and no credentials", async () => {
1413
const result = (await authorize(
1514
{
1615
secretKey: null,
@@ -20,30 +19,13 @@ describe("authorizeClient", () => {
2019
secretKeyHash: null,
2120
hashedJWT: null,
2221
jwt: null,
22+
ecosystemId: null,
23+
ecosystemPartnerId: null,
2324
},
2425
validServiceConfig,
2526
// biome-ignore lint/suspicious/noExplicitAny: test only
2627
)) as any;
2728

28-
expect(result.authorized).toBe(true);
29-
expect(result.apiKeyMeta).toEqual(null);
30-
});
31-
32-
it("should continue authorization if auth enforced", async () => {
33-
const result = (await authorize(
34-
{
35-
secretKey: null,
36-
clientId: null,
37-
origin: null,
38-
bundleId: null,
39-
secretKeyHash: null,
40-
hashedJWT: null,
41-
jwt: null,
42-
},
43-
{ ...validServiceConfig, enforceAuth: true },
44-
// biome-ignore lint/suspicious/noExplicitAny: test only
45-
)) as any;
46-
4729
expect(result.authorized).toBe(false);
4830
});
4931
});

0 commit comments

Comments
 (0)