@zapier/zapier-sdk 0.18.4 → 1.0.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 +11 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +11 -6
- package/dist/api/client.test.js +82 -27
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -3
- package/dist/api/schemas.d.ts +5 -5
- package/dist/api/types.d.ts +8 -3
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth.d.ts +54 -26
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +211 -39
- package/dist/auth.test.js +338 -64
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +14 -0
- package/dist/credentials.d.ts +57 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +174 -0
- package/dist/index.cjs +341 -46
- package/dist/index.d.mts +213 -29
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.mjs +321 -45
- package/dist/plugins/api/index.d.ts +2 -0
- package/dist/plugins/api/index.d.ts.map +1 -1
- package/dist/plugins/api/index.js +8 -4
- package/dist/plugins/eventEmission/index.d.ts.map +1 -1
- package/dist/plugins/eventEmission/index.js +1 -3
- package/dist/plugins/eventEmission/index.test.js +14 -17
- package/dist/plugins/getAction/schemas.d.ts +1 -1
- package/dist/plugins/getInputFieldsSchema/schemas.d.ts +1 -1
- package/dist/plugins/listActions/index.test.js +1 -0
- package/dist/plugins/listActions/schemas.d.ts +1 -1
- package/dist/plugins/listInputFieldChoices/schemas.d.ts +1 -1
- package/dist/plugins/listInputFields/schemas.d.ts +1 -1
- package/dist/plugins/runAction/schemas.d.ts +1 -1
- package/dist/schemas/Action.d.ts +1 -1
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.test.js +5 -4
- package/dist/types/credentials.d.ts +65 -0
- package/dist/types/credentials.d.ts.map +1 -0
- package/dist/types/credentials.js +42 -0
- package/dist/types/properties.d.ts +1 -1
- package/dist/types/sdk.d.ts +12 -3
- package/dist/types/sdk.d.ts.map +1 -1
- package/dist/utils/logging.d.ts +13 -0
- package/dist/utils/logging.d.ts.map +1 -0
- package/dist/utils/logging.js +20 -0
- package/package.json +2 -2
package/dist/auth.js
CHANGED
|
@@ -2,15 +2,129 @@
|
|
|
2
2
|
* SDK Authentication Utilities
|
|
3
3
|
*
|
|
4
4
|
* This module provides SDK-level authentication utilities focused
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* on token acquisition. It uses the credentials system for resolution
|
|
6
|
+
* and handles different credential types appropriately.
|
|
7
|
+
*
|
|
8
|
+
* CLI-specific functionality like login/logout is handled by the
|
|
9
|
+
* @zapier/zapier-sdk-cli-login package.
|
|
10
|
+
*/
|
|
11
|
+
import { isClientCredentials, isPkceCredentials } from "./types/credentials";
|
|
12
|
+
import { resolveCredentials, getClientIdFromCredentials } from "./credentials";
|
|
13
|
+
import { ZAPIER_BASE_URL } from "./constants";
|
|
14
|
+
export { isClientCredentials, isPkceCredentials, isCredentialsObject, isCredentialsFunction, } from "./types/credentials";
|
|
15
|
+
/**
|
|
16
|
+
* In-memory cache for tokens obtained via client credentials flow.
|
|
17
|
+
* Keyed by clientId.
|
|
18
|
+
*/
|
|
19
|
+
const tokenCache = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* In-flight token exchange promises to prevent duplicate exchanges.
|
|
22
|
+
* When an exchange is in progress, subsequent calls await the same promise.
|
|
23
|
+
*/
|
|
24
|
+
const pendingExchanges = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Clear the token cache. Useful for testing or forcing re-authentication.
|
|
27
|
+
*/
|
|
28
|
+
export function clearTokenCache() {
|
|
29
|
+
tokenCache.clear();
|
|
30
|
+
pendingExchanges.clear();
|
|
31
|
+
}
|
|
32
|
+
const TOKEN_EXPIRATION_BUFFER = 5 * 60 * 1000; // 5 minutes
|
|
33
|
+
/**
|
|
34
|
+
* Get a cached token if it exists and is not expired.
|
|
35
|
+
* Returns undefined if no valid cached token exists.
|
|
7
36
|
*/
|
|
37
|
+
function getCachedToken(clientId) {
|
|
38
|
+
const cached = tokenCache.get(clientId);
|
|
39
|
+
if (!cached)
|
|
40
|
+
return undefined;
|
|
41
|
+
// Check if token is still valid (with expiration buffer)
|
|
42
|
+
if (cached.expiresAt > Date.now() + TOKEN_EXPIRATION_BUFFER) {
|
|
43
|
+
return cached.accessToken;
|
|
44
|
+
}
|
|
45
|
+
// Token expired, remove from cache
|
|
46
|
+
tokenCache.delete(clientId);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Cache a token obtained from client credentials flow.
|
|
51
|
+
*/
|
|
52
|
+
function cacheToken(clientId, accessToken, expiresIn) {
|
|
53
|
+
tokenCache.set(clientId, {
|
|
54
|
+
accessToken,
|
|
55
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Invalidate a cached token. Called when we get a 401 response.
|
|
60
|
+
*/
|
|
61
|
+
export function invalidateCachedToken(clientId) {
|
|
62
|
+
tokenCache.delete(clientId);
|
|
63
|
+
}
|
|
8
64
|
/**
|
|
9
|
-
*
|
|
10
|
-
* Returns undefined if not set.
|
|
65
|
+
* Get the token endpoint URL for client credentials exchange.
|
|
11
66
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
67
|
+
function getTokenEndpointUrl(baseUrl) {
|
|
68
|
+
const base = baseUrl || ZAPIER_BASE_URL;
|
|
69
|
+
return `${base}/oauth/token/`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Exchange client credentials for an access token.
|
|
73
|
+
*/
|
|
74
|
+
async function exchangeClientCredentials(options) {
|
|
75
|
+
const { clientId, clientSecret, baseUrl, scope, onEvent } = options;
|
|
76
|
+
const fetchFn = options.fetch || globalThis.fetch;
|
|
77
|
+
const tokenUrl = getTokenEndpointUrl(baseUrl);
|
|
78
|
+
onEvent?.({
|
|
79
|
+
type: "auth_exchanging",
|
|
80
|
+
payload: {
|
|
81
|
+
message: "Exchanging client credentials for token...",
|
|
82
|
+
operation: "client_credentials",
|
|
83
|
+
},
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
});
|
|
86
|
+
const response = await fetchFn(tokenUrl, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
90
|
+
},
|
|
91
|
+
body: new URLSearchParams({
|
|
92
|
+
grant_type: "client_credentials",
|
|
93
|
+
client_id: clientId,
|
|
94
|
+
client_secret: clientSecret,
|
|
95
|
+
scope: scope || "external",
|
|
96
|
+
audience: "zapier.com",
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const errorText = await response.text();
|
|
101
|
+
onEvent?.({
|
|
102
|
+
type: "auth_error",
|
|
103
|
+
payload: {
|
|
104
|
+
message: `Client credentials exchange failed: ${response.status}`,
|
|
105
|
+
error: errorText,
|
|
106
|
+
operation: "client_credentials",
|
|
107
|
+
},
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
});
|
|
110
|
+
throw new Error(`Client credentials exchange failed: ${response.status} ${response.statusText}`);
|
|
111
|
+
}
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
if (!data.access_token) {
|
|
114
|
+
throw new Error("Client credentials response missing access_token");
|
|
115
|
+
}
|
|
116
|
+
// Cache the token
|
|
117
|
+
const expiresIn = data.expires_in || 3600; // Default to 1 hour
|
|
118
|
+
cacheToken(clientId, data.access_token, expiresIn);
|
|
119
|
+
onEvent?.({
|
|
120
|
+
type: "auth_success",
|
|
121
|
+
payload: {
|
|
122
|
+
message: "Client credentials exchange successful",
|
|
123
|
+
operation: "client_credentials",
|
|
124
|
+
},
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
});
|
|
127
|
+
return data.access_token;
|
|
14
128
|
}
|
|
15
129
|
/**
|
|
16
130
|
* Attempts to get a token by optionally importing from CLI login package.
|
|
@@ -18,11 +132,13 @@ export function getTokenFromEnv() {
|
|
|
18
132
|
*
|
|
19
133
|
* Returns undefined if no valid token is found or CLI package is not available.
|
|
20
134
|
*/
|
|
21
|
-
export async function getTokenFromCliLogin(options
|
|
135
|
+
export async function getTokenFromCliLogin(options) {
|
|
22
136
|
try {
|
|
23
|
-
// Dynamically import the CLI login package if available
|
|
24
|
-
|
|
25
|
-
|
|
137
|
+
// Dynamically import the CLI login package if available.
|
|
138
|
+
// This is intentionally an async import because this module is a dependency
|
|
139
|
+
// of the CLI and is only available when the CLI is installed!
|
|
140
|
+
const cliLogin = await import("@zapier/zapier-sdk-cli-login");
|
|
141
|
+
return await cliLogin.getToken(options);
|
|
26
142
|
}
|
|
27
143
|
catch {
|
|
28
144
|
// CLI login package is not available, return undefined
|
|
@@ -30,43 +146,99 @@ export async function getTokenFromCliLogin(options = {}) {
|
|
|
30
146
|
}
|
|
31
147
|
}
|
|
32
148
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
149
|
+
* Resolves an auth token from wherever it can be found.
|
|
150
|
+
*
|
|
151
|
+
* Resolution order:
|
|
152
|
+
* 1. Explicit credentials option (or deprecated token option)
|
|
153
|
+
* 2. Environment variables
|
|
154
|
+
* 3. CLI login package (if available)
|
|
155
|
+
*
|
|
156
|
+
* For different credential types:
|
|
157
|
+
* - String: Used directly as the token
|
|
158
|
+
* - Client credentials: Exchanged for an access token (with caching)
|
|
159
|
+
* - PKCE: Delegates to CLI login (throws if not available)
|
|
36
160
|
*
|
|
37
161
|
* Returns undefined if no valid token is found.
|
|
38
162
|
*/
|
|
39
|
-
export async function
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
163
|
+
export async function resolveAuthToken(options = {}) {
|
|
164
|
+
// Resolve credentials from options and env vars
|
|
165
|
+
const credentials = await resolveCredentials({
|
|
166
|
+
credentials: options.credentials,
|
|
167
|
+
token: options.token,
|
|
168
|
+
});
|
|
169
|
+
// If we got credentials, process them based on type
|
|
170
|
+
if (credentials !== undefined) {
|
|
171
|
+
return resolveAuthTokenFromCredentials(credentials, options);
|
|
44
172
|
}
|
|
45
|
-
//
|
|
46
|
-
return getTokenFromCliLogin(
|
|
173
|
+
// No credentials from options or env, try CLI login for stored token
|
|
174
|
+
return getTokenFromCliLogin({
|
|
175
|
+
onEvent: options.onEvent,
|
|
176
|
+
fetch: options.fetch,
|
|
177
|
+
});
|
|
47
178
|
}
|
|
48
179
|
/**
|
|
49
|
-
*
|
|
50
|
-
* 1. Explicitly provided token in options
|
|
51
|
-
* 2. Token from getToken callback in options
|
|
52
|
-
* 3. ZAPIER_TOKEN environment variable
|
|
53
|
-
* 4. CLI login package (if available)
|
|
54
|
-
*
|
|
55
|
-
* This is the canonical token resolution logic used throughout the SDK.
|
|
56
|
-
* Returns undefined if no valid token is found.
|
|
180
|
+
* Resolve an auth token from resolved credentials.
|
|
57
181
|
*/
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
182
|
+
async function resolveAuthTokenFromCredentials(credentials, options) {
|
|
183
|
+
// String credentials are used directly as tokens
|
|
184
|
+
if (typeof credentials === "string") {
|
|
185
|
+
return credentials;
|
|
186
|
+
}
|
|
187
|
+
// Client credentials: exchange for token
|
|
188
|
+
if (isClientCredentials(credentials)) {
|
|
189
|
+
const { clientId } = credentials;
|
|
190
|
+
// Check cache first
|
|
191
|
+
const cached = getCachedToken(clientId);
|
|
192
|
+
if (cached) {
|
|
193
|
+
return cached;
|
|
194
|
+
}
|
|
195
|
+
// Check if there's already an exchange in progress for this clientId
|
|
196
|
+
const pending = pendingExchanges.get(clientId);
|
|
197
|
+
if (pending) {
|
|
198
|
+
return pending;
|
|
199
|
+
}
|
|
200
|
+
// Start new exchange and cache the promise to prevent duplicate exchanges
|
|
201
|
+
const exchangePromise = exchangeClientCredentials({
|
|
202
|
+
clientId: credentials.clientId,
|
|
203
|
+
clientSecret: credentials.clientSecret,
|
|
204
|
+
baseUrl: credentials.baseUrl || options.baseUrl,
|
|
205
|
+
scope: credentials.scope,
|
|
206
|
+
fetch: options.fetch,
|
|
207
|
+
onEvent: options.onEvent,
|
|
208
|
+
}).finally(() => {
|
|
209
|
+
// Remove from pending when done (success or failure)
|
|
210
|
+
pendingExchanges.delete(clientId);
|
|
211
|
+
});
|
|
212
|
+
pendingExchanges.set(clientId, exchangePromise);
|
|
213
|
+
return exchangePromise;
|
|
62
214
|
}
|
|
63
|
-
//
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
215
|
+
// PKCE credentials: delegate to CLI login
|
|
216
|
+
if (isPkceCredentials(credentials)) {
|
|
217
|
+
// Try to get stored token from CLI login
|
|
218
|
+
const storedToken = await getTokenFromCliLogin({
|
|
219
|
+
onEvent: options.onEvent,
|
|
220
|
+
fetch: options.fetch,
|
|
221
|
+
credentials,
|
|
222
|
+
});
|
|
223
|
+
if (storedToken) {
|
|
224
|
+
return storedToken;
|
|
68
225
|
}
|
|
226
|
+
// No stored token - user needs to run login
|
|
227
|
+
throw new Error("PKCE credentials require interactive login. " +
|
|
228
|
+
"Please run the 'login' command with the CLI first, or use client_credentials flow.");
|
|
229
|
+
}
|
|
230
|
+
// Should not reach here, but handle gracefully
|
|
231
|
+
throw new Error("Unknown credentials type");
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Invalidate the cached token for the given credentials, if applicable.
|
|
235
|
+
* This is called when we receive a 401 response, indicating the token
|
|
236
|
+
* is no longer valid. Only affects client_credentials flow tokens.
|
|
237
|
+
*/
|
|
238
|
+
export async function invalidateCredentialsToken(options) {
|
|
239
|
+
const resolved = await resolveCredentials(options);
|
|
240
|
+
const clientId = getClientIdFromCredentials(resolved);
|
|
241
|
+
if (clientId) {
|
|
242
|
+
invalidateCachedToken(clientId);
|
|
69
243
|
}
|
|
70
|
-
// Third and fourth priorities: environment variable or CLI login
|
|
71
|
-
return getTokenFromEnvOrConfig(options);
|
|
72
244
|
}
|