@zapier/zapier-sdk-cli 0.46.1 → 0.48.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 +20 -0
- package/README.md +1 -1
- package/dist/cli.cjs +569 -84
- package/dist/cli.mjs +569 -84
- package/dist/experimental.cjs +561 -86
- package/dist/experimental.mjs +560 -85
- package/dist/index.cjs +562 -87
- package/dist/index.mjs +561 -86
- package/dist/login.cjs +94 -25
- package/dist/login.d.mts +29 -2
- package/dist/login.d.ts +29 -2
- package/dist/login.mjs +90 -25
- package/dist/package.json +1 -1
- package/dist/src/login/config.d.ts +4 -0
- package/dist/src/login/config.js +21 -0
- package/dist/src/login/credentials-revoke.d.ts +13 -0
- package/dist/src/login/credentials-revoke.js +48 -0
- package/dist/src/login/credentials-store.d.ts +33 -0
- package/dist/src/login/credentials-store.js +142 -0
- package/dist/src/login/index.d.ts +5 -2
- package/dist/src/login/index.js +11 -27
- package/dist/src/login/legacy-jwt.d.ts +4 -0
- package/dist/src/login/legacy-jwt.js +18 -0
- package/dist/src/plugins/auth/credentials-base-url.d.ts +11 -0
- package/dist/src/plugins/auth/credentials-base-url.js +24 -0
- package/dist/src/plugins/login/index.d.ts +6 -1
- package/dist/src/plugins/login/index.js +154 -14
- package/dist/src/plugins/logout/index.d.ts +14 -0
- package/dist/src/plugins/logout/index.js +35 -3
- package/dist/src/utils/auth/client-credentials.d.ts +16 -0
- package/dist/src/utils/auth/client-credentials.js +53 -0
- package/dist/src/utils/auth/oauth-flow.d.ts +12 -0
- package/dist/src/utils/auth/{login.js → oauth-flow.js} +36 -58
- package/dist/src/utils/parameter-resolver.js +13 -0
- package/dist/src/utils/retry.d.ts +5 -0
- package/dist/src/utils/retry.js +21 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/src/utils/auth/login.d.ts +0 -7
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import * as jwt from 'jsonwebtoken';
|
|
2
|
+
import { deletePassword, getKeyring, getPassword, setPassword } from 'cross-keychain';
|
|
1
3
|
import Conf from 'conf';
|
|
2
4
|
import * as fs from 'fs';
|
|
3
5
|
import { promises, createWriteStream, existsSync, readdirSync, rmSync, mkdirSync, writeFileSync, copyFileSync, readFileSync } from 'fs';
|
|
4
|
-
import * as jwt from 'jsonwebtoken';
|
|
5
|
-
import { getPassword, getKeyring, setPassword, deletePassword } from 'cross-keychain';
|
|
6
6
|
import crypto, { createHash } from 'crypto';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { resolve, join, dirname, basename, relative, extname } from 'path';
|
|
9
9
|
import * as lockfile from 'proper-lockfile';
|
|
10
|
-
import {
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { definePlugin, createPluginMethod, getOrCreateApiClient, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, isCredentialsObject, invalidateCachedToken, buildApplicationLifecycleEvent, batch, toSnakeCase, ZapierAbortDrainSignal, ZapierError, createZapierSdk, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
|
|
12
|
+
import { hostname } from 'os';
|
|
13
|
+
import inquirer from 'inquirer';
|
|
11
14
|
import open from 'open';
|
|
12
15
|
import express from 'express';
|
|
13
16
|
import pkceChallenge from 'pkce-challenge';
|
|
14
17
|
import ora from 'ora';
|
|
15
18
|
import chalk3 from 'chalk';
|
|
16
|
-
import inquirer from 'inquirer';
|
|
17
|
-
import { z } from 'zod';
|
|
18
19
|
import { startMcpServer } from '@zapier/zapier-sdk-mcp';
|
|
19
20
|
import { buildSync } from 'esbuild';
|
|
20
21
|
import { mkdir, writeFile, access } from 'fs/promises';
|
|
@@ -35,8 +36,11 @@ var __export = (target, all) => {
|
|
|
35
36
|
var login_exports = {};
|
|
36
37
|
__export(login_exports, {
|
|
37
38
|
AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
|
|
39
|
+
DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
|
|
38
40
|
ZapierAuthenticationError: () => ZapierAuthenticationError,
|
|
41
|
+
clearTokensFromKeychain: () => clearTokensFromKeychain,
|
|
39
42
|
createCache: () => createCache,
|
|
43
|
+
getActiveCredentials: () => getActiveCredentials,
|
|
40
44
|
getAuthAuthorizeUrl: () => getAuthAuthorizeUrl,
|
|
41
45
|
getAuthTokenUrl: () => getAuthTokenUrl,
|
|
42
46
|
getConfig: () => getConfig,
|
|
@@ -44,6 +48,7 @@ __export(login_exports, {
|
|
|
44
48
|
getLoggedInUser: () => getLoggedInUser,
|
|
45
49
|
getLoginStorageMode: () => getLoginStorageMode,
|
|
46
50
|
getPkceLoginConfig: () => getPkceLoginConfig,
|
|
51
|
+
getStoredClientCredentials: () => getStoredClientCredentials,
|
|
47
52
|
getToken: () => getToken,
|
|
48
53
|
logout: () => logout,
|
|
49
54
|
unloadConfig: () => unloadConfig,
|
|
@@ -124,6 +129,38 @@ async function clearTokensFromKeychain({
|
|
|
124
129
|
}
|
|
125
130
|
});
|
|
126
131
|
}
|
|
132
|
+
var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
|
|
133
|
+
var config = null;
|
|
134
|
+
function getConfig() {
|
|
135
|
+
if (!config) {
|
|
136
|
+
config = new Conf({ projectName: "zapier-sdk-cli" });
|
|
137
|
+
if (!config.has("login_storage_mode")) {
|
|
138
|
+
config.set(
|
|
139
|
+
"login_storage_mode",
|
|
140
|
+
existsSync(config.path) ? "config" : "keychain"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return config;
|
|
145
|
+
}
|
|
146
|
+
function resetConfig() {
|
|
147
|
+
config = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/login/legacy-jwt.ts
|
|
151
|
+
function clearLegacyJwtConfigKeys(config2) {
|
|
152
|
+
config2.delete("login_jwt");
|
|
153
|
+
config2.delete("login_refresh_token");
|
|
154
|
+
config2.delete("login_expires_at");
|
|
155
|
+
}
|
|
156
|
+
async function clearLegacyJwtState() {
|
|
157
|
+
clearLegacyJwtConfigKeys(getConfig());
|
|
158
|
+
await clearTokensFromKeychain();
|
|
159
|
+
}
|
|
160
|
+
function hasLegacyJwtConfig() {
|
|
161
|
+
const cfg = getConfig();
|
|
162
|
+
return typeof cfg.get("login_jwt") === "string" || typeof cfg.get("login_refresh_token") === "string" || typeof cfg.get("login_expires_at") === "number";
|
|
163
|
+
}
|
|
127
164
|
var SERVICE2 = "zapier-sdk-cache";
|
|
128
165
|
var CONFIG_KEY = "cache";
|
|
129
166
|
var LOCK_UPDATE_MS = 5e3;
|
|
@@ -251,6 +288,163 @@ function createCache() {
|
|
|
251
288
|
}
|
|
252
289
|
};
|
|
253
290
|
}
|
|
291
|
+
var SERVICE3 = "zapier-sdk-cli";
|
|
292
|
+
var CREDENTIALS_KEY = "credentials";
|
|
293
|
+
var REGISTRY_KEY = "credentialsRegistry";
|
|
294
|
+
var CredentialsEntrySchema = z.object({
|
|
295
|
+
name: z.string(),
|
|
296
|
+
clientId: z.string(),
|
|
297
|
+
createdAt: z.number(),
|
|
298
|
+
scopes: z.array(z.string()),
|
|
299
|
+
baseUrl: z.string()
|
|
300
|
+
});
|
|
301
|
+
function normalizeBaseUrl(baseUrl) {
|
|
302
|
+
return baseUrl ?? DEFAULT_AUTH_BASE_URL;
|
|
303
|
+
}
|
|
304
|
+
function keychainAccount2(key) {
|
|
305
|
+
return createHash("sha256").update(key).digest("hex");
|
|
306
|
+
}
|
|
307
|
+
function buildKeychainKey(clientId, scopes, baseUrl) {
|
|
308
|
+
const sortedScopes = [...scopes].sort().join(",");
|
|
309
|
+
return `zapier-sdk/client-credentials-secret/${clientId}:${sortedScopes}:${baseUrl}`;
|
|
310
|
+
}
|
|
311
|
+
function findEntry(registry, name, baseUrl) {
|
|
312
|
+
return registry.find((e) => e.name === name && e.baseUrl === baseUrl);
|
|
313
|
+
}
|
|
314
|
+
function readRegistry() {
|
|
315
|
+
const stored = getConfig().get(REGISTRY_KEY);
|
|
316
|
+
if (!Array.isArray(stored)) return [];
|
|
317
|
+
return stored.flatMap((entry) => {
|
|
318
|
+
const result = CredentialsEntrySchema.safeParse(entry);
|
|
319
|
+
return result.success ? [result.data] : [];
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function getActiveCredentials(options) {
|
|
323
|
+
const name = getConfig().get(CREDENTIALS_KEY);
|
|
324
|
+
if (!name) return void 0;
|
|
325
|
+
return findEntry(readRegistry(), name, normalizeBaseUrl(options?.baseUrl));
|
|
326
|
+
}
|
|
327
|
+
async function storeClientCredentials({
|
|
328
|
+
name,
|
|
329
|
+
clientId,
|
|
330
|
+
clientSecret,
|
|
331
|
+
scopes,
|
|
332
|
+
baseUrl
|
|
333
|
+
}) {
|
|
334
|
+
if (!name || typeof name !== "string") {
|
|
335
|
+
throw new Error("storeClientCredentials: name is required");
|
|
336
|
+
}
|
|
337
|
+
if (!clientId || typeof clientId !== "string") {
|
|
338
|
+
throw new Error("storeClientCredentials: clientId is required");
|
|
339
|
+
}
|
|
340
|
+
if (!clientSecret || typeof clientSecret !== "string") {
|
|
341
|
+
throw new Error("storeClientCredentials: clientSecret is required");
|
|
342
|
+
}
|
|
343
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
344
|
+
throw new Error("storeClientCredentials: scopes must be a non-empty array");
|
|
345
|
+
}
|
|
346
|
+
const sortedScopes = [...scopes].sort();
|
|
347
|
+
const resolvedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
348
|
+
const keychainKey = buildKeychainKey(clientId, sortedScopes, resolvedBaseUrl);
|
|
349
|
+
const existingEntry = findEntry(readRegistry(), name, resolvedBaseUrl);
|
|
350
|
+
const existingKeychainKey = existingEntry ? buildKeychainKey(
|
|
351
|
+
existingEntry.clientId,
|
|
352
|
+
existingEntry.scopes,
|
|
353
|
+
existingEntry.baseUrl
|
|
354
|
+
) : void 0;
|
|
355
|
+
await enqueue(async () => {
|
|
356
|
+
await getBackendInfo();
|
|
357
|
+
await setPassword(SERVICE3, keychainAccount2(keychainKey), clientSecret);
|
|
358
|
+
});
|
|
359
|
+
const entry = {
|
|
360
|
+
name,
|
|
361
|
+
clientId,
|
|
362
|
+
createdAt: Date.now(),
|
|
363
|
+
scopes: sortedScopes,
|
|
364
|
+
baseUrl: resolvedBaseUrl
|
|
365
|
+
};
|
|
366
|
+
const registry = readRegistry().filter(
|
|
367
|
+
(e) => !(e.name === name && e.baseUrl === resolvedBaseUrl)
|
|
368
|
+
);
|
|
369
|
+
registry.push(entry);
|
|
370
|
+
const cfg = getConfig();
|
|
371
|
+
cfg.set(REGISTRY_KEY, registry);
|
|
372
|
+
cfg.set(CREDENTIALS_KEY, name);
|
|
373
|
+
if (existingEntry && existingKeychainKey !== keychainKey) {
|
|
374
|
+
await deleteKeychainSecret(existingEntry);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function credentialsNameExists({
|
|
378
|
+
name,
|
|
379
|
+
baseUrl
|
|
380
|
+
}) {
|
|
381
|
+
return !!findEntry(readRegistry(), name, normalizeBaseUrl(baseUrl));
|
|
382
|
+
}
|
|
383
|
+
async function getStoredClientCredentials(options) {
|
|
384
|
+
const entry = options?.name ? findEntry(readRegistry(), options.name, normalizeBaseUrl(options.baseUrl)) : getActiveCredentials(options);
|
|
385
|
+
if (!entry) return void 0;
|
|
386
|
+
const keychainKey = buildKeychainKey(
|
|
387
|
+
entry.clientId,
|
|
388
|
+
entry.scopes,
|
|
389
|
+
entry.baseUrl
|
|
390
|
+
);
|
|
391
|
+
const clientSecret = await enqueue(async () => {
|
|
392
|
+
await getBackendInfo();
|
|
393
|
+
return getPassword(SERVICE3, keychainAccount2(keychainKey));
|
|
394
|
+
});
|
|
395
|
+
if (!clientSecret) return void 0;
|
|
396
|
+
return {
|
|
397
|
+
type: "client_credentials",
|
|
398
|
+
clientId: entry.clientId,
|
|
399
|
+
clientSecret,
|
|
400
|
+
baseUrl: entry.baseUrl,
|
|
401
|
+
scope: [...entry.scopes].sort().join(" ")
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function deleteRegistryEntry(registry, name, baseUrl) {
|
|
405
|
+
const idx = registry.findIndex(
|
|
406
|
+
(e) => e.name === name && e.baseUrl === baseUrl
|
|
407
|
+
);
|
|
408
|
+
if (idx === -1) return void 0;
|
|
409
|
+
const [removed] = registry.splice(idx, 1);
|
|
410
|
+
return removed;
|
|
411
|
+
}
|
|
412
|
+
function unsetMatchingCredentialsKey(cfg, name) {
|
|
413
|
+
const activeName = cfg.get(CREDENTIALS_KEY);
|
|
414
|
+
if (activeName === name && !readRegistry().some((e) => e.name === name)) {
|
|
415
|
+
cfg.delete(CREDENTIALS_KEY);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function deleteKeychainSecret(entry) {
|
|
419
|
+
const keychainKey = buildKeychainKey(
|
|
420
|
+
entry.clientId,
|
|
421
|
+
entry.scopes,
|
|
422
|
+
entry.baseUrl
|
|
423
|
+
);
|
|
424
|
+
try {
|
|
425
|
+
await enqueue(async () => {
|
|
426
|
+
await getBackendInfo();
|
|
427
|
+
await deletePassword(SERVICE3, keychainAccount2(keychainKey));
|
|
428
|
+
});
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function deleteStoredClientCredentials({
|
|
433
|
+
name,
|
|
434
|
+
baseUrl
|
|
435
|
+
}) {
|
|
436
|
+
const registry = readRegistry();
|
|
437
|
+
const removed = deleteRegistryEntry(
|
|
438
|
+
registry,
|
|
439
|
+
name,
|
|
440
|
+
normalizeBaseUrl(baseUrl)
|
|
441
|
+
);
|
|
442
|
+
if (!removed) return;
|
|
443
|
+
const cfg = getConfig();
|
|
444
|
+
cfg.set(REGISTRY_KEY, registry);
|
|
445
|
+
unsetMatchingCredentialsKey(cfg, name);
|
|
446
|
+
await deleteKeychainSecret(removed);
|
|
447
|
+
}
|
|
254
448
|
|
|
255
449
|
// src/login/index.ts
|
|
256
450
|
var ZapierAuthenticationError = class extends Error {
|
|
@@ -259,7 +453,6 @@ var ZapierAuthenticationError = class extends Error {
|
|
|
259
453
|
this.name = "ZapierAuthenticationError";
|
|
260
454
|
}
|
|
261
455
|
};
|
|
262
|
-
var config = null;
|
|
263
456
|
var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
|
|
264
457
|
var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
265
458
|
function createDebugLog(enabled) {
|
|
@@ -285,7 +478,6 @@ function getAuthClientId(clientId) {
|
|
|
285
478
|
return clientId || DEFAULT_AUTH_CLIENT_ID;
|
|
286
479
|
}
|
|
287
480
|
var AUTH_MODE_HEADER = "X-Auth";
|
|
288
|
-
var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
|
|
289
481
|
function getAuthTokenUrl(options) {
|
|
290
482
|
const authBaseUrl = options?.baseUrl || DEFAULT_AUTH_BASE_URL;
|
|
291
483
|
return `${authBaseUrl}/oauth/token/`;
|
|
@@ -295,29 +487,16 @@ function getAuthAuthorizeUrl(options) {
|
|
|
295
487
|
return `${authBaseUrl}/oauth/authorize/`;
|
|
296
488
|
}
|
|
297
489
|
function getPkceLoginConfig(options) {
|
|
490
|
+
const effectiveBaseUrl = options?.credentials?.baseUrl ?? options?.baseUrl;
|
|
298
491
|
return {
|
|
299
492
|
clientId: getAuthClientId(options?.credentials?.clientId),
|
|
300
|
-
tokenUrl: getAuthTokenUrl({ baseUrl:
|
|
301
|
-
authorizeUrl: getAuthAuthorizeUrl({
|
|
302
|
-
baseUrl: options?.credentials?.baseUrl
|
|
303
|
-
})
|
|
493
|
+
tokenUrl: getAuthTokenUrl({ baseUrl: effectiveBaseUrl }),
|
|
494
|
+
authorizeUrl: getAuthAuthorizeUrl({ baseUrl: effectiveBaseUrl })
|
|
304
495
|
};
|
|
305
496
|
}
|
|
306
497
|
var cachedLogin;
|
|
307
|
-
function getConfig() {
|
|
308
|
-
if (!config) {
|
|
309
|
-
config = new Conf({ projectName: "zapier-sdk-cli" });
|
|
310
|
-
if (!config.has("login_storage_mode")) {
|
|
311
|
-
config.set(
|
|
312
|
-
"login_storage_mode",
|
|
313
|
-
existsSync(config.path) ? "config" : "keychain"
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return config;
|
|
318
|
-
}
|
|
319
498
|
function unloadConfig() {
|
|
320
|
-
|
|
499
|
+
resetConfig();
|
|
321
500
|
cachedLogin = void 0;
|
|
322
501
|
}
|
|
323
502
|
async function updateLogin(loginData, options = {}) {
|
|
@@ -590,9 +769,7 @@ async function logout(options = {}) {
|
|
|
590
769
|
await clearTokensFromKeychain();
|
|
591
770
|
const cfg = getConfig();
|
|
592
771
|
cfg.set("login_storage_mode", mode);
|
|
593
|
-
cfg
|
|
594
|
-
cfg.delete("login_jwt");
|
|
595
|
-
cfg.delete("login_refresh_token");
|
|
772
|
+
clearLegacyJwtConfigKeys(cfg);
|
|
596
773
|
onEvent?.({
|
|
597
774
|
type: "auth_logout",
|
|
598
775
|
payload: { message: "Logged out successfully", operation: "logout" },
|
|
@@ -604,6 +781,79 @@ function getConfigPath() {
|
|
|
604
781
|
return cfg.path;
|
|
605
782
|
}
|
|
606
783
|
|
|
784
|
+
// src/utils/retry.ts
|
|
785
|
+
function sleep(ms) {
|
|
786
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
787
|
+
}
|
|
788
|
+
async function withRetry({
|
|
789
|
+
action,
|
|
790
|
+
attempts = 3,
|
|
791
|
+
initialDelayMs = 100
|
|
792
|
+
}) {
|
|
793
|
+
if (attempts <= 0) {
|
|
794
|
+
throw new Error("withRetry: attempts must be greater than 0");
|
|
795
|
+
}
|
|
796
|
+
let lastError;
|
|
797
|
+
for (let i = 0; i < attempts; i++) {
|
|
798
|
+
try {
|
|
799
|
+
return await action();
|
|
800
|
+
} catch (err) {
|
|
801
|
+
lastError = err;
|
|
802
|
+
if (i < attempts - 1) {
|
|
803
|
+
await sleep(initialDelayMs * 2 ** i);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
throw lastError;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/login/credentials-revoke.ts
|
|
811
|
+
function emitAuthLogout(onEvent) {
|
|
812
|
+
onEvent?.({
|
|
813
|
+
type: "auth_logout",
|
|
814
|
+
payload: { message: "Logged out successfully", operation: "logout" },
|
|
815
|
+
timestamp: Date.now()
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
function isNotFoundError(err) {
|
|
819
|
+
return typeof err === "object" && err !== null && "statusCode" in err && err.statusCode === 404;
|
|
820
|
+
}
|
|
821
|
+
async function revokeCredentials({
|
|
822
|
+
api: api2,
|
|
823
|
+
credentials,
|
|
824
|
+
onEvent
|
|
825
|
+
}) {
|
|
826
|
+
await withRetry({
|
|
827
|
+
action: async () => {
|
|
828
|
+
try {
|
|
829
|
+
await api2.delete(
|
|
830
|
+
`/api/v0/client-credentials/${credentials.clientId}`,
|
|
831
|
+
void 0,
|
|
832
|
+
{ authRequired: true, requiredScopes: ["credentials"] }
|
|
833
|
+
);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
if (isNotFoundError(err)) return;
|
|
836
|
+
throw err;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
try {
|
|
841
|
+
await deleteStoredClientCredentials({
|
|
842
|
+
name: credentials.name,
|
|
843
|
+
baseUrl: credentials.baseUrl
|
|
844
|
+
});
|
|
845
|
+
} catch (err) {
|
|
846
|
+
console.warn("[revokeCredentials] Local store cleanup failed:", err);
|
|
847
|
+
}
|
|
848
|
+
await clearLegacyJwtState();
|
|
849
|
+
await invalidateCachedToken({
|
|
850
|
+
clientId: credentials.clientId,
|
|
851
|
+
scopes: credentials.scopes,
|
|
852
|
+
baseUrl: credentials.baseUrl
|
|
853
|
+
});
|
|
854
|
+
emitAuthLogout(onEvent);
|
|
855
|
+
}
|
|
856
|
+
|
|
607
857
|
// src/utils/constants.ts
|
|
608
858
|
var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
|
|
609
859
|
var LOGIN_TIMEOUT_MS = 3e5;
|
|
@@ -717,6 +967,8 @@ var getCallablePromise = () => {
|
|
|
717
967
|
};
|
|
718
968
|
};
|
|
719
969
|
var getCallablePromise_default = getCallablePromise;
|
|
970
|
+
|
|
971
|
+
// src/utils/auth/oauth-flow.ts
|
|
720
972
|
var findAvailablePort = () => {
|
|
721
973
|
return new Promise((resolve4, reject) => {
|
|
722
974
|
let portIndex = 0;
|
|
@@ -751,10 +1003,9 @@ var findAvailablePort = () => {
|
|
|
751
1003
|
var generateRandomString = () => {
|
|
752
1004
|
const array = new Uint32Array(28);
|
|
753
1005
|
crypto.getRandomValues(array);
|
|
754
|
-
return Array.from(
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
).join("");
|
|
1006
|
+
return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
|
|
1007
|
+
""
|
|
1008
|
+
);
|
|
758
1009
|
};
|
|
759
1010
|
function ensureOfflineAccess(scope) {
|
|
760
1011
|
if (scope.includes("offline_access")) {
|
|
@@ -762,17 +1013,18 @@ function ensureOfflineAccess(scope) {
|
|
|
762
1013
|
}
|
|
763
1014
|
return `${scope} offline_access`;
|
|
764
1015
|
}
|
|
765
|
-
|
|
1016
|
+
async function runOauthFlow({
|
|
766
1017
|
timeoutMs = LOGIN_TIMEOUT_MS,
|
|
767
|
-
|
|
768
|
-
|
|
1018
|
+
pkceCredentials,
|
|
1019
|
+
baseUrl
|
|
1020
|
+
}) {
|
|
769
1021
|
const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
|
|
770
|
-
credentials
|
|
1022
|
+
credentials: pkceCredentials,
|
|
1023
|
+
baseUrl
|
|
771
1024
|
});
|
|
772
1025
|
const scope = ensureOfflineAccess(
|
|
773
|
-
|
|
1026
|
+
pkceCredentials?.scope || "internal credentials"
|
|
774
1027
|
);
|
|
775
|
-
await logout();
|
|
776
1028
|
const availablePort = await findAvailablePort();
|
|
777
1029
|
const redirectUri = `http://localhost:${availablePort}/oauth`;
|
|
778
1030
|
log_default.info(`Using port ${availablePort} for OAuth callback`);
|
|
@@ -781,13 +1033,30 @@ var login = async ({
|
|
|
781
1033
|
resolve: setCode,
|
|
782
1034
|
reject: rejectCode
|
|
783
1035
|
} = getCallablePromise_default();
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
1036
|
+
const oauthState = generateRandomString();
|
|
1037
|
+
const expressApp = express();
|
|
1038
|
+
expressApp.get("/oauth", (req, res) => {
|
|
787
1039
|
res.setHeader("Connection", "close");
|
|
1040
|
+
if (req.query.state !== oauthState) {
|
|
1041
|
+
rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
|
|
1042
|
+
res.status(400).end("Invalid state. You can close this tab.");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (req.query.error) {
|
|
1046
|
+
const desc = req.query.error_description ?? req.query.error;
|
|
1047
|
+
rejectCode(new Error(`Authorization denied: ${desc}`));
|
|
1048
|
+
res.end("Authorization was denied. You can close this tab.");
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (!req.query.code) {
|
|
1052
|
+
rejectCode(new Error("No authorization code received"));
|
|
1053
|
+
res.end("No authorization code received. You can close this tab.");
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
setCode(String(req.query.code));
|
|
788
1057
|
res.end("You can now close this tab and return to the CLI.");
|
|
789
1058
|
});
|
|
790
|
-
const server =
|
|
1059
|
+
const server = expressApp.listen(availablePort);
|
|
791
1060
|
const connections = /* @__PURE__ */ new Set();
|
|
792
1061
|
server.on("connection", (conn) => {
|
|
793
1062
|
connections.add(conn);
|
|
@@ -806,7 +1075,7 @@ var login = async ({
|
|
|
806
1075
|
client_id: clientId,
|
|
807
1076
|
redirect_uri: redirectUri,
|
|
808
1077
|
scope,
|
|
809
|
-
state:
|
|
1078
|
+
state: oauthState,
|
|
810
1079
|
code_challenge: codeChallenge,
|
|
811
1080
|
code_challenge_method: "S256"
|
|
812
1081
|
}).toString()}`;
|
|
@@ -865,36 +1134,79 @@ var login = async ({
|
|
|
865
1134
|
}
|
|
866
1135
|
}
|
|
867
1136
|
);
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1137
|
+
log_default.info("Token exchange completed successfully");
|
|
1138
|
+
return {
|
|
1139
|
+
accessToken: data.access_token,
|
|
1140
|
+
refreshToken: data.refresh_token,
|
|
1141
|
+
expiresIn: data.expires_in
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/utils/auth/client-credentials.ts
|
|
1146
|
+
var CREDENTIALS_SCOPES = ["external", "credentials"];
|
|
1147
|
+
async function createCredentialsOnServer(api2, name) {
|
|
1148
|
+
const response = await api2.post(
|
|
1149
|
+
"/api/v0/client-credentials",
|
|
1150
|
+
{ name, allowed_scopes: CREDENTIALS_SCOPES },
|
|
1151
|
+
{ authRequired: true, requiredScopes: ["credentials"] }
|
|
1152
|
+
);
|
|
1153
|
+
return {
|
|
1154
|
+
clientId: response.data.client_id,
|
|
1155
|
+
clientSecret: response.data.client_secret
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
async function deleteCredentialsOnServer(api2, clientId) {
|
|
1159
|
+
await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
|
|
1160
|
+
authRequired: true,
|
|
1161
|
+
requiredScopes: ["credentials"]
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
async function setupClientCredentials({
|
|
1165
|
+
api: api2,
|
|
1166
|
+
name,
|
|
1167
|
+
credentialsBaseUrl
|
|
1168
|
+
}) {
|
|
1169
|
+
const { clientId, clientSecret } = await createCredentialsOnServer(api2, name);
|
|
882
1170
|
try {
|
|
883
|
-
await
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1171
|
+
await withRetry({
|
|
1172
|
+
action: () => storeClientCredentials({
|
|
1173
|
+
name,
|
|
1174
|
+
clientId,
|
|
1175
|
+
clientSecret,
|
|
1176
|
+
scopes: [...CREDENTIALS_SCOPES],
|
|
1177
|
+
baseUrl: credentialsBaseUrl
|
|
1178
|
+
})
|
|
1179
|
+
});
|
|
1180
|
+
} catch (storeErr) {
|
|
1181
|
+
try {
|
|
1182
|
+
await withRetry({
|
|
1183
|
+
action: () => deleteCredentialsOnServer(api2, clientId)
|
|
1184
|
+
});
|
|
1185
|
+
} catch {
|
|
1186
|
+
console.error(
|
|
1187
|
+
`Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
|
|
888
1188
|
);
|
|
889
|
-
await updateLogin(data, { storage: "config" });
|
|
890
|
-
} else {
|
|
891
|
-
throw err;
|
|
892
1189
|
}
|
|
1190
|
+
throw storeErr;
|
|
893
1191
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1192
|
+
return { clientId };
|
|
1193
|
+
}
|
|
1194
|
+
function getBaseUrlFromResolvedCredentials(credentials) {
|
|
1195
|
+
if (credentials && isCredentialsObject(credentials)) {
|
|
1196
|
+
return credentials.baseUrl;
|
|
1197
|
+
}
|
|
1198
|
+
return void 0;
|
|
1199
|
+
}
|
|
1200
|
+
function getBaseUrlFromOptionsCredentials(credentials) {
|
|
1201
|
+
if (credentials && typeof credentials === "object" && "baseUrl" in credentials && typeof credentials.baseUrl === "string") {
|
|
1202
|
+
return credentials.baseUrl;
|
|
1203
|
+
}
|
|
1204
|
+
return void 0;
|
|
1205
|
+
}
|
|
1206
|
+
async function resolveCredentialsBaseUrl(context) {
|
|
1207
|
+
const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
|
|
1208
|
+
return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
|
|
1209
|
+
}
|
|
898
1210
|
var LoginSchema = z.object({
|
|
899
1211
|
timeout: z.string().optional().describe("Login timeout in seconds (default: 300)")
|
|
900
1212
|
}).describe("Log in to Zapier to access your account");
|
|
@@ -911,6 +1223,105 @@ function toPkceCredentials(credentials) {
|
|
|
911
1223
|
}
|
|
912
1224
|
return void 0;
|
|
913
1225
|
}
|
|
1226
|
+
async function confirmRevokeAndRelogin(activeCredentials) {
|
|
1227
|
+
const { confirmed } = await inquirer.prompt([
|
|
1228
|
+
{
|
|
1229
|
+
type: "confirm",
|
|
1230
|
+
name: "confirmed",
|
|
1231
|
+
message: `You are already logged in as "${activeCredentials.name}".
|
|
1232
|
+
Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
|
|
1233
|
+
Log out and log in again?`,
|
|
1234
|
+
default: false
|
|
1235
|
+
}
|
|
1236
|
+
]);
|
|
1237
|
+
if (!confirmed) {
|
|
1238
|
+
console.log("Login cancelled.");
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
async function confirmJwtMigration() {
|
|
1244
|
+
const { confirmed } = await inquirer.prompt([
|
|
1245
|
+
{
|
|
1246
|
+
type: "confirm",
|
|
1247
|
+
name: "confirmed",
|
|
1248
|
+
message: "We're upgrading your login to client credentials for a simpler, more reliable experience and to support future security controls. Older Zapier SDK/CLI versions on this machine may stop working after the upgrade. Continue?",
|
|
1249
|
+
default: true
|
|
1250
|
+
}
|
|
1251
|
+
]);
|
|
1252
|
+
if (!confirmed) {
|
|
1253
|
+
console.log("Login cancelled.");
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1258
|
+
async function confirmLocalLoginReset() {
|
|
1259
|
+
const { confirmed } = await inquirer.prompt([
|
|
1260
|
+
{
|
|
1261
|
+
type: "confirm",
|
|
1262
|
+
name: "confirmed",
|
|
1263
|
+
message: "Login cleanup failed. Reset local session state and continue?",
|
|
1264
|
+
default: false
|
|
1265
|
+
}
|
|
1266
|
+
]);
|
|
1267
|
+
if (!confirmed) {
|
|
1268
|
+
console.log("Login cancelled.");
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
return true;
|
|
1272
|
+
}
|
|
1273
|
+
function parseTimeoutSeconds(timeout) {
|
|
1274
|
+
const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
|
|
1275
|
+
if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
1276
|
+
throw new Error("Timeout must be a positive number");
|
|
1277
|
+
}
|
|
1278
|
+
return timeoutSeconds;
|
|
1279
|
+
}
|
|
1280
|
+
async function promptCredentialsName(email, baseUrl) {
|
|
1281
|
+
const { credentialName } = await inquirer.prompt([
|
|
1282
|
+
{
|
|
1283
|
+
type: "input",
|
|
1284
|
+
name: "credentialName",
|
|
1285
|
+
message: "Enter a name to identify them:",
|
|
1286
|
+
default: `${email}@${hostname()}`,
|
|
1287
|
+
validate: (input) => {
|
|
1288
|
+
if (!input.trim()) return "Name cannot be empty";
|
|
1289
|
+
if (credentialsNameExists({ name: input.trim(), baseUrl })) {
|
|
1290
|
+
return `Credentials named "${input.trim()}" already exist. Please provide a different name.`;
|
|
1291
|
+
}
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
]);
|
|
1296
|
+
return credentialName;
|
|
1297
|
+
}
|
|
1298
|
+
function emitLoginSuccess({
|
|
1299
|
+
sdk,
|
|
1300
|
+
profile
|
|
1301
|
+
}) {
|
|
1302
|
+
sdk.context.eventEmission.emit(
|
|
1303
|
+
"platform.sdk.ApplicationLifecycleEvent",
|
|
1304
|
+
buildApplicationLifecycleEvent(
|
|
1305
|
+
{ lifecycle_event_type: "login_success" },
|
|
1306
|
+
{
|
|
1307
|
+
customuser_id: profile.user_id,
|
|
1308
|
+
account_id: profile.roles[0]?.account_id ?? null
|
|
1309
|
+
}
|
|
1310
|
+
)
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
async function getProfile(api2) {
|
|
1314
|
+
return api2.get("/zapier/api/v4/profile/", {
|
|
1315
|
+
authRequired: true
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
async function bestEffortClearLegacyJwtState() {
|
|
1319
|
+
try {
|
|
1320
|
+
await clearLegacyJwtState();
|
|
1321
|
+
} catch (err) {
|
|
1322
|
+
console.error("[login] Best-effort legacy JWT cleanup failed:", err);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
914
1325
|
var loginPlugin = definePlugin(
|
|
915
1326
|
(sdk) => createPluginMethod(sdk, {
|
|
916
1327
|
name: "login",
|
|
@@ -918,25 +1329,61 @@ var loginPlugin = definePlugin(
|
|
|
918
1329
|
inputSchema: LoginSchema,
|
|
919
1330
|
supportsJsonOutput: false,
|
|
920
1331
|
handler: async ({ sdk: sdk2, options }) => {
|
|
921
|
-
const timeoutSeconds =
|
|
922
|
-
if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
923
|
-
throw new Error("Timeout must be a positive number");
|
|
924
|
-
}
|
|
1332
|
+
const timeoutSeconds = parseTimeoutSeconds(options.timeout);
|
|
925
1333
|
const resolvedCredentials = await sdk2.context.resolveCredentials();
|
|
926
1334
|
const pkceCredentials = toPkceCredentials(resolvedCredentials);
|
|
927
|
-
await
|
|
1335
|
+
const credentialsBaseUrl = await resolveCredentialsBaseUrl({
|
|
1336
|
+
...sdk2.context,
|
|
1337
|
+
resolvedCredentials
|
|
1338
|
+
});
|
|
1339
|
+
const activeCredentials = getActiveCredentials({
|
|
1340
|
+
baseUrl: credentialsBaseUrl
|
|
1341
|
+
});
|
|
1342
|
+
if (activeCredentials) {
|
|
1343
|
+
if (!await confirmRevokeAndRelogin(activeCredentials)) return;
|
|
1344
|
+
try {
|
|
1345
|
+
await revokeCredentials({
|
|
1346
|
+
api: sdk2.context.api,
|
|
1347
|
+
credentials: activeCredentials
|
|
1348
|
+
});
|
|
1349
|
+
} catch {
|
|
1350
|
+
if (!await confirmLocalLoginReset()) return;
|
|
1351
|
+
await deleteStoredClientCredentials({
|
|
1352
|
+
name: activeCredentials.name,
|
|
1353
|
+
baseUrl: activeCredentials.baseUrl
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
} else if (hasLegacyJwtConfig()) {
|
|
1357
|
+
if (!await confirmJwtMigration()) return;
|
|
1358
|
+
}
|
|
1359
|
+
const { accessToken } = await runOauthFlow({
|
|
928
1360
|
timeoutMs: timeoutSeconds * 1e3,
|
|
929
|
-
|
|
1361
|
+
pkceCredentials,
|
|
1362
|
+
baseUrl: credentialsBaseUrl
|
|
930
1363
|
});
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1364
|
+
const scopedApi = getOrCreateApiClient({
|
|
1365
|
+
credentials: accessToken,
|
|
1366
|
+
baseUrl: credentialsBaseUrl
|
|
1367
|
+
});
|
|
1368
|
+
const profile = await getProfile(scopedApi);
|
|
1369
|
+
console.log(`\u{1F464} Logged in as ${profile.email}`);
|
|
1370
|
+
console.log(
|
|
1371
|
+
"\nGenerating credentials so this machine can make authenticated requests on your behalf."
|
|
1372
|
+
);
|
|
1373
|
+
const credentialName = await promptCredentialsName(
|
|
1374
|
+
profile.email,
|
|
1375
|
+
credentialsBaseUrl
|
|
938
1376
|
);
|
|
939
|
-
|
|
1377
|
+
await setupClientCredentials({
|
|
1378
|
+
api: scopedApi,
|
|
1379
|
+
name: credentialName,
|
|
1380
|
+
credentialsBaseUrl
|
|
1381
|
+
});
|
|
1382
|
+
await bestEffortClearLegacyJwtState();
|
|
1383
|
+
console.log(
|
|
1384
|
+
`\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
|
|
1385
|
+
);
|
|
1386
|
+
emitLoginSuccess({ sdk: sdk2, profile });
|
|
940
1387
|
}
|
|
941
1388
|
})
|
|
942
1389
|
);
|
|
@@ -949,8 +1396,36 @@ var logoutPlugin = definePlugin(
|
|
|
949
1396
|
categories: ["account"],
|
|
950
1397
|
inputSchema: LogoutSchema,
|
|
951
1398
|
supportsJsonOutput: false,
|
|
952
|
-
handler: async () => {
|
|
953
|
-
await
|
|
1399
|
+
handler: async ({ sdk: sdk2 }) => {
|
|
1400
|
+
const credentialsBaseUrl = await resolveCredentialsBaseUrl(sdk2.context);
|
|
1401
|
+
const activeCredentials = getActiveCredentials({
|
|
1402
|
+
baseUrl: credentialsBaseUrl
|
|
1403
|
+
});
|
|
1404
|
+
const onEvent = sdk2.context.options?.onEvent;
|
|
1405
|
+
if (!activeCredentials) {
|
|
1406
|
+
await logout({ onEvent });
|
|
1407
|
+
console.log("\u2705 Successfully logged out");
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
const { confirmed } = await inquirer.prompt([
|
|
1411
|
+
{
|
|
1412
|
+
type: "confirm",
|
|
1413
|
+
name: "confirmed",
|
|
1414
|
+
message: `Logging out will delete credentials "${activeCredentials.name}".
|
|
1415
|
+
This may interrupt other Zapier SDK or CLI sessions using them.
|
|
1416
|
+
Do you want to continue?`,
|
|
1417
|
+
default: true
|
|
1418
|
+
}
|
|
1419
|
+
]);
|
|
1420
|
+
if (!confirmed) {
|
|
1421
|
+
console.log("Logout cancelled.");
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
await revokeCredentials({
|
|
1425
|
+
api: sdk2.context.api,
|
|
1426
|
+
credentials: activeCredentials,
|
|
1427
|
+
onEvent
|
|
1428
|
+
});
|
|
954
1429
|
console.log("\u2705 Successfully logged out");
|
|
955
1430
|
}
|
|
956
1431
|
})
|
|
@@ -3450,7 +3925,7 @@ definePlugin(
|
|
|
3450
3925
|
// package.json with { type: 'json' }
|
|
3451
3926
|
var package_default = {
|
|
3452
3927
|
name: "@zapier/zapier-sdk-cli",
|
|
3453
|
-
version: "0.
|
|
3928
|
+
version: "0.48.0"};
|
|
3454
3929
|
|
|
3455
3930
|
// src/sdk.ts
|
|
3456
3931
|
injectCliLogin(login_exports);
|
|
@@ -3478,7 +3953,7 @@ function createZapierCliSdk(options = {}) {
|
|
|
3478
3953
|
|
|
3479
3954
|
// package.json
|
|
3480
3955
|
var package_default2 = {
|
|
3481
|
-
version: "0.
|
|
3956
|
+
version: "0.48.0"};
|
|
3482
3957
|
|
|
3483
3958
|
// src/telemetry/builders.ts
|
|
3484
3959
|
function createCliBaseEvent(context = {}) {
|