@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist/cli.cjs +569 -84
  4. package/dist/cli.mjs +569 -84
  5. package/dist/experimental.cjs +561 -86
  6. package/dist/experimental.mjs +560 -85
  7. package/dist/index.cjs +562 -87
  8. package/dist/index.mjs +561 -86
  9. package/dist/login.cjs +94 -25
  10. package/dist/login.d.mts +29 -2
  11. package/dist/login.d.ts +29 -2
  12. package/dist/login.mjs +90 -25
  13. package/dist/package.json +1 -1
  14. package/dist/src/login/config.d.ts +4 -0
  15. package/dist/src/login/config.js +21 -0
  16. package/dist/src/login/credentials-revoke.d.ts +13 -0
  17. package/dist/src/login/credentials-revoke.js +48 -0
  18. package/dist/src/login/credentials-store.d.ts +33 -0
  19. package/dist/src/login/credentials-store.js +142 -0
  20. package/dist/src/login/index.d.ts +5 -2
  21. package/dist/src/login/index.js +11 -27
  22. package/dist/src/login/legacy-jwt.d.ts +4 -0
  23. package/dist/src/login/legacy-jwt.js +18 -0
  24. package/dist/src/plugins/auth/credentials-base-url.d.ts +11 -0
  25. package/dist/src/plugins/auth/credentials-base-url.js +24 -0
  26. package/dist/src/plugins/login/index.d.ts +6 -1
  27. package/dist/src/plugins/login/index.js +154 -14
  28. package/dist/src/plugins/logout/index.d.ts +14 -0
  29. package/dist/src/plugins/logout/index.js +35 -3
  30. package/dist/src/utils/auth/client-credentials.d.ts +16 -0
  31. package/dist/src/utils/auth/client-credentials.js +53 -0
  32. package/dist/src/utils/auth/oauth-flow.d.ts +12 -0
  33. package/dist/src/utils/auth/{login.js → oauth-flow.js} +36 -58
  34. package/dist/src/utils/parameter-resolver.js +13 -0
  35. package/dist/src/utils/retry.d.ts +5 -0
  36. package/dist/src/utils/retry.js +21 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +3 -3
  39. package/dist/src/utils/auth/login.d.ts +0 -7
package/dist/cli.cjs CHANGED
@@ -9,13 +9,14 @@ var chalk7 = require('chalk');
9
9
  var ora = require('ora');
10
10
  var util = require('util');
11
11
  var wrapAnsi = require('wrap-ansi');
12
- var Conf = require('conf');
13
- var fs = require('fs');
14
12
  var jwt = require('jsonwebtoken');
15
13
  var crossKeychain = require('cross-keychain');
14
+ var Conf = require('conf');
15
+ var fs = require('fs');
16
16
  var crypto = require('crypto');
17
17
  var path = require('path');
18
18
  var lockfile = require('proper-lockfile');
19
+ var os = require('os');
19
20
  var open = require('open');
20
21
  var express = require('express');
21
22
  var pkceChallenge = require('pkce-challenge');
@@ -57,9 +58,9 @@ var chalk7__default = /*#__PURE__*/_interopDefault(chalk7);
57
58
  var ora__default = /*#__PURE__*/_interopDefault(ora);
58
59
  var util__default = /*#__PURE__*/_interopDefault(util);
59
60
  var wrapAnsi__default = /*#__PURE__*/_interopDefault(wrapAnsi);
61
+ var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
60
62
  var Conf__default = /*#__PURE__*/_interopDefault(Conf);
61
63
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
62
- var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
63
64
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
64
65
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
65
66
  var lockfile__namespace = /*#__PURE__*/_interopNamespace(lockfile);
@@ -939,11 +940,18 @@ Optional fields${pathContext}:`));
939
940
  const choices = [...initialChoices];
940
941
  let nextCursor = initialCursor;
941
942
  const LOAD_MORE_SENTINEL = Symbol("LOAD_MORE");
943
+ const CUSTOM_VALUE_SENTINEL = Symbol("CUSTOM_VALUE");
942
944
  while (true) {
943
945
  const promptChoices = choices.map((choice) => ({
944
946
  name: choice.label,
945
947
  value: choice.value
946
948
  }));
949
+ if (!fieldMeta.isMultiSelect) {
950
+ promptChoices.unshift({
951
+ name: chalk7__default.default.dim("(Enter custom value)"),
952
+ value: CUSTOM_VALUE_SENTINEL
953
+ });
954
+ }
947
955
  if (nextCursor) {
948
956
  promptChoices.push({
949
957
  name: chalk7__default.default.dim("(Load more...)"),
@@ -969,6 +977,9 @@ Optional fields${pathContext}:`));
969
977
  };
970
978
  const answer = await inquirer__default.default.prompt([promptConfig]);
971
979
  let selectedValue = answer[fieldMeta.key];
