@zapier/zapier-sdk-cli 0.47.0 → 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 +14 -0
- package/README.md +1 -1
- package/dist/cli.cjs +559 -84
- package/dist/cli.mjs +559 -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/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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PkceCredentials } from "../../login";
|
|
2
|
+
export interface OauthTokens {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
expiresIn: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RunOauthFlowOptions {
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
pkceCredentials?: PkceCredentials;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function runOauthFlow({ timeoutMs, pkceCredentials, baseUrl, }: RunOauthFlowOptions): Promise<OauthTokens>;
|
|
@@ -7,8 +7,7 @@ import { spinPromise } from "../spinner";
|
|
|
7
7
|
import log from "../log";
|
|
8
8
|
import api from "../api/client";
|
|
9
9
|
import getCallablePromise from "../getCallablePromise";
|
|
10
|
-
import
|
|
11
|
-
import { updateLogin, logout, getPkceLoginConfig, getLoginStorageMode, getConfigPath, } from "../../login";
|
|
10
|
+
import { getPkceLoginConfig } from "../../login";
|
|
12
11
|
import { ZapierCliUserCancellationError } from "../errors";
|
|
13
12
|
const findAvailablePort = () => {
|
|
14
13
|
return new Promise((resolve, reject) => {
|
|
@@ -21,11 +20,9 @@ const findAvailablePort = () => {
|
|
|
21
20
|
server.on("error", (err) => {
|
|
22
21
|
if (err.code === "EADDRINUSE") {
|
|
23
22
|
if (portIndex < LOGIN_PORTS.length) {
|
|
24
|
-
// Try next predefined port
|
|
25
23
|
tryPort(LOGIN_PORTS[portIndex++]);
|
|
26
24
|
}
|
|
27
25
|
else {
|
|
28
|
-
// All configured ports are busy
|
|
29
26
|
reject(new Error(`All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`));
|
|
30
27
|
}
|
|
31
28
|
}
|
|
@@ -45,41 +42,54 @@ const findAvailablePort = () => {
|
|
|
45
42
|
const generateRandomString = () => {
|
|
46
43
|
const array = new Uint32Array(28);
|
|
47
44
|
crypto.getRandomValues(array);
|
|
48
|
-
return Array.from(array, (dec) => ("0" + dec.toString(16)).
|
|
45
|
+
return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join("");
|
|
49
46
|
};
|
|
50
|
-
// Ensure offline_access is included in scope for PKCE (needed for refresh tokens)
|
|
51
47
|
function ensureOfflineAccess(scope) {
|
|
52
48
|
if (scope.includes("offline_access")) {
|
|
53
49
|
return scope;
|
|
54
50
|
}
|
|
55
51
|
return `${scope} offline_access`;
|
|
56
52
|
}
|
|
57
|
-
|
|
53
|
+
// Does not persist anything — caller decides what to do with the tokens.
|
|
54
|
+
export async function runOauthFlow({ timeoutMs = LOGIN_TIMEOUT_MS, pkceCredentials, baseUrl, }) {
|
|
58
55
|
const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
|
|
59
|
-
credentials,
|
|
56
|
+
credentials: pkceCredentials,
|
|
57
|
+
baseUrl,
|
|
60
58
|
});
|
|
61
|
-
const scope = ensureOfflineAccess(
|
|
62
|
-
await logout();
|
|
63
|
-
// Find an available port
|
|
59
|
+
const scope = ensureOfflineAccess(pkceCredentials?.scope || "internal credentials");
|
|
64
60
|
const availablePort = await findAvailablePort();
|
|
65
61
|
const redirectUri = `http://localhost:${availablePort}/oauth`;
|
|
66
62
|
log.info(`Using port ${availablePort} for OAuth callback`);
|
|
67
63
|
const { promise: promisedCode, resolve: setCode, reject: rejectCode, } = getCallablePromise();
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Set headers to prevent keep-alive
|
|
64
|
+
const oauthState = generateRandomString();
|
|
65
|
+
const expressApp = express();
|
|
66
|
+
expressApp.get("/oauth", (req, res) => {
|
|
72
67
|
res.setHeader("Connection", "close");
|
|
68
|
+
if (req.query.state !== oauthState) {
|
|
69
|
+
rejectCode(new Error("OAuth state mismatch — possible CSRF"));
|
|
70
|
+
res.status(400).end("Invalid state. You can close this tab.");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (req.query.error) {
|
|
74
|
+
const desc = req.query.error_description ?? req.query.error;
|
|
75
|
+
rejectCode(new Error(`Authorization denied: ${desc}`));
|
|
76
|
+
res.end("Authorization was denied. You can close this tab.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!req.query.code) {
|
|
80
|
+
rejectCode(new Error("No authorization code received"));
|
|
81
|
+
res.end("No authorization code received. You can close this tab.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setCode(String(req.query.code));
|
|
73
85
|
res.end("You can now close this tab and return to the CLI.");
|
|
74
86
|
});
|
|
75
|
-
const server =
|
|
76
|
-
// Track connections to force close them if needed
|
|
87
|
+
const server = expressApp.listen(availablePort);
|
|
77
88
|
const connections = new Set();
|
|
78
89
|
server.on("connection", (conn) => {
|
|
79
90
|
connections.add(conn);
|
|
80
91
|
conn.on("close", () => connections.delete(conn));
|
|
81
92
|
});
|
|
82
|
-
// Set up signal handlers for graceful shutdown
|
|
83
93
|
const cleanup = () => {
|
|
84
94
|
server.close();
|
|
85
95
|
log.info("\n❌ Login cancelled by user");
|
|
@@ -93,14 +103,13 @@ const login = async ({ timeoutMs = LOGIN_TIMEOUT_MS, credentials, }) => {
|
|
|
93
103
|
client_id: clientId,
|
|
94
104
|
redirect_uri: redirectUri,
|
|
95
105
|
scope,
|
|
96
|
-
state:
|
|
106
|
+
state: oauthState,
|
|
97
107
|
code_challenge: codeChallenge,
|
|
98
108
|
code_challenge_method: "S256",
|
|
99
109
|
}).toString()}`;
|
|
100
110
|
log.info("Opening your browser to log in.");
|
|
101
111
|
log.info("If it doesn't open, visit:", authUrl);
|
|
102
112
|
open(authUrl);
|
|
103
|
-
// Track the timeout timer so we can cancel it after login succeeds
|
|
104
113
|
let timeoutTimer;
|
|
105
114
|
try {
|
|
106
115
|
await spinPromise(Promise.race([
|
|
@@ -113,21 +122,17 @@ const login = async ({ timeoutMs = LOGIN_TIMEOUT_MS, credentials, }) => {
|
|
|
113
122
|
]), "Waiting for you to login and authorize");
|
|
114
123
|
}
|
|
115
124
|
finally {
|
|
116
|
-
// Clear the timeout timer to prevent it from keeping the process alive
|
|
117
125
|
if (timeoutTimer) {
|
|
118
126
|
clearTimeout(timeoutTimer);
|
|
119
127
|
}
|
|
120
|
-
// Remove signal handlers
|
|
121
128
|
process.off("SIGINT", cleanup);
|
|
122
129
|
process.off("SIGTERM", cleanup);
|
|
123
|
-
// Close server with timeout and force-close connections if needed
|
|
124
130
|
await new Promise((resolve) => {
|
|
125
131
|
const timeout = setTimeout(() => {
|
|
126
132
|
log.info("Server close timed out, forcing connection shutdown...");
|
|
127
|
-
// Force close all connections
|
|
128
133
|
connections.forEach((conn) => conn.destroy());
|
|
129
134
|
resolve();
|
|
130
|
-
}, 1000);
|
|
135
|
+
}, 1000);
|
|
131
136
|
server.close(() => {
|
|
132
137
|
clearTimeout(timeout);
|
|
133
138
|
resolve();
|
|
@@ -147,37 +152,10 @@ const login = async ({ timeoutMs = LOGIN_TIMEOUT_MS, credentials, }) => {
|
|
|
147
152
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
148
153
|
},
|
|
149
154
|
});
|
|
150
|
-
let targetStorage;
|
|
151
|
-
if (getLoginStorageMode() === "config") {
|
|
152
|
-
const { upgrade } = await inquirer.prompt([
|
|
153
|
-
{
|
|
154
|
-
type: "confirm",
|
|
155
|
-
name: "upgrade",
|
|
156
|
-
message: "Would you like to upgrade to system keychain storage? " +
|
|
157
|
-
"This is recommended to securely store your credentials. " +
|
|
158
|
-
"However, note that older SDK/CLI versions will NOT be able to read these credentials, " +
|
|
159
|
-
"so you will want to upgrade them to the latest version.",
|
|
160
|
-
default: true,
|
|
161
|
-
},
|
|
162
|
-
]);
|
|
163
|
-
targetStorage = upgrade ? "keychain" : "config";
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
targetStorage = "keychain";
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
await updateLogin(data, { storage: targetStorage });
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
if (targetStorage === "keychain") {
|
|
173
|
-
log.warn(`Could not store credentials in system keychain. Storing in plaintext at ${getConfigPath()}.`);
|
|
174
|
-
await updateLogin(data, { storage: "config" });
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
throw err;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
155
|
log.info("Token exchange completed successfully");
|
|
181
|
-
return
|
|
182
|
-
|
|
183
|
-
|
|
156
|
+
return {
|
|
157
|
+
accessToken: data.access_token,
|
|
158
|
+
refreshToken: data.refresh_token,
|
|
159
|
+
expiresIn: data.expires_in,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function sleep(ms) {
|
|
2
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
+
}
|
|
4
|
+
export async function withRetry({ action, attempts = 3, initialDelayMs = 100, }) {
|
|
5
|
+
if (attempts <= 0) {
|
|
6
|
+
throw new Error("withRetry: attempts must be greater than 0");
|
|
7
|
+
}
|
|
8
|
+
let lastError;
|
|
9
|
+
for (let i = 0; i < attempts; i++) {
|
|
10
|
+
try {
|
|
11
|
+
return await action();
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
lastError = err;
|
|
15
|
+
if (i < attempts - 1) {
|
|
16
|
+
await sleep(initialDelayMs * 2 ** i);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
throw lastError;
|
|
21
|
+
}
|