@zapier/zapier-sdk 0.22.0 → 0.23.0
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/CHANGELOG.md +12 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +26 -17
- package/dist/api/client.test.js +81 -1
- package/dist/api/types.d.ts +6 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth.d.ts +13 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +72 -20
- package/dist/auth.test.js +98 -2
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +15 -6
- package/dist/index.cjs +96 -41
- package/dist/index.d.mts +19 -2
- package/dist/index.mjs +96 -41
- package/dist/plugins/createClientCredentials/index.d.ts.map +1 -1
- package/dist/plugins/createClientCredentials/index.js +1 -0
- package/dist/plugins/deleteClientCredentials/index.d.ts.map +1 -1
- package/dist/plugins/deleteClientCredentials/index.js +1 -0
- package/dist/plugins/listClientCredentials/index.d.ts.map +1 -1
- package/dist/plugins/listClientCredentials/index.js +1 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @zapier/zapier-sdk
|
|
2
2
|
|
|
3
|
+
## 0.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7cf2b12: Add debug logging to zapier-sdk-cli-login. Only allow one token refresh at a time.
|
|
8
|
+
|
|
9
|
+
## 0.22.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 8ebde4b: Add required scopes for credentials methods.
|
|
14
|
+
|
|
3
15
|
## 0.22.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAGjB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AA2gBjB,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,SAW3D,CAAC"}
|
package/dist/api/client.js
CHANGED
|
@@ -69,18 +69,20 @@ class ZapierApiClient {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
// Helper to get a token from the different places it could be gotten
|
|
72
|
-
async getAuthToken() {
|
|
72
|
+
async getAuthToken(options) {
|
|
73
73
|
return resolveAuthToken({
|
|
74
74
|
credentials: this.options.credentials,
|
|
75
75
|
token: this.options.token,
|
|
76
76
|
onEvent: this.options.onEvent,
|
|
77
77
|
fetch: this.options.fetch,
|
|
78
78
|
baseUrl: this.options.baseUrl,
|
|
79
|
+
requiredScopes: options?.requiredScopes,
|
|
80
|
+
debug: this.options.debug,
|
|
79
81
|
});
|
|
80
82
|
}
|
|
81
83
|
// Helper to handle responses
|
|
82
84
|
async handleResponse(params) {
|
|
83
|
-
const { response, customErrorHandler, wasMissingAuthToken } = params;
|
|
85
|
+
const { response, customErrorHandler, wasMissingAuthToken, requiredScopes, } = params;
|
|
84
86
|
const { data: responseData } = await this.parseResult(response);
|
|
85
87
|
if (response.ok) {
|
|
86
88
|
return responseData;
|
|
@@ -123,6 +125,7 @@ class ZapierApiClient {
|
|
|
123
125
|
await invalidateCredentialsToken({
|
|
124
126
|
credentials: this.options.credentials,
|
|
125
127
|
token: this.options.token,
|
|
128
|
+
requiredScopes,
|
|
126
129
|
});
|
|
127
130
|
}
|
|
128
131
|
throw new ZapierAuthenticationError(message, errorOptions);
|
|
@@ -237,6 +240,16 @@ class ZapierApiClient {
|
|
|
237
240
|
// Find matching path configuration.
|
|
238
241
|
const matchingPathKey = Object.keys(pathConfig).find((configPath) => path === configPath || path.startsWith(configPath + "/"));
|
|
239
242
|
const config = matchingPathKey ? pathConfig[matchingPathKey] : undefined;
|
|
243
|
+
// Apply pathPrefix if there's a matching config with pathPrefix.
|
|
244
|
+
let finalPath = path;
|
|
245
|
+
if (config &&
|
|
246
|
+
"pathPrefix" in config &&
|
|
247
|
+
config.pathPrefix &&
|
|
248
|
+
matchingPathKey) {
|
|
249
|
+
// Strip the matching path key, and use the pathPrefix instead.
|
|
250
|
+
const pathWithoutPrefix = path.slice(matchingPathKey.length) || "/";
|
|
251
|
+
finalPath = `${config.pathPrefix}${pathWithoutPrefix}`;
|
|
252
|
+
}
|
|
240
253
|
// Check if baseUrl is a Zapier-inferred base URL.
|
|
241
254
|
const zapierBaseUrl = getZapierBaseUrl(this.options.baseUrl);
|
|
242
255
|
// Let's remain compatible with a base URL that is set to a Zapier-inferred
|
|
@@ -246,26 +259,17 @@ class ZapierApiClient {
|
|
|
246
259
|
// If baseUrl is already the Zapier base URL, use sdkapi subdomain.
|
|
247
260
|
const originalBaseUrl = new URL(this.options.baseUrl);
|
|
248
261
|
const finalBaseUrl = `https://sdkapi.${originalBaseUrl.hostname}`;
|
|
249
|
-
// Only prepend pathPrefix if there's a matching config with pathPrefix.
|
|
250
|
-
let finalPath = path;
|
|
251
|
-
if (config &&
|
|
252
|
-
"pathPrefix" in config &&
|
|
253
|
-
config.pathPrefix &&
|
|
254
|
-
matchingPathKey) {
|
|
255
|
-
// Strip the matching path key, and use the pathPrefix instead.
|
|
256
|
-
const pathWithoutPrefix = path.slice(matchingPathKey.length) || "/";
|
|
257
|
-
finalPath = `${config.pathPrefix}${pathWithoutPrefix}`;
|
|
258
|
-
}
|
|
259
262
|
return {
|
|
260
263
|
url: new URL(finalPath, finalBaseUrl),
|
|
261
264
|
pathConfig: config,
|
|
262
265
|
};
|
|
263
266
|
}
|
|
264
|
-
// For a base URL that isn't a Zapier-inferred domain
|
|
267
|
+
// For a base URL that isn't a Zapier-inferred domain (e.g., localhost),
|
|
268
|
+
// preserve any path from the base URL and append the final path.
|
|
265
269
|
const baseUrl = new URL(this.options.baseUrl);
|
|
266
|
-
const
|
|
270
|
+
const basePath = baseUrl.pathname.replace(/\/$/, "");
|
|
267
271
|
return {
|
|
268
|
-
url: new URL(
|
|
272
|
+
url: new URL(basePath + finalPath, baseUrl.origin),
|
|
269
273
|
pathConfig: config,
|
|
270
274
|
};
|
|
271
275
|
}
|
|
@@ -286,7 +290,9 @@ class ZapierApiClient {
|
|
|
286
290
|
// useful context to the API. The session is a good example of this. Auth
|
|
287
291
|
// is not required, but if we don't add auth, then we won't get the user's
|
|
288
292
|
// session!
|
|
289
|
-
const authToken = await this.getAuthToken(
|
|
293
|
+
const authToken = await this.getAuthToken({
|
|
294
|
+
requiredScopes: options.requiredScopes,
|
|
295
|
+
});
|
|
290
296
|
if (authToken) {
|
|
291
297
|
const authHeaderName = pathConfig && pathConfig.authHeader
|
|
292
298
|
? pathConfig.authHeader
|
|
@@ -310,7 +316,9 @@ class ZapierApiClient {
|
|
|
310
316
|
headers["Content-Type"] = "application/json";
|
|
311
317
|
}
|
|
312
318
|
// Check if we have an auth token available
|
|
313
|
-
const wasMissingAuthToken = options.authRequired &&
|
|
319
|
+
const wasMissingAuthToken = options.authRequired &&
|
|
320
|
+
(await this.getAuthToken({ requiredScopes: options.requiredScopes })) ==
|
|
321
|
+
null;
|
|
314
322
|
const response = await this.plainFetch(path, {
|
|
315
323
|
...options,
|
|
316
324
|
method,
|
|
@@ -322,6 +330,7 @@ class ZapierApiClient {
|
|
|
322
330
|
response,
|
|
323
331
|
customErrorHandler: options.customErrorHandler,
|
|
324
332
|
wasMissingAuthToken,
|
|
333
|
+
requiredScopes: options.requiredScopes,
|
|
325
334
|
});
|
|
326
335
|
// Allow empty responses for DELETE or 204 No Content
|
|
327
336
|
if (typeof result === "string") {
|
package/dist/api/client.test.js
CHANGED
|
@@ -5,14 +5,16 @@ vi.mock("../auth");
|
|
|
5
5
|
describe("ApiClient", () => {
|
|
6
6
|
const mockResolveAuthToken = vi.mocked(auth.resolveAuthToken);
|
|
7
7
|
const mockInvalidateCredentialsToken = vi.mocked(auth.invalidateCredentialsToken);
|
|
8
|
+
let mockFetch;
|
|
8
9
|
beforeEach(() => {
|
|
9
10
|
vi.clearAllMocks();
|
|
10
11
|
// Prevent any actual HTTP calls
|
|
11
|
-
|
|
12
|
+
mockFetch = vi.fn().mockResolvedValue({
|
|
12
13
|
ok: true,
|
|
13
14
|
status: 200,
|
|
14
15
|
text: () => Promise.resolve(JSON.stringify({ data: "test" })),
|
|
15
16
|
});
|
|
17
|
+
global.fetch = mockFetch;
|
|
16
18
|
});
|
|
17
19
|
describe("authentication token resolution", () => {
|
|
18
20
|
it("should pass credentials to resolveAuthToken when provided", async () => {
|
|
@@ -35,6 +37,8 @@ describe("ApiClient", () => {
|
|
|
35
37
|
onEvent: undefined,
|
|
36
38
|
fetch: expect.any(Function),
|
|
37
39
|
baseUrl: "https://api.custom.zapier.dev",
|
|
40
|
+
requiredScopes: undefined,
|
|
41
|
+
debug: false,
|
|
38
42
|
});
|
|
39
43
|
});
|
|
40
44
|
it("should pass undefined credentials when not provided", async () => {
|
|
@@ -51,6 +55,8 @@ describe("ApiClient", () => {
|
|
|
51
55
|
onEvent: undefined,
|
|
52
56
|
fetch: expect.any(Function),
|
|
53
57
|
baseUrl: "https://api.custom.zapier.dev",
|
|
58
|
+
requiredScopes: undefined,
|
|
59
|
+
debug: false,
|
|
54
60
|
});
|
|
55
61
|
});
|
|
56
62
|
it("should pass deprecated token option to resolveAuthToken", async () => {
|
|
@@ -69,6 +75,8 @@ describe("ApiClient", () => {
|
|
|
69
75
|
onEvent: undefined,
|
|
70
76
|
fetch: expect.any(Function),
|
|
71
77
|
baseUrl: "https://api.custom.zapier.dev",
|
|
78
|
+
requiredScopes: undefined,
|
|
79
|
+
debug: false,
|
|
72
80
|
});
|
|
73
81
|
});
|
|
74
82
|
it("should pass string credentials to resolveAuthToken", async () => {
|
|
@@ -85,6 +93,8 @@ describe("ApiClient", () => {
|
|
|
85
93
|
onEvent: undefined,
|
|
86
94
|
fetch: expect.any(Function),
|
|
87
95
|
baseUrl: "https://api.custom.zapier.dev",
|
|
96
|
+
requiredScopes: undefined,
|
|
97
|
+
debug: false,
|
|
88
98
|
});
|
|
89
99
|
});
|
|
90
100
|
it("should pass credentials function to resolveAuthToken", async () => {
|
|
@@ -102,6 +112,8 @@ describe("ApiClient", () => {
|
|
|
102
112
|
onEvent: undefined,
|
|
103
113
|
fetch: expect.any(Function),
|
|
104
114
|
baseUrl: "https://api.custom.zapier.dev",
|
|
115
|
+
requiredScopes: undefined,
|
|
116
|
+
debug: false,
|
|
105
117
|
});
|
|
106
118
|
});
|
|
107
119
|
});
|
|
@@ -148,4 +160,72 @@ describe("ApiClient", () => {
|
|
|
148
160
|
expect(mockInvalidateCredentialsToken).not.toHaveBeenCalled();
|
|
149
161
|
});
|
|
150
162
|
});
|
|
163
|
+
describe("URL path configuration", () => {
|
|
164
|
+
beforeEach(() => {
|
|
165
|
+
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
166
|
+
});
|
|
167
|
+
it("should apply pathPrefix for /relay path with Zapier base URL", async () => {
|
|
168
|
+
const client = createZapierApi({
|
|
169
|
+
baseUrl: "https://zapier.com",
|
|
170
|
+
credentials: "test-token",
|
|
171
|
+
debug: false,
|
|
172
|
+
});
|
|
173
|
+
await client.get("/relay/some/path");
|
|
174
|
+
expect(mockFetch).toHaveBeenCalledWith("https://sdkapi.zapier.com/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
175
|
+
});
|
|
176
|
+
it("should apply pathPrefix for /zapier path with Zapier base URL", async () => {
|
|
177
|
+
const client = createZapierApi({
|
|
178
|
+
baseUrl: "https://zapier.com",
|
|
179
|
+
credentials: "test-token",
|
|
180
|
+
debug: false,
|
|
181
|
+
});
|
|
182
|
+
await client.get("/zapier/apps");
|
|
183
|
+
expect(mockFetch).toHaveBeenCalledWith("https://sdkapi.zapier.com/api/v0/sdk/zapier/apps", expect.any(Object));
|
|
184
|
+
});
|
|
185
|
+
it("should apply pathPrefix for /relay path with localhost base URL", async () => {
|
|
186
|
+
const client = createZapierApi({
|
|
187
|
+
baseUrl: "http://localhost:3000",
|
|
188
|
+
credentials: "test-token",
|
|
189
|
+
debug: false,
|
|
190
|
+
});
|
|
191
|
+
await client.get("/relay/some/path");
|
|
192
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
193
|
+
});
|
|
194
|
+
it("should apply pathPrefix for /zapier path with localhost base URL", async () => {
|
|
195
|
+
const client = createZapierApi({
|
|
196
|
+
baseUrl: "http://localhost:3000",
|
|
197
|
+
credentials: "test-token",
|
|
198
|
+
debug: false,
|
|
199
|
+
});
|
|
200
|
+
await client.get("/zapier/apps");
|
|
201
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/zapier/apps", expect.any(Object));
|
|
202
|
+
});
|
|
203
|
+
it("should not apply pathPrefix for non-configured paths", async () => {
|
|
204
|
+
const client = createZapierApi({
|
|
205
|
+
baseUrl: "http://localhost:3000",
|
|
206
|
+
credentials: "test-token",
|
|
207
|
+
debug: false,
|
|
208
|
+
});
|
|
209
|
+
await client.get("/other/path");
|
|
210
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/other/path", expect.any(Object));
|
|
211
|
+
});
|
|
212
|
+
it("should handle exact path match for pathPrefix", async () => {
|
|
213
|
+
const client = createZapierApi({
|
|
214
|
+
baseUrl: "http://localhost:3000",
|
|
215
|
+
credentials: "test-token",
|
|
216
|
+
debug: false,
|
|
217
|
+
});
|
|
218
|
+
await client.get("/relay");
|
|
219
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/relay/", expect.any(Object));
|
|
220
|
+
});
|
|
221
|
+
it("should preserve base URL path for localhost with path component", async () => {
|
|
222
|
+
const client = createZapierApi({
|
|
223
|
+
baseUrl: "http://localhost:3000/a/b/c",
|
|
224
|
+
credentials: "test-token",
|
|
225
|
+
debug: false,
|
|
226
|
+
});
|
|
227
|
+
await client.get("/relay/some/path");
|
|
228
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/a/b/c/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
229
|
+
});
|
|
230
|
+
});
|
|
151
231
|
});
|
package/dist/api/types.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface RequestOptions {
|
|
|
43
43
|
headers?: Record<string, string>;
|
|
44
44
|
searchParams?: Record<string, string>;
|
|
45
45
|
authRequired?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* OAuth scopes required for this request. When using client credentials,
|
|
48
|
+
* these scopes will be requested during token exchange (merged with any
|
|
49
|
+
* scopes specified in the credentials object).
|
|
50
|
+
*/
|
|
51
|
+
requiredScopes?: string[];
|
|
46
52
|
customErrorHandler?: (errorInfo: {
|
|
47
53
|
status: number;
|
|
48
54
|
statusText: string;
|
package/dist/api/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
CHANGED
|
@@ -23,15 +23,24 @@ export interface ResolveAuthTokenOptions {
|
|
|
23
23
|
onEvent?: EventCallback;
|
|
24
24
|
fetch?: typeof globalThis.fetch;
|
|
25
25
|
baseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Additional OAuth scopes required for this request. When using client
|
|
28
|
+
* credentials, these scopes will be merged with any scopes specified in
|
|
29
|
+
* the credentials object during token exchange.
|
|
30
|
+
*/
|
|
31
|
+
requiredScopes?: string[];
|
|
32
|
+
/** Enable debug logging for auth operations. */
|
|
33
|
+
debug?: boolean;
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
36
|
* Clear the token cache. Useful for testing or forcing re-authentication.
|
|
29
37
|
*/
|
|
30
38
|
export declare function clearTokenCache(): void;
|
|
31
39
|
/**
|
|
32
|
-
* Invalidate a cached token
|
|
40
|
+
* Invalidate a cached token for a specific clientId and scope combination.
|
|
41
|
+
* Called when we get a 401 response.
|
|
33
42
|
*/
|
|
34
|
-
export declare function invalidateCachedToken(clientId: string): void;
|
|
43
|
+
export declare function invalidateCachedToken(clientId: string, scopes: string[]): void;
|
|
35
44
|
/**
|
|
36
45
|
* Options for getTokenFromCliLogin.
|
|
37
46
|
*/
|
|
@@ -44,6 +53,7 @@ interface CliLoginOptions {
|
|
|
44
53
|
baseUrl?: string;
|
|
45
54
|
scope?: string;
|
|
46
55
|
};
|
|
56
|
+
debug?: boolean;
|
|
47
57
|
}
|
|
48
58
|
/**
|
|
49
59
|
* Attempts to get a token by optionally importing from CLI login package.
|
|
@@ -77,5 +87,6 @@ export declare function resolveAuthToken(options?: ResolveAuthTokenOptions): Pro
|
|
|
77
87
|
export declare function invalidateCredentialsToken(options: {
|
|
78
88
|
credentials?: Credentials;
|
|
79
89
|
token?: string;
|
|
90
|
+
requiredScopes?: string[];
|
|
80
91
|
}): Promise<void>;
|
|
81
92
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,qBAAqB,CAAC;AAM5E,YAAY,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,aAAa,GACd,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,qBAAqB,CAAC;AAM5E,YAAY,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,aAAa,GACd,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA+BD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AA0CD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,CAIN;AA6HD;;GAEG;AACH,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AA6ED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE;IACxD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB"}
|
package/dist/auth.js
CHANGED
|
@@ -14,14 +14,22 @@ import { ZAPIER_BASE_URL } from "./constants";
|
|
|
14
14
|
export { isClientCredentials, isPkceCredentials, isCredentialsObject, isCredentialsFunction, } from "./types/credentials";
|
|
15
15
|
/**
|
|
16
16
|
* In-memory cache for tokens obtained via client credentials flow.
|
|
17
|
-
* Keyed by clientId.
|
|
17
|
+
* Keyed by clientId + sorted scopes.
|
|
18
18
|
*/
|
|
19
19
|
const tokenCache = new Map();
|
|
20
20
|
/**
|
|
21
21
|
* In-flight token exchange promises to prevent duplicate exchanges.
|
|
22
22
|
* When an exchange is in progress, subsequent calls await the same promise.
|
|
23
|
+
* Keyed by clientId + sorted scopes.
|
|
23
24
|
*/
|
|
24
25
|
const pendingExchanges = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* Build a cache key from clientId and scopes.
|
|
28
|
+
*/
|
|
29
|
+
function buildCacheKey(clientId, scopes) {
|
|
30
|
+
const sortedScopes = [...scopes].sort().join(",");
|
|
31
|
+
return `${clientId}:${sortedScopes}`;
|
|
32
|
+
}
|
|
25
33
|
/**
|
|
26
34
|
* Clear the token cache. Useful for testing or forcing re-authentication.
|
|
27
35
|
*/
|
|
@@ -34,8 +42,9 @@ const TOKEN_EXPIRATION_BUFFER = 5 * 60 * 1000; // 5 minutes
|
|
|
34
42
|
* Get a cached token if it exists and is not expired.
|
|
35
43
|
* Returns undefined if no valid cached token exists.
|
|
36
44
|
*/
|
|
37
|
-
function getCachedToken(clientId) {
|
|
38
|
-
const
|
|
45
|
+
function getCachedToken(clientId, scopes) {
|
|
46
|
+
const cacheKey = buildCacheKey(clientId, scopes);
|
|
47
|
+
const cached = tokenCache.get(cacheKey);
|
|
39
48
|
if (!cached)
|
|
40
49
|
return undefined;
|
|
41
50
|
// Check if token is still valid (with expiration buffer)
|
|
@@ -43,23 +52,27 @@ function getCachedToken(clientId) {
|
|
|
43
52
|
return cached.accessToken;
|
|
44
53
|
}
|
|
45
54
|
// Token expired, remove from cache
|
|
46
|
-
tokenCache.delete(
|
|
55
|
+
tokenCache.delete(cacheKey);
|
|
47
56
|
return undefined;
|
|
48
57
|
}
|
|
49
58
|
/**
|
|
50
59
|
* Cache a token obtained from client credentials flow.
|
|
51
60
|
*/
|
|
52
|
-
function cacheToken(clientId, accessToken, expiresIn) {
|
|
53
|
-
|
|
61
|
+
function cacheToken(clientId, scopes, accessToken, expiresIn) {
|
|
62
|
+
const cacheKey = buildCacheKey(clientId, scopes);
|
|
63
|
+
tokenCache.set(cacheKey, {
|
|
54
64
|
accessToken,
|
|
55
65
|
expiresAt: Date.now() + expiresIn * 1000,
|
|
56
66
|
});
|
|
57
67
|
}
|
|
58
68
|
/**
|
|
59
|
-
* Invalidate a cached token
|
|
69
|
+
* Invalidate a cached token for a specific clientId and scope combination.
|
|
70
|
+
* Called when we get a 401 response.
|
|
60
71
|
*/
|
|
61
|
-
export function invalidateCachedToken(clientId) {
|
|
62
|
-
|
|
72
|
+
export function invalidateCachedToken(clientId, scopes) {
|
|
73
|
+
const cacheKey = buildCacheKey(clientId, scopes);
|
|
74
|
+
tokenCache.delete(cacheKey);
|
|
75
|
+
pendingExchanges.delete(cacheKey);
|
|
63
76
|
}
|
|
64
77
|
/**
|
|
65
78
|
* Get the token endpoint URL for client credentials exchange.
|
|
@@ -68,13 +81,43 @@ function getTokenEndpointUrl(baseUrl) {
|
|
|
68
81
|
const base = baseUrl || ZAPIER_BASE_URL;
|
|
69
82
|
return `${base}/oauth/token/`;
|
|
70
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Merge and deduplicate scopes from credentials and required scopes.
|
|
86
|
+
* If no credentials scope is specified, defaults to "external".
|
|
87
|
+
* Returns a sorted array of unique scopes.
|
|
88
|
+
*/
|
|
89
|
+
function mergeScopes(credentialsScope, requiredScopes) {
|
|
90
|
+
const scopeSet = new Set();
|
|
91
|
+
// Add scopes from credentials (space-separated string)
|
|
92
|
+
// If no credentials scope specified, default to "external"
|
|
93
|
+
if (credentialsScope) {
|
|
94
|
+
for (const s of credentialsScope.split(" ")) {
|
|
95
|
+
if (s.trim()) {
|
|
96
|
+
scopeSet.add(s.trim());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
scopeSet.add("external");
|
|
102
|
+
}
|
|
103
|
+
// Add required scopes
|
|
104
|
+
if (requiredScopes) {
|
|
105
|
+
for (const s of requiredScopes) {
|
|
106
|
+
scopeSet.add(s);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return [...scopeSet].sort();
|
|
110
|
+
}
|
|
71
111
|
/**
|
|
72
112
|
* Exchange client credentials for an access token.
|
|
73
113
|
*/
|
|
74
114
|
async function exchangeClientCredentials(options) {
|
|
75
|
-
const { clientId, clientSecret, baseUrl, scope, onEvent } = options;
|
|
115
|
+
const { clientId, clientSecret, baseUrl, scope, requiredScopes, onEvent } = options;
|
|
76
116
|
const fetchFn = options.fetch || globalThis.fetch;
|
|
77
117
|
const tokenUrl = getTokenEndpointUrl(baseUrl);
|
|
118
|
+
// Merge credentials scope with required scopes
|
|
119
|
+
const mergedScopes = mergeScopes(scope, requiredScopes);
|
|
120
|
+
const scopeString = mergedScopes.join(" ");
|
|
78
121
|
onEvent?.({
|
|
79
122
|
type: "auth_exchanging",
|
|
80
123
|
payload: {
|
|
@@ -92,7 +135,7 @@ async function exchangeClientCredentials(options) {
|
|
|
92
135
|
grant_type: "client_credentials",
|
|
93
136
|
client_id: clientId,
|
|
94
137
|
client_secret: clientSecret,
|
|
95
|
-
scope:
|
|
138
|
+
scope: scopeString,
|
|
96
139
|
audience: "zapier.com",
|
|
97
140
|
}),
|
|
98
141
|
});
|
|
@@ -113,9 +156,9 @@ async function exchangeClientCredentials(options) {
|
|
|
113
156
|
if (!data.access_token) {
|
|
114
157
|
throw new Error("Client credentials response missing access_token");
|
|
115
158
|
}
|
|
116
|
-
// Cache the token
|
|
159
|
+
// Cache the token with the scopes used
|
|
117
160
|
const expiresIn = data.expires_in || 3600; // Default to 1 hour
|
|
118
|
-
cacheToken(clientId, data.access_token, expiresIn);
|
|
161
|
+
cacheToken(clientId, mergedScopes, data.access_token, expiresIn);
|
|
119
162
|
onEvent?.({
|
|
120
163
|
type: "auth_success",
|
|
121
164
|
payload: {
|
|
@@ -177,6 +220,7 @@ export async function resolveAuthToken(options = {}) {
|
|
|
177
220
|
return getTokenFromCliLogin({
|
|
178
221
|
onEvent: options.onEvent,
|
|
179
222
|
fetch: options.fetch,
|
|
223
|
+
debug: options.debug,
|
|
180
224
|
});
|
|
181
225
|
}
|
|
182
226
|
/**
|
|
@@ -190,13 +234,16 @@ async function resolveAuthTokenFromCredentials(credentials, options) {
|
|
|
190
234
|
// Client credentials: exchange for token
|
|
191
235
|
if (isClientCredentials(credentials)) {
|
|
192
236
|
const { clientId } = credentials;
|
|
237
|
+
// Compute merged scopes for cache lookup
|
|
238
|
+
const mergedScopes = mergeScopes(credentials.scope, options.requiredScopes);
|
|
239
|
+
const cacheKey = buildCacheKey(clientId, mergedScopes);
|
|
193
240
|
// Check cache first
|
|
194
|
-
const cached = getCachedToken(clientId);
|
|
241
|
+
const cached = getCachedToken(clientId, mergedScopes);
|
|
195
242
|
if (cached) {
|
|
196
243
|
return cached;
|
|
197
244
|
}
|
|
198
|
-
// Check if there's already an exchange in progress for this clientId
|
|
199
|
-
const pending = pendingExchanges.get(
|
|
245
|
+
// Check if there's already an exchange in progress for this clientId + scopes
|
|
246
|
+
const pending = pendingExchanges.get(cacheKey);
|
|
200
247
|
if (pending) {
|
|
201
248
|
return pending;
|
|
202
249
|
}
|
|
@@ -206,13 +253,14 @@ async function resolveAuthTokenFromCredentials(credentials, options) {
|
|
|
206
253
|
clientSecret: credentials.clientSecret,
|
|
207
254
|
baseUrl: credentials.baseUrl || options.baseUrl,
|
|
208
255
|
scope: credentials.scope,
|
|
256
|
+
requiredScopes: options.requiredScopes,
|
|
209
257
|
fetch: options.fetch,
|
|
210
258
|
onEvent: options.onEvent,
|
|
211
259
|
}).finally(() => {
|
|
212
260
|
// Remove from pending when done (success or failure)
|
|
213
|
-
pendingExchanges.delete(
|
|
261
|
+
pendingExchanges.delete(cacheKey);
|
|
214
262
|
});
|
|
215
|
-
pendingExchanges.set(
|
|
263
|
+
pendingExchanges.set(cacheKey, exchangePromise);
|
|
216
264
|
return exchangePromise;
|
|
217
265
|
}
|
|
218
266
|
// PKCE credentials: delegate to CLI login
|
|
@@ -222,6 +270,7 @@ async function resolveAuthTokenFromCredentials(credentials, options) {
|
|
|
222
270
|
onEvent: options.onEvent,
|
|
223
271
|
fetch: options.fetch,
|
|
224
272
|
credentials,
|
|
273
|
+
debug: options.debug,
|
|
225
274
|
});
|
|
226
275
|
if (storedToken) {
|
|
227
276
|
return storedToken;
|
|
@@ -240,8 +289,11 @@ async function resolveAuthTokenFromCredentials(credentials, options) {
|
|
|
240
289
|
*/
|
|
241
290
|
export async function invalidateCredentialsToken(options) {
|
|
242
291
|
const resolved = await resolveCredentials(options);
|
|
292
|
+
if (!resolved)
|
|
293
|
+
return;
|
|
243
294
|
const clientId = getClientIdFromCredentials(resolved);
|
|
244
|
-
if (clientId) {
|
|
245
|
-
|
|
295
|
+
if (clientId && isClientCredentials(resolved)) {
|
|
296
|
+
const scopes = mergeScopes(resolved.scope, options.requiredScopes);
|
|
297
|
+
invalidateCachedToken(clientId, scopes);
|
|
246
298
|
}
|
|
247
299
|
}
|
package/dist/auth.test.js
CHANGED
|
@@ -218,6 +218,102 @@ describe("auth", () => {
|
|
|
218
218
|
fetch: mockFetch,
|
|
219
219
|
})).rejects.toThrow("Client credentials exchange failed");
|
|
220
220
|
});
|
|
221
|
+
it("should merge requiredScopes with credentials scope", async () => {
|
|
222
|
+
mockFetch.mockResolvedValue({
|
|
223
|
+
ok: true,
|
|
224
|
+
json: () => Promise.resolve({
|
|
225
|
+
access_token: "merged-scope-token",
|
|
226
|
+
expires_in: 3600,
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
await auth.resolveAuthToken({
|
|
230
|
+
credentials: {
|
|
231
|
+
type: "client_credentials",
|
|
232
|
+
clientId: "scope-test-client",
|
|
233
|
+
clientSecret: "secret",
|
|
234
|
+
scope: "external",
|
|
235
|
+
},
|
|
236
|
+
requiredScopes: ["credentials"],
|
|
237
|
+
fetch: mockFetch,
|
|
238
|
+
});
|
|
239
|
+
const callArgs = mockFetch.mock.calls[0];
|
|
240
|
+
const body = callArgs[1].body;
|
|
241
|
+
// Scopes should be merged and sorted alphabetically
|
|
242
|
+
expect(body.get("scope")).toBe("credentials external");
|
|
243
|
+
});
|
|
244
|
+
it("should always include external scope with requiredScopes", async () => {
|
|
245
|
+
mockFetch.mockResolvedValue({
|
|
246
|
+
ok: true,
|
|
247
|
+
json: () => Promise.resolve({
|
|
248
|
+
access_token: "required-scope-token",
|
|
249
|
+
expires_in: 3600,
|
|
250
|
+
}),
|
|
251
|
+
});
|
|
252
|
+
await auth.resolveAuthToken({
|
|
253
|
+
credentials: {
|
|
254
|
+
type: "client_credentials",
|
|
255
|
+
clientId: "required-scope-client",
|
|
256
|
+
clientSecret: "secret",
|
|
257
|
+
},
|
|
258
|
+
requiredScopes: ["credentials"],
|
|
259
|
+
fetch: mockFetch,
|
|
260
|
+
});
|
|
261
|
+
const callArgs = mockFetch.mock.calls[0];
|
|
262
|
+
const body = callArgs[1].body;
|
|
263
|
+
// Should include both external (always present) and credentials (required)
|
|
264
|
+
expect(body.get("scope")).toBe("credentials external");
|
|
265
|
+
});
|
|
266
|
+
it("should cache tokens separately for different scope combinations", async () => {
|
|
267
|
+
mockFetch.mockResolvedValue({
|
|
268
|
+
ok: true,
|
|
269
|
+
json: () => Promise.resolve({
|
|
270
|
+
access_token: "first-scope-token",
|
|
271
|
+
expires_in: 3600,
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
// First call with no requiredScopes
|
|
275
|
+
const result1 = await auth.resolveAuthToken({
|
|
276
|
+
credentials: {
|
|
277
|
+
type: "client_credentials",
|
|
278
|
+
clientId: "scope-cache-client",
|
|
279
|
+
clientSecret: "secret",
|
|
280
|
+
},
|
|
281
|
+
fetch: mockFetch,
|
|
282
|
+
});
|
|
283
|
+
expect(result1).toBe("first-scope-token");
|
|
284
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
285
|
+
// Second call with different requiredScopes - should NOT use cache
|
|
286
|
+
mockFetch.mockResolvedValue({
|
|
287
|
+
ok: true,
|
|
288
|
+
json: () => Promise.resolve({
|
|
289
|
+
access_token: "second-scope-token",
|
|
290
|
+
expires_in: 3600,
|
|
291
|
+
}),
|
|
292
|
+
});
|
|
293
|
+
const result2 = await auth.resolveAuthToken({
|
|
294
|
+
credentials: {
|
|
295
|
+
type: "client_credentials",
|
|
296
|
+
clientId: "scope-cache-client",
|
|
297
|
+
clientSecret: "secret",
|
|
298
|
+
},
|
|
299
|
+
requiredScopes: ["credentials"],
|
|
300
|
+
fetch: mockFetch,
|
|
301
|
+
});
|
|
302
|
+
expect(result2).toBe("second-scope-token");
|
|
303
|
+
expect(mockFetch).toHaveBeenCalledTimes(2); // Should have made a new request
|
|
304
|
+
// Third call with same requiredScopes as second - should use cache
|
|
305
|
+
const result3 = await auth.resolveAuthToken({
|
|
306
|
+
credentials: {
|
|
307
|
+
type: "client_credentials",
|
|
308
|
+
clientId: "scope-cache-client",
|
|
309
|
+
clientSecret: "secret",
|
|
310
|
+
},
|
|
311
|
+
requiredScopes: ["credentials"],
|
|
312
|
+
fetch: mockFetch,
|
|
313
|
+
});
|
|
314
|
+
expect(result3).toBe("second-scope-token");
|
|
315
|
+
expect(mockFetch).toHaveBeenCalledTimes(2); // No new request
|
|
316
|
+
});
|
|
221
317
|
});
|
|
222
318
|
describe("PKCE credentials", () => {
|
|
223
319
|
it("should delegate PKCE credentials to CLI login", async () => {
|
|
@@ -260,8 +356,8 @@ describe("auth", () => {
|
|
|
260
356
|
fetch: mockFetch,
|
|
261
357
|
});
|
|
262
358
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
263
|
-
// Invalidate the cache
|
|
264
|
-
auth.invalidateCachedToken("invalidate-test");
|
|
359
|
+
// Invalidate the cache (default scope is "external")
|
|
360
|
+
auth.invalidateCachedToken("invalidate-test", ["external"]);
|
|
265
361
|
// Next call should fetch again
|
|
266
362
|
mockFetch.mockResolvedValue({
|
|
267
363
|
ok: true,
|