980
+ if (selectedValue === CUSTOM_VALUE_SENTINEL) {
981
+ return await this.promptFreeForm(fieldMeta);
982
+ }
972
983
  const wantsMore = fieldMeta.isMultiSelect ? Array.isArray(selectedValue) && selectedValue.includes(LOAD_MORE_SENTINEL) : selectedValue === LOAD_MORE_SENTINEL;
973
984
  if (wantsMore && nextCursor && context) {
974
985
  if (fieldMeta.isMultiSelect && Array.isArray(selectedValue)) {
@@ -1179,7 +1190,7 @@ var SHARED_COMMAND_CLI_OPTIONS = [
1179
1190
 
1180
1191
  // package.json
1181
1192
  var package_default = {
1182
- version: "0.46.1"};
1193
+ version: "0.48.0"};
1183
1194
 
1184
1195
  // src/telemetry/builders.ts
1185
1196
  function createCliBaseEvent(context = {}) {
@@ -2331,8 +2342,11 @@ function convertValue(value, type, elementType) {
2331
2342
  var login_exports = {};
2332
2343
  __export(login_exports, {
2333
2344
  AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
2345
+ DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
2334
2346
  ZapierAuthenticationError: () => ZapierAuthenticationError,
2347
+ clearTokensFromKeychain: () => clearTokensFromKeychain,
2335
2348
  createCache: () => createCache,
2349
+ getActiveCredentials: () => getActiveCredentials,
2336
2350
  getAuthAuthorizeUrl: () => getAuthAuthorizeUrl,
2337
2351
  getAuthTokenUrl: () => getAuthTokenUrl,
2338
2352
  getConfig: () => getConfig,
@@ -2340,6 +2354,7 @@ __export(login_exports, {
2340
2354
  getLoggedInUser: () => getLoggedInUser,
2341
2355
  getLoginStorageMode: () => getLoginStorageMode,
2342
2356
  getPkceLoginConfig: () => getPkceLoginConfig,
2357
+ getStoredClientCredentials: () => getStoredClientCredentials,
2343
2358
  getToken: () => getToken,
2344
2359
  logout: () => logout,
2345
2360
  unloadConfig: () => unloadConfig,
@@ -2420,6 +2435,38 @@ async function clearTokensFromKeychain({
2420
2435
  }
2421
2436
  });
2422
2437
  }
2438
+ var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
2439
+ var config = null;
2440
+ function getConfig() {
2441
+ if (!config) {
2442
+ config = new Conf__default.default({ projectName: "zapier-sdk-cli" });
2443
+ if (!config.has("login_storage_mode")) {
2444
+ config.set(
2445
+ "login_storage_mode",
2446
+ fs.existsSync(config.path) ? "config" : "keychain"
2447
+ );
2448
+ }
2449
+ }
2450
+ return config;
2451
+ }
2452
+ function resetConfig() {
2453
+ config = null;
2454
+ }
2455
+
2456
+ // src/login/legacy-jwt.ts
2457
+ function clearLegacyJwtConfigKeys(config2) {
2458
+ config2.delete("login_jwt");
2459
+ config2.delete("login_refresh_token");
2460
+ config2.delete("login_expires_at");
2461
+ }
2462
+ async function clearLegacyJwtState() {
2463
+ clearLegacyJwtConfigKeys(getConfig());
2464
+ await clearTokensFromKeychain();
2465
+ }
2466
+ function hasLegacyJwtConfig() {
2467
+ const cfg = getConfig();
2468
+ return typeof cfg.get("login_jwt") === "string" || typeof cfg.get("login_refresh_token") === "string" || typeof cfg.get("login_expires_at") === "number";
2469
+ }
2423
2470
  var SERVICE2 = "zapier-sdk-cache";
2424
2471
  var CONFIG_KEY = "cache";
2425
2472
  var LOCK_UPDATE_MS = 5e3;
@@ -2547,6 +2594,163 @@ function createCache() {
2547
2594
  }
2548
2595
  };
2549
2596
  }
2597
+ var SERVICE3 = "zapier-sdk-cli";
2598
+ var CREDENTIALS_KEY = "credentials";
2599
+ var REGISTRY_KEY = "credentialsRegistry";
2600
+ var CredentialsEntrySchema = zod.z.object({
2601
+ name: zod.z.string(),
2602
+ clientId: zod.z.string(),
2603
+ createdAt: zod.z.number(),
2604
+ scopes: zod.z.array(zod.z.string()),
2605
+ baseUrl: zod.z.string()
2606
+ });
2607
+ function normalizeBaseUrl(baseUrl2) {
2608
+ return baseUrl2 ?? DEFAULT_AUTH_BASE_URL;
2609
+ }
2610
+ function keychainAccount2(key) {
2611
+ return crypto.createHash("sha256").update(key).digest("hex");
2612
+ }
2613
+ function buildKeychainKey(clientId, scopes, baseUrl2) {
2614
+ const sortedScopes = [...scopes].sort().join(",");
2615
+ return `zapier-sdk/client-credentials-secret/${clientId}:${sortedScopes}:${baseUrl2}`;
2616
+ }
2617
+ function findEntry(registry, name, baseUrl2) {
2618
+ return registry.find((e) => e.name === name && e.baseUrl === baseUrl2);
2619
+ }
2620
+ function readRegistry() {
2621
+ const stored = getConfig().get(REGISTRY_KEY);
2622
+ if (!Array.isArray(stored)) return [];
2623
+ return stored.flatMap((entry) => {
2624
+ const result = CredentialsEntrySchema.safeParse(entry);
2625
+ return result.success ? [result.data] : [];
2626
+ });
2627
+ }
2628
+ function getActiveCredentials(options) {
2629
+ const name = getConfig().get(CREDENTIALS_KEY);
2630
+ if (!name) return void 0;
2631
+ return findEntry(readRegistry(), name, normalizeBaseUrl(options?.baseUrl));
2632
+ }
2633
+ async function storeClientCredentials({
2634
+ name,
2635
+ clientId,
2636
+ clientSecret,
2637
+ scopes,
2638
+ baseUrl: baseUrl2
2639
+ }) {
2640
+ if (!name || typeof name !== "string") {
2641
+ throw new Error("storeClientCredentials: name is required");
2642
+ }
2643
+ if (!clientId || typeof clientId !== "string") {
2644
+ throw new Error("storeClientCredentials: clientId is required");
2645
+ }
2646
+ if (!clientSecret || typeof clientSecret !== "string") {
2647
+ throw new Error("storeClientCredentials: clientSecret is required");
2648
+ }
2649
+ if (!Array.isArray(scopes) || scopes.length === 0) {
2650
+ throw new Error("storeClientCredentials: scopes must be a non-empty array");
2651
+ }
2652
+ const sortedScopes = [...scopes].sort();
2653
+ const resolvedBaseUrl = normalizeBaseUrl(baseUrl2);
2654
+ const keychainKey = buildKeychainKey(clientId, sortedScopes, resolvedBaseUrl);
2655
+ const existingEntry = findEntry(readRegistry(), name, resolvedBaseUrl);
2656
+ const existingKeychainKey = existingEntry ? buildKeychainKey(
2657
+ existingEntry.clientId,
2658
+ existingEntry.scopes,
2659
+ existingEntry.baseUrl
2660
+ ) : void 0;
2661
+ await enqueue(async () => {
2662
+ await getBackendInfo();
2663
+ await crossKeychain.setPassword(SERVICE3, keychainAccount2(keychainKey), clientSecret);
2664
+ });
2665
+ const entry = {
2666
+ name,
2667
+ clientId,
2668
+ createdAt: Date.now(),
2669
+ scopes: sortedScopes,
2670
+ baseUrl: resolvedBaseUrl
2671
+ };
2672
+ const registry = readRegistry().filter(
2673
+ (e) => !(e.name === name && e.baseUrl === resolvedBaseUrl)
2674
+ );
2675
+ registry.push(entry);
2676
+ const cfg = getConfig();
2677
+ cfg.set(REGISTRY_KEY, registry);
2678
+ cfg.set(CREDENTIALS_KEY, name);
2679
+ if (existingEntry && existingKeychainKey !== keychainKey) {
2680
+ await deleteKeychainSecret(existingEntry);
2681
+ }
2682
+ }
2683
+ function credentialsNameExists({
2684
+ name,
2685
+ baseUrl: baseUrl2
2686
+ }) {
2687
+ return !!findEntry(readRegistry(), name, normalizeBaseUrl(baseUrl2));
2688
+ }
2689
+ async function getStoredClientCredentials(options) {
2690
+ const entry = options?.name ? findEntry(readRegistry(), options.name, normalizeBaseUrl(options.baseUrl)) : getActiveCredentials(options);
2691
+ if (!entry) return void 0;
2692
+ const keychainKey = buildKeychainKey(
2693
+ entry.clientId,
2694
+ entry.scopes,
2695
+ entry.baseUrl
2696
+ );
2697
+ const clientSecret = await enqueue(async () => {
2698
+ await getBackendInfo();
2699
+ return crossKeychain.getPassword(SERVICE3, keychainAccount2(keychainKey));
2700
+ });
2701
+ if (!clientSecret) return void 0;
2702
+ return {
2703
+ type: "client_credentials",
2704
+ clientId: entry.clientId,
2705
+ clientSecret,
2706
+ baseUrl: entry.baseUrl,
2707
+ scope: [...entry.scopes].sort().join(" ")
2708
+ };
2709
+ }
2710
+ function deleteRegistryEntry(registry, name, baseUrl2) {
2711
+ const idx = registry.findIndex(
2712
+ (e) => e.name === name && e.baseUrl === baseUrl2
2713
+ );
2714
+ if (idx === -1) return void 0;
2715
+ const [removed] = registry.splice(idx, 1);
2716
+ return removed;
2717
+ }
2718
+ function unsetMatchingCredentialsKey(cfg, name) {
2719
+ const activeName = cfg.get(CREDENTIALS_KEY);
2720
+ if (activeName === name && !readRegistry().some((e) => e.name === name)) {
2721
+ cfg.delete(CREDENTIALS_KEY);
2722
+ }
2723
+ }
2724
+ async function deleteKeychainSecret(entry) {
2725
+ const keychainKey = buildKeychainKey(
2726
+ entry.clientId,
2727
+ entry.scopes,
2728
+ entry.baseUrl
2729
+ );
2730
+ try {
2731
+ await enqueue(async () => {
2732
+ await getBackendInfo();
2733
+ await crossKeychain.deletePassword(SERVICE3, keychainAccount2(keychainKey));
2734
+ });
2735
+ } catch {
2736
+ }
2737
+ }
2738
+ async function deleteStoredClientCredentials({
2739
+ name,
2740
+ baseUrl: baseUrl2
2741
+ }) {
2742
+ const registry = readRegistry();
2743
+ const removed = deleteRegistryEntry(
2744
+ registry,
2745
+ name,
2746
+ normalizeBaseUrl(baseUrl2)
2747
+ );
2748
+ if (!removed) return;
2749
+ const cfg = getConfig();
2750
+ cfg.set(REGISTRY_KEY, registry);
2751
+ unsetMatchingCredentialsKey(cfg, name);
2752
+ await deleteKeychainSecret(removed);
2753
+ }
2550
2754
 
2551
2755
  // src/login/index.ts
2552
2756
  var ZapierAuthenticationError = class extends Error {
@@ -2555,7 +2759,6 @@ var ZapierAuthenticationError = class extends Error {
2555
2759
  this.name = "ZapierAuthenticationError";
2556
2760
  }
2557
2761
  };
2558
- var config = null;
2559
2762
  var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
2560
2763
  var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
2561
2764
  function createDebugLog(enabled) {
@@ -2581,7 +2784,6 @@ function getAuthClientId(clientId) {
2581
2784
  return clientId || DEFAULT_AUTH_CLIENT_ID;
2582
2785
  }
2583
2786
  var AUTH_MODE_HEADER = "X-Auth";
2584
- var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
2585
2787
  function getAuthTokenUrl(options) {
2586
2788
  const authBaseUrl = options?.baseUrl || DEFAULT_AUTH_BASE_URL;
2587
2789
  return `${authBaseUrl}/oauth/token/`;
@@ -2591,29 +2793,16 @@ function getAuthAuthorizeUrl(options) {
2591
2793
  return `${authBaseUrl}/oauth/authorize/`;
2592
2794
  }
2593
2795
  function getPkceLoginConfig(options) {
2796
+ const effectiveBaseUrl = options?.credentials?.baseUrl ?? options?.baseUrl;
2594
2797
  return {
2595
2798
  clientId: getAuthClientId(options?.credentials?.clientId),
2596
- tokenUrl: getAuthTokenUrl({ baseUrl: options?.credentials?.baseUrl }),
2597
- authorizeUrl: getAuthAuthorizeUrl({
2598
- baseUrl: options?.credentials?.baseUrl
2599
- })
2799
+ tokenUrl: getAuthTokenUrl({ baseUrl: effectiveBaseUrl }),
2800
+ authorizeUrl: getAuthAuthorizeUrl({ baseUrl: effectiveBaseUrl })
2600
2801
  };
2601
2802
  }
2602
2803
  var cachedLogin;
2603
- function getConfig() {
2604
- if (!config) {
2605
- config = new Conf__default.default({ projectName: "zapier-sdk-cli" });
2606
- if (!config.has("login_storage_mode")) {
2607
- config.set(
2608
- "login_storage_mode",
2609
- fs.existsSync(config.path) ? "config" : "keychain"
2610
- );
2611
- }
2612
- }
2613
- return config;
2614
- }
2615
2804
  function unloadConfig() {
2616
- config = null;
2805
+ resetConfig();
2617
2806
  cachedLogin = void 0;
2618
2807
  }
2619
2808
  async function updateLogin(loginData, options = {}) {
@@ -2886,9 +3075,7 @@ async function logout(options = {}) {
2886
3075
  await clearTokensFromKeychain();
2887
3076
  const cfg = getConfig();
2888
3077
  cfg.set("login_storage_mode", mode);
2889
- cfg.delete("login_expires_at");
2890
- cfg.delete("login_jwt");
2891
- cfg.delete("login_refresh_token");
3078
+ clearLegacyJwtConfigKeys(cfg);
2892
3079
  onEvent?.({
2893
3080
  type: "auth_logout",
2894
3081
  payload: { message: "Logged out successfully", operation: "logout" },
@@ -2900,6 +3087,79 @@ function getConfigPath() {
2900
3087
  return cfg.path;
2901
3088
  }
2902
3089
 
3090
+ // src/utils/retry.ts
3091
+ function sleep(ms) {
3092
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
3093
+ }
3094
+ async function withRetry({
3095
+ action,
3096
+ attempts = 3,
3097
+ initialDelayMs = 100
3098
+ }) {
3099
+ if (attempts <= 0) {
3100
+ throw new Error("withRetry: attempts must be greater than 0");
3101
+ }
3102
+ let lastError;
3103
+ for (let i = 0; i < attempts; i++) {
3104
+ try {
3105
+ return await action();
3106
+ } catch (err) {
3107
+ lastError = err;
3108
+ if (i < attempts - 1) {
3109
+ await sleep(initialDelayMs * 2 ** i);
3110
+ }
3111
+ }
3112
+ }
3113
+ throw lastError;
3114
+ }
3115
+
3116
+ // src/login/credentials-revoke.ts
3117
+ function emitAuthLogout(onEvent) {
3118
+ onEvent?.({
3119
+ type: "auth_logout",
3120
+ payload: { message: "Logged out successfully", operation: "logout" },
3121
+ timestamp: Date.now()
3122
+ });
3123
+ }
3124
+ function isNotFoundError(err) {
3125
+ return typeof err === "object" && err !== null && "statusCode" in err && err.statusCode === 404;
3126
+ }
3127
+ async function revokeCredentials({
3128
+ api: api2,
3129
+ credentials: credentials2,
3130
+ onEvent
3131
+ }) {
3132
+ await withRetry({
3133
+ action: async () => {
3134
+ try {
3135
+ await api2.delete(
3136
+ `/api/v0/client-credentials/${credentials2.clientId}`,
3137
+ void 0,
3138
+ { authRequired: true, requiredScopes: ["credentials"] }
3139
+ );
3140
+ } catch (err) {
3141
+ if (isNotFoundError(err)) return;
3142
+ throw err;
3143
+ }
3144
+ }
3145
+ });
3146
+ try {
3147
+ await deleteStoredClientCredentials({
3148
+ name: credentials2.name,
3149
+ baseUrl: credentials2.baseUrl
3150
+ });
3151
+ } catch (err) {
3152
+ console.warn("[revokeCredentials] Local store cleanup failed:", err);
3153
+ }
3154
+ await clearLegacyJwtState();
3155
+ await zapierSdk.invalidateCachedToken({
3156
+ clientId: credentials2.clientId,
3157
+ scopes: credentials2.scopes,
3158
+ baseUrl: credentials2.baseUrl
3159
+ });
3160
+ emitAuthLogout(onEvent);
3161
+ }
3162
+
2903
3163
  // src/utils/constants.ts
2904
3164
  var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
2905
3165
  var LOGIN_TIMEOUT_MS = 3e5;
@@ -2985,6 +3245,8 @@ var getCallablePromise = () => {
2985
3245
  };
2986
3246
  };
2987
3247
  var getCallablePromise_default = getCallablePromise;
3248
+
3249
+ // src/utils/auth/oauth-flow.ts
2988
3250
  var findAvailablePort = () => {
2989
3251
  return new Promise((resolve4, reject) => {
2990
3252
  let portIndex = 0;
@@ -3019,10 +3281,9 @@ var findAvailablePort = () => {
3019
3281
  var generateRandomString = () => {
3020
3282
  const array = new Uint32Array(28);
3021
3283
  crypto__default.default.getRandomValues(array);
3022
- return Array.from(
3023
- array,
3024
- (dec) => ("0" + dec.toString(16)).substring(-2)
3025
- ).join("");
3284
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
3285
+ ""
3286
+ );
3026
3287
  };
3027
3288
  function ensureOfflineAccess(scope) {
3028
3289
  if (scope.includes("offline_access")) {
@@ -3030,17 +3291,18 @@ function ensureOfflineAccess(scope) {
3030
3291
  }
3031
3292
  return `${scope} offline_access`;
3032
3293
  }
3033
- var login = async ({
3294
+ async function runOauthFlow({
3034
3295
  timeoutMs = LOGIN_TIMEOUT_MS,
3035
- credentials: credentials2
3036
- }) => {
3296
+ pkceCredentials,
3297
+ baseUrl: baseUrl2
3298
+ }) {
3037
3299
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
3038
- credentials: credentials2
3300
+ credentials: pkceCredentials,
3301
+ baseUrl: baseUrl2
3039
3302
  });
3040
3303
  const scope = ensureOfflineAccess(
3041
- credentials2?.scope || "internal credentials"
3304
+ pkceCredentials?.scope || "internal credentials"
3042
3305
  );
3043
- await logout();
3044
3306
  const availablePort = await findAvailablePort();
3045
3307
  const redirectUri = `http://localhost:${availablePort}/oauth`;
3046
3308
  log_default.info(`Using port ${availablePort} for OAuth callback`);
@@ -3049,13 +3311,30 @@ var login = async ({
3049
3311
  resolve: setCode,
3050
3312
  reject: rejectCode
3051
3313
  } = getCallablePromise_default();
3052
- const app = express__default.default();
3053
- app.get("/oauth", (req, res) => {
3054
- setCode(String(req.query.code));
3314
+ const oauthState = generateRandomString();
3315
+ const expressApp = express__default.default();
3316
+ expressApp.get("/oauth", (req, res) => {
3055
3317
  res.setHeader("Connection", "close");
3318
+ if (req.query.state !== oauthState) {
3319
+ rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
3320
+ res.status(400).end("Invalid state. You can close this tab.");
3321
+ return;
3322
+ }
3323
+ if (req.query.error) {
3324
+ const desc = req.query.error_description ?? req.query.error;
3325
+ rejectCode(new Error(`Authorization denied: ${desc}`));
3326
+ res.end("Authorization was denied. You can close this tab.");
3327
+ return;
3328
+ }
3329
+ if (!req.query.code) {
3330
+ rejectCode(new Error("No authorization code received"));
3331
+ res.end("No authorization code received. You can close this tab.");
3332
+ return;
3333
+ }
3334
+ setCode(String(req.query.code));
3056
3335
  res.end("You can now close this tab and return to the CLI.");
3057
3336
  });
3058
- const server = app.listen(availablePort);
3337
+ const server = expressApp.listen(availablePort);
3059
3338
  const connections = /* @__PURE__ */ new Set();
3060
3339
  server.on("connection", (conn) => {
3061
3340
  connections.add(conn);
@@ -3074,7 +3353,7 @@ var login = async ({
3074
3353
  client_id: clientId,
3075
3354
  redirect_uri: redirectUri,
3076
3355
  scope,
3077
- state: generateRandomString(),
3356
+ state: oauthState,
3078
3357
  code_challenge: codeChallenge,
3079
3358
  code_challenge_method: "S256"
3080
3359
  }).toString()}`;
@@ -3133,36 +3412,79 @@ var login = async ({
3133
3412
  }
3134
3413
  }
3135
3414
  );
3136
- let targetStorage;
3137
- if (getLoginStorageMode() === "config") {
3138
- const { upgrade } = await inquirer__default.default.prompt([
3139
- {
3140
- type: "confirm",
3141
- name: "upgrade",
3142
- message: "Would you like to upgrade to system keychain storage? This is recommended to securely store your credentials. However, note that older SDK/CLI versions will NOT be able to read these credentials, so you will want to upgrade them to the latest version.",
3143
- default: true
3144
- }
3145
- ]);
3146
- targetStorage = upgrade ? "keychain" : "config";
3147
- } else {
3148
- targetStorage = "keychain";
3149
- }
3415
+ log_default.info("Token exchange completed successfully");
3416
+ return {
3417
+ accessToken: data.access_token,
3418
+ refreshToken: data.refresh_token,
3419
+ expiresIn: data.expires_in
3420
+ };
3421
+ }
3422
+
3423
+ // src/utils/auth/client-credentials.ts
3424
+ var CREDENTIALS_SCOPES = ["external", "credentials"];
3425
+ async function createCredentialsOnServer(api2, name) {
3426
+ const response = await api2.post(
3427
+ "/api/v0/client-credentials",
3428
+ { name, allowed_scopes: CREDENTIALS_SCOPES },
3429
+ { authRequired: true, requiredScopes: ["credentials"] }
3430
+ );
3431
+ return {
3432
+ clientId: response.data.client_id,
3433
+ clientSecret: response.data.client_secret
3434
+ };
3435
+ }
3436
+ async function deleteCredentialsOnServer(api2, clientId) {
3437
+ await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
3438
+ authRequired: true,
3439
+ requiredScopes: ["credentials"]
3440
+ });
3441
+ }
3442
+ async function setupClientCredentials({
3443
+ api: api2,
3444
+ name,
3445
+ credentialsBaseUrl: credentialsBaseUrl2
3446
+ }) {
3447
+ const { clientId, clientSecret } = await createCredentialsOnServer(api2, name);
3150
3448
  try {
3151
- await updateLogin(data, { storage: targetStorage });
3152
- } catch (err) {
3153
- if (targetStorage === "keychain") {
3154
- log_default.warn(
3155
- `Could not store credentials in system keychain. Storing in plaintext at ${getConfigPath()}.`
3449
+ await withRetry({
3450
+ action: () => storeClientCredentials({
3451
+ name,
3452
+ clientId,
3453
+ clientSecret,
3454
+ scopes: [...CREDENTIALS_SCOPES],
3455
+ baseUrl: credentialsBaseUrl2
3456
+ })
3457
+ });
3458
+ } catch (storeErr) {
3459
+ try {
3460
+ await withRetry({
3461
+ action: () => deleteCredentialsOnServer(api2, clientId)
3462
+ });
3463
+ } catch {
3464
+ console.error(
3465
+ `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
3156
3466
  );
3157
- await updateLogin(data, { storage: "config" });
3158
- } else {
3159
- throw err;
3160
3467
  }
3468
+ throw storeErr;
3161
3469
  }
3162
- log_default.info("Token exchange completed successfully");
3163
- return data.access_token;
3164
- };
3165
- var login_default = login;
3470
+ return { clientId };
3471
+ }
3472
+ function getBaseUrlFromResolvedCredentials(credentials2) {
3473
+ if (credentials2 && zapierSdk.isCredentialsObject(credentials2)) {
3474
+ return credentials2.baseUrl;
3475
+ }
3476
+ return void 0;
3477
+ }
3478
+ function getBaseUrlFromOptionsCredentials(credentials2) {
3479
+ if (credentials2 && typeof credentials2 === "object" && "baseUrl" in credentials2 && typeof credentials2.baseUrl === "string") {
3480
+ return credentials2.baseUrl;
3481
+ }
3482
+ return void 0;
3483
+ }
3484
+ async function resolveCredentialsBaseUrl(context) {
3485
+ const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
3486
+ return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
3487
+ }
3166
3488
  var LoginSchema = zod.z.object({
3167
3489
  timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)")
3168
3490
  }).describe("Log in to Zapier to access your account");
@@ -3179,6 +3501,105 @@ function toPkceCredentials(credentials2) {
3179
3501
  }
3180
3502
  return void 0;
3181
3503
  }
3504
+ async function confirmRevokeAndRelogin(activeCredentials) {
3505
+ const { confirmed } = await inquirer__default.default.prompt([
3506
+ {
3507
+ type: "confirm",
3508
+ name: "confirmed",
3509
+ message: `You are already logged in as "${activeCredentials.name}".
3510
+ Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
3511
+ Log out and log in again?`,
3512
+ default: false
3513
+ }
3514
+ ]);
3515
+ if (!confirmed) {
3516
+ console.log("Login cancelled.");
3517
+ return false;
3518
+ }
3519
+ return true;
3520
+ }
3521
+ async function confirmJwtMigration() {
3522
+ const { confirmed } = await inquirer__default.default.prompt([
3523
+ {
3524
+ type: "confirm",
3525
+ name: "confirmed",
3526
+ 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?",
3527
+ default: true
3528
+ }
3529
+ ]);
3530
+ if (!confirmed) {
3531
+ console.log("Login cancelled.");
3532
+ return false;
3533
+ }
3534
+ return true;
3535
+ }
3536
+ async function confirmLocalLoginReset() {
3537
+ const { confirmed } = await inquirer__default.default.prompt([
3538
+ {
3539
+ type: "confirm",
3540
+ name: "confirmed",
3541
+ message: "Login cleanup failed. Reset local session state and continue?",
3542
+ default: false
3543
+ }
3544
+ ]);
3545
+ if (!confirmed) {
3546
+ console.log("Login cancelled.");
3547
+ return false;
3548
+ }
3549
+ return true;
3550
+ }
3551
+ function parseTimeoutSeconds(timeout) {
3552
+ const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
3553
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
3554
+ throw new Error("Timeout must be a positive number");
3555
+ }
3556
+ return timeoutSeconds;
3557
+ }
3558
+ async function promptCredentialsName(email, baseUrl2) {
3559
+ const { credentialName } = await inquirer__default.default.prompt([
3560
+ {
3561
+ type: "input",
3562
+ name: "credentialName",
3563
+ message: "Enter a name to identify them:",
3564
+ default: `${email}@${os.hostname()}`,
3565
+ validate: (input) => {
3566
+ if (!input.trim()) return "Name cannot be empty";
3567
+ if (credentialsNameExists({ name: input.trim(), baseUrl: baseUrl2 })) {
3568
+ return `Credentials named "${input.trim()}" already exist. Please provide a different name.`;
3569
+ }
3570
+ return true;
3571
+ }
3572
+ }
3573
+ ]);
3574
+ return credentialName;
3575
+ }
3576
+ function emitLoginSuccess({
3577
+ sdk,
3578
+ profile
3579
+ }) {
3580
+ sdk.context.eventEmission.emit(
3581
+ "platform.sdk.ApplicationLifecycleEvent",
3582
+ zapierSdk.buildApplicationLifecycleEvent(
3583
+ { lifecycle_event_type: "login_success" },
3584
+ {
3585
+ customuser_id: profile.user_id,
3586
+ account_id: profile.roles[0]?.account_id ?? null
3587
+ }
3588
+ )
3589
+ );
3590
+ }
3591
+ async function getProfile(api2) {
3592
+ return api2.get("/zapier/api/v4/profile/", {
3593
+ authRequired: true
3594
+ });
3595
+ }
3596
+ async function bestEffortClearLegacyJwtState() {
3597
+ try {
3598
+ await clearLegacyJwtState();
3599
+ } catch (err) {
3600
+ console.error("[login] Best-effort legacy JWT cleanup failed:", err);
3601
+ }
3602
+ }
3182
3603
  var loginPlugin = zapierSdk.definePlugin(
3183
3604
  (sdk) => zapierSdk.createPluginMethod(sdk, {
3184
3605
  name: "login",
@@ -3186,25 +3607,61 @@ var loginPlugin = zapierSdk.definePlugin(
3186
3607
  inputSchema: LoginSchema,
3187
3608
  supportsJsonOutput: false,
3188
3609
  handler: async ({ sdk: sdk2, options }) => {
3189
- const timeoutSeconds = options.timeout ? parseInt(options.timeout, 10) : 300;
3190
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
3191
- throw new Error("Timeout must be a positive number");
3192
- }
3610
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
3193
3611
  const resolvedCredentials = await sdk2.context.resolveCredentials();
3194
3612
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
3195
- await login_default({
3613
+ const credentialsBaseUrl2 = await resolveCredentialsBaseUrl({
3614
+ ...sdk2.context,
3615
+ resolvedCredentials
3616
+ });
3617
+ const activeCredentials = getActiveCredentials({
3618
+ baseUrl: credentialsBaseUrl2
3619
+ });
3620
+ if (activeCredentials) {
3621
+ if (!await confirmRevokeAndRelogin(activeCredentials)) return;
3622
+ try {
3623
+ await revokeCredentials({
3624
+ api: sdk2.context.api,
3625
+ credentials: activeCredentials
3626
+ });
3627
+ } catch {
3628
+ if (!await confirmLocalLoginReset()) return;
3629
+ await deleteStoredClientCredentials({
3630
+ name: activeCredentials.name,
3631
+ baseUrl: activeCredentials.baseUrl
3632
+ });
3633
+ }
3634
+ } else if (hasLegacyJwtConfig()) {
3635
+ if (!await confirmJwtMigration()) return;
3636
+ }
3637
+ const { accessToken } = await runOauthFlow({
3196
3638
  timeoutMs: timeoutSeconds * 1e3,
3197
- credentials: pkceCredentials
3639
+ pkceCredentials,
3640
+ baseUrl: credentialsBaseUrl2
3198
3641
  });
3199
- const user = await getLoggedInUser();
3200
- sdk2.context.eventEmission.emit(
3201
- "platform.sdk.ApplicationLifecycleEvent",
3202
- zapierSdk.buildApplicationLifecycleEvent(
3203
- { lifecycle_event_type: "login_success" },
3204
- { customuser_id: user.customUserId, account_id: user.accountId }
3205
- )
3642
+ const scopedApi = zapierSdk.getOrCreateApiClient({
3643
+ credentials: accessToken,
3644
+ baseUrl: credentialsBaseUrl2
3645
+ });
3646
+ const profile = await getProfile(scopedApi);
3647
+ console.log(`\u{1F464} Logged in as ${profile.email}`);
3648
+ console.log(
3649
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf."
3650
+ );
3651
+ const credentialName = await promptCredentialsName(
3652
+ profile.email,
3653
+ credentialsBaseUrl2
3654
+ );
3655
+ await setupClientCredentials({
3656
+ api: scopedApi,
3657
+ name: credentialName,
3658
+ credentialsBaseUrl: credentialsBaseUrl2
3659
+ });
3660
+ await bestEffortClearLegacyJwtState();
3661
+ console.log(
3662
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
3206
3663
  );
3207
- console.log(`\u2705 Successfully logged in as ${user.email}`);
3664
+ emitLoginSuccess({ sdk: sdk2, profile });
3208
3665
  }
3209
3666
  })
3210
3667
  );
@@ -3217,8 +3674,36 @@ var logoutPlugin = zapierSdk.definePlugin(
3217
3674
  categories: ["account"],
3218
3675
  inputSchema: LogoutSchema,
3219
3676
  supportsJsonOutput: false,
3220
- handler: async () => {
3221
- await logout();
3677
+ handler: async ({ sdk: sdk2 }) => {
3678
+ const credentialsBaseUrl2 = await resolveCredentialsBaseUrl(sdk2.context);
3679
+ const activeCredentials = getActiveCredentials({
3680
+ baseUrl: credentialsBaseUrl2
3681
+ });
3682
+ const onEvent = sdk2.context.options?.onEvent;
3683
+ if (!activeCredentials) {
3684
+ await logout({ onEvent });
3685
+ console.log("\u2705 Successfully logged out");
3686
+ return;
3687
+ }
3688
+ const { confirmed } = await inquirer__default.default.prompt([
3689
+ {
3690
+ type: "confirm",
3691
+ name: "confirmed",
3692
+ message: `Logging out will delete credentials "${activeCredentials.name}".
3693
+ This may interrupt other Zapier SDK or CLI sessions using them.
3694
+ Do you want to continue?`,
3695
+ default: true
3696
+ }
3697
+ ]);
3698
+ if (!confirmed) {
3699
+ console.log("Logout cancelled.");
3700
+ return;
3701
+ }
3702
+ await revokeCredentials({
3703
+ api: sdk2.context.api,
3704
+ credentials: activeCredentials,
3705
+ onEvent
3706
+ });
3222
3707
  console.log("\u2705 Successfully logged out");
3223
3708
  }
3224
3709
  })
@@ -5735,7 +6220,7 @@ var watchTriggerInboxCliPlugin = zapierSdk.definePlugin(
5735
6220
  // package.json with { type: 'json' }
5736
6221
  var package_default2 = {
5737
6222
  name: "@zapier/zapier-sdk-cli",
5738
- version: "0.46.1"};
6223
+ version: "0.48.0"};
5739
6224
 
5740
6225
  // src/sdk.ts
5741
6226
  zapierSdk.injectCliLogin(login_exports);