zapier-platform-core 17.7.0 → 17.7.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zapier-platform-core",
|
|
3
|
-
"version": "17.7.
|
|
3
|
+
"version": "17.7.1",
|
|
4
4
|
"description": "The core SDK for CLI apps in the Zapier Developer Platform.",
|
|
5
5
|
"repository": "zapier/zapier-platform",
|
|
6
6
|
"homepage": "https://platform.zapier.com/",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"node-fetch": "2.7.0",
|
|
64
64
|
"oauth-sign": "0.9.0",
|
|
65
65
|
"semver": "7.7.1",
|
|
66
|
-
"zapier-platform-schema": "17.7.
|
|
66
|
+
"zapier-platform-schema": "17.7.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node-fetch": "^2.6.11",
|
|
@@ -2,19 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const querystring = require('querystring');
|
|
5
|
+
|
|
6
|
+
const { scrub, findSensitiveValues } = require('@zapier/secret-scrubber');
|
|
7
|
+
|
|
5
8
|
const { replaceHeaders } = require('./middleware-utils');
|
|
6
9
|
const { FORM_TYPE } = require('../../tools/http');
|
|
7
10
|
const errors = require('../../errors');
|
|
11
|
+
const {
|
|
12
|
+
findSensitiveValuesFromAuthData,
|
|
13
|
+
} = require('../../tools/secret-scrubber');
|
|
8
14
|
|
|
9
|
-
const
|
|
15
|
+
const buildSensitiveValues = (bundle) => {
|
|
16
|
+
const authData = bundle?.authData || {};
|
|
17
|
+
const result = [
|
|
18
|
+
...findSensitiveValuesFromAuthData(authData),
|
|
19
|
+
...findSensitiveValues(process.env),
|
|
20
|
+
];
|
|
21
|
+
return [...new Set(result)];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const _throwForStatus = (response, bundle) => {
|
|
10
25
|
// calling this always throws, regardless of the skipThrowForStatus value
|
|
11
26
|
// eslint-disable-next-line yoda
|
|
12
27
|
if (400 <= response.status && response.status < 600) {
|
|
28
|
+
// Create a cleaned version of the response to avoid sensitive data leaks
|
|
29
|
+
try {
|
|
30
|
+
// Find sensitive values from environment variables and bundle authData
|
|
31
|
+
const sensitiveValues = buildSensitiveValues(bundle);
|
|
32
|
+
if (sensitiveValues.length > 0 && response?.request?.url) {
|
|
33
|
+
response.request.url = scrub(response.request.url, sensitiveValues);
|
|
34
|
+
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
// don't fail the whole request if we can't scrub for some reason
|
|
37
|
+
}
|
|
13
38
|
throw new errors.ResponseError(response);
|
|
14
39
|
}
|
|
15
40
|
};
|
|
16
41
|
|
|
17
|
-
const prepareRawResponse = (resp, request) => {
|
|
42
|
+
const prepareRawResponse = (resp, request, bundle) => {
|
|
18
43
|
// TODO: if !2xx should we go ahead and get response.content for them?
|
|
19
44
|
// retain the response signature for raw control
|
|
20
45
|
const extendedResp = {
|
|
@@ -24,7 +49,7 @@ const prepareRawResponse = (resp, request) => {
|
|
|
24
49
|
const outResp = _.extend(resp, extendedResp, replaceHeaders(resp));
|
|
25
50
|
|
|
26
51
|
outResp.throwForStatus = () => {
|
|
27
|
-
_throwForStatus(outResp);
|
|
52
|
+
_throwForStatus(outResp, bundle);
|
|
28
53
|
};
|
|
29
54
|
|
|
30
55
|
Object.defineProperty(outResp, 'content', {
|
|
@@ -39,7 +64,7 @@ const prepareRawResponse = (resp, request) => {
|
|
|
39
64
|
return outResp;
|
|
40
65
|
};
|
|
41
66
|
|
|
42
|
-
const prepareContentResponse = async (resp, request) => {
|
|
67
|
+
const prepareContentResponse = async (resp, request, bundle) => {
|
|
43
68
|
// TODO: does it make sense to not trim the signature? more equivalence to raw...
|
|
44
69
|
const content = await resp.text();
|
|
45
70
|
|
|
@@ -67,14 +92,14 @@ const prepareContentResponse = async (resp, request) => {
|
|
|
67
92
|
} catch (_e) {}
|
|
68
93
|
|
|
69
94
|
outResp.throwForStatus = () => {
|
|
70
|
-
_throwForStatus(outResp);
|
|
95
|
+
_throwForStatus(outResp, bundle);
|
|
71
96
|
};
|
|
72
97
|
|
|
73
98
|
return outResp;
|
|
74
99
|
};
|
|
75
100
|
|
|
76
101
|
// Provide a standardized plain JS responseObj for common consumption, or raw response for streaming.
|
|
77
|
-
const prepareResponse = (resp) => {
|
|
102
|
+
const prepareResponse = (resp, z, bundle) => {
|
|
78
103
|
const request = resp.input;
|
|
79
104
|
delete resp.input;
|
|
80
105
|
|
|
@@ -82,7 +107,7 @@ const prepareResponse = (resp) => {
|
|
|
82
107
|
? prepareRawResponse
|
|
83
108
|
: prepareContentResponse;
|
|
84
109
|
|
|
85
|
-
return responseFunc(resp, request);
|
|
110
|
+
return responseFunc(resp, request, bundle);
|
|
86
111
|
};
|
|
87
112
|
|
|
88
113
|
module.exports = prepareResponse;
|
|
@@ -15,16 +15,8 @@ const {
|
|
|
15
15
|
SAFE_LOG_KEYS,
|
|
16
16
|
} = require('../constants');
|
|
17
17
|
const { unheader } = require('./http');
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
findSensitiveValues,
|
|
21
|
-
recurseExtract,
|
|
22
|
-
} = require('@zapier/secret-scrubber');
|
|
23
|
-
// not really a public function, but it came from here originally
|
|
24
|
-
const {
|
|
25
|
-
isUrlWithSecrets,
|
|
26
|
-
isSensitiveKey,
|
|
27
|
-
} = require('@zapier/secret-scrubber/lib/convenience');
|
|
18
|
+
const { scrub, findSensitiveValues } = require('@zapier/secret-scrubber');
|
|
19
|
+
const { findSensitiveValuesFromAuthData } = require('./secret-scrubber');
|
|
28
20
|
|
|
29
21
|
// The payload size per request to stream logs. This should be slighly lower
|
|
30
22
|
// than the limit (16 MB) on the server side.
|
|
@@ -32,70 +24,8 @@ const LOG_STREAM_BYTES_LIMIT = 15 * 1024 * 1024;
|
|
|
32
24
|
|
|
33
25
|
const DEFAULT_LOGGER_TIMEOUT = 200;
|
|
34
26
|
|
|
35
|
-
// Will be initialized lazily
|
|
36
|
-
const SAFE_AUTH_DATA_KEYS = new Set();
|
|
37
|
-
|
|
38
27
|
const sleep = promisify(setTimeout);
|
|
39
28
|
|
|
40
|
-
const initSafeAuthDataKeys = () => {
|
|
41
|
-
// An authData key in (safePrefixes x safeSuffixes) is considered safe to log
|
|
42
|
-
// uncensored
|
|
43
|
-
const safePrefixes = [
|
|
44
|
-
'account',
|
|
45
|
-
'bot_user',
|
|
46
|
-
'cloud',
|
|
47
|
-
'cloud_site',
|
|
48
|
-
'company',
|
|
49
|
-
'domain',
|
|
50
|
-
'email',
|
|
51
|
-
'environment',
|
|
52
|
-
'location',
|
|
53
|
-
'org',
|
|
54
|
-
'organization',
|
|
55
|
-
'project',
|
|
56
|
-
'region',
|
|
57
|
-
'scope',
|
|
58
|
-
'scopes',
|
|
59
|
-
'site',
|
|
60
|
-
'subdomain',
|
|
61
|
-
'team',
|
|
62
|
-
'token_type',
|
|
63
|
-
'user',
|
|
64
|
-
'workspace',
|
|
65
|
-
];
|
|
66
|
-
const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
|
|
67
|
-
for (const prefix of safePrefixes) {
|
|
68
|
-
for (const suffix of safeSuffixes) {
|
|
69
|
-
SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const isUrl = (value) => {
|
|
75
|
-
if (!value || typeof value !== 'string') {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
const commonProtocols = [
|
|
79
|
-
'https://',
|
|
80
|
-
'http://',
|
|
81
|
-
'ftp://',
|
|
82
|
-
'ftps://',
|
|
83
|
-
'file://',
|
|
84
|
-
];
|
|
85
|
-
for (const protocol of commonProtocols) {
|
|
86
|
-
if (value.startsWith(protocol)) {
|
|
87
|
-
try {
|
|
88
|
-
// eslint-disable-next-line no-new
|
|
89
|
-
new URL(value);
|
|
90
|
-
return true;
|
|
91
|
-
} catch (e) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
29
|
const MAX_LENGTH = 3500;
|
|
100
30
|
const truncateString = (str) => simpleTruncate(str, MAX_LENGTH, ' [...]');
|
|
101
31
|
|
|
@@ -188,35 +118,12 @@ const attemptFindSecretsInStr = (s, isGettingNewSecret) => {
|
|
|
188
118
|
return findSensitiveValues(parsedRespContent);
|
|
189
119
|
};
|
|
190
120
|
|
|
191
|
-
const isSafeAuthDataKey = (key) => {
|
|
192
|
-
if (SAFE_AUTH_DATA_KEYS.size === 0) {
|
|
193
|
-
initSafeAuthDataKeys();
|
|
194
|
-
}
|
|
195
|
-
return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
|
|
196
|
-
};
|
|
197
|
-
|
|
198
121
|
const buildSensitiveValues = (event, data) => {
|
|
199
122
|
const bundle = event.bundle || {};
|
|
200
123
|
const authData = bundle.authData || {};
|
|
201
|
-
// for the most part, we should censor all the values from authData
|
|
202
|
-
// the exception is safe urls, which should be filtered out - we want those to be logged
|
|
203
|
-
// but, we _should_ censor-no-matter-what sensitive keys, even if their value is a safe url
|
|
204
|
-
// this covers the case where someone's password is a valid url ¯\_(ツ)_/¯
|
|
205
|
-
const sensitiveAuthData = recurseExtract(authData, (key, value) => {
|
|
206
|
-
if (isSensitiveKey(key)) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
if (isSafeAuthDataKey(key)) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
if (isUrl(value) && !isUrlWithSecrets(value)) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
return true;
|
|
216
|
-
});
|
|
217
124
|
|
|
218
125
|
const result = [
|
|
219
|
-
...
|
|
126
|
+
...findSensitiveValuesFromAuthData(authData),
|
|
220
127
|
...findSensitiveValues(process.env),
|
|
221
128
|
...findSensitiveValues(data),
|
|
222
129
|
];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { recurseExtract } = require('@zapier/secret-scrubber');
|
|
4
|
+
const {
|
|
5
|
+
isUrlWithSecrets,
|
|
6
|
+
isSensitiveKey,
|
|
7
|
+
} = require('@zapier/secret-scrubber/lib/convenience');
|
|
8
|
+
|
|
9
|
+
// Will be initialized lazily
|
|
10
|
+
const SAFE_AUTH_DATA_KEYS = new Set();
|
|
11
|
+
|
|
12
|
+
const initSafeAuthDataKeys = () => {
|
|
13
|
+
// An authData key in (safePrefixes x safeSuffixes) is considered safe to log
|
|
14
|
+
// uncensored
|
|
15
|
+
const safePrefixes = [
|
|
16
|
+
'account',
|
|
17
|
+
'bot_user',
|
|
18
|
+
'cloud',
|
|
19
|
+
'cloud_site',
|
|
20
|
+
'company',
|
|
21
|
+
'domain',
|
|
22
|
+
'email',
|
|
23
|
+
'environment',
|
|
24
|
+
'location',
|
|
25
|
+
'org',
|
|
26
|
+
'organization',
|
|
27
|
+
'project',
|
|
28
|
+
'region',
|
|
29
|
+
'scope',
|
|
30
|
+
'scopes',
|
|
31
|
+
'site',
|
|
32
|
+
'subdomain',
|
|
33
|
+
'team',
|
|
34
|
+
'token_type',
|
|
35
|
+
'user',
|
|
36
|
+
'workspace',
|
|
37
|
+
];
|
|
38
|
+
const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
|
|
39
|
+
for (const prefix of safePrefixes) {
|
|
40
|
+
for (const suffix of safeSuffixes) {
|
|
41
|
+
SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isSafeAuthDataKey = (key) => {
|
|
47
|
+
if (SAFE_AUTH_DATA_KEYS.size === 0) {
|
|
48
|
+
initSafeAuthDataKeys();
|
|
49
|
+
}
|
|
50
|
+
return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const isUrl = (value) => {
|
|
54
|
+
if (!value || typeof value !== 'string') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const commonProtocols = [
|
|
58
|
+
'https://',
|
|
59
|
+
'http://',
|
|
60
|
+
'ftp://',
|
|
61
|
+
'ftps://',
|
|
62
|
+
'file://',
|
|
63
|
+
];
|
|
64
|
+
for (const protocol of commonProtocols) {
|
|
65
|
+
if (value.startsWith(protocol)) {
|
|
66
|
+
try {
|
|
67
|
+
// eslint-disable-next-line no-new
|
|
68
|
+
new URL(value);
|
|
69
|
+
return true;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const findSensitiveValuesFromAuthData = (authData) =>
|
|
79
|
+
recurseExtract(authData, (key, value) => {
|
|
80
|
+
// for the most part, we should censor all the values from authData
|
|
81
|
+
// the exception is safe urls, which should be filtered out - we want those to be logged
|
|
82
|
+
// but, we _should_ censor-no-matter-what sensitive keys, even if their value is a safe url
|
|
83
|
+
// this covers the case where someone's password is a valid url ¯\_(ツ)_/¯
|
|
84
|
+
if (isSensitiveKey(key)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (isSafeAuthDataKey(key)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (isUrl(value) && !isUrlWithSecrets(value)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
findSensitiveValuesFromAuthData,
|
|
98
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* files, and/or the schema-to-ts tool and run its CLI to regenerate
|
|
5
5
|
* these typings.
|
|
6
6
|
*
|
|
7
|
-
* zapier-platform-schema version: 17.
|
|
7
|
+
* zapier-platform-schema version: 17.7.1
|
|
8
8
|
* schema-to-ts compiler version: 0.1.0
|
|
9
9
|
*/
|
|
10
10
|
import type {
|
|
@@ -131,7 +131,8 @@ export interface BaseApp {
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Represents a simplified semver string, from `0.0.0` to
|
|
134
|
-
* `999.999.999
|
|
134
|
+
* `999.999.999` with optional simplified label. They need to be
|
|
135
|
+
* case-insensitive unique.
|
|
135
136
|
*/
|
|
136
137
|
export type Version = string;
|
|
137
138
|
|
|
@@ -938,6 +939,12 @@ export interface BasicPollingOperation<
|
|
|
938
939
|
* for the window is exceeded.
|
|
939
940
|
*/
|
|
940
941
|
throttle?: ThrottleObject;
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
945
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
946
|
+
*/
|
|
947
|
+
skipCleanArrayInputData?: boolean;
|
|
941
948
|
}
|
|
942
949
|
|
|
943
950
|
/**
|
|
@@ -1027,6 +1034,12 @@ export interface BasicHookOperation<
|
|
|
1027
1034
|
* this belongs to a resource that has a top-level sample
|
|
1028
1035
|
*/
|
|
1029
1036
|
sample?: Record<string, unknown>;
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1040
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1041
|
+
*/
|
|
1042
|
+
skipCleanArrayInputData?: boolean;
|
|
1030
1043
|
}
|
|
1031
1044
|
|
|
1032
1045
|
/**
|
|
@@ -1088,6 +1101,12 @@ export interface BasicHookToPollOperation<
|
|
|
1088
1101
|
*/
|
|
1089
1102
|
sample?: Record<string, unknown>;
|
|
1090
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1106
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1107
|
+
*/
|
|
1108
|
+
skipCleanArrayInputData?: boolean;
|
|
1109
|
+
|
|
1091
1110
|
/**
|
|
1092
1111
|
* The maximum amount of time to wait between polling requests in
|
|
1093
1112
|
* seconds. Minimum value is 20s and will default to 20 if not set,
|
|
@@ -1159,6 +1178,12 @@ export interface BasicActionOperation {
|
|
|
1159
1178
|
* for the window is exceeded.
|
|
1160
1179
|
*/
|
|
1161
1180
|
throttle?: ThrottleObject;
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1184
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1185
|
+
*/
|
|
1186
|
+
skipCleanArrayInputData?: boolean;
|
|
1162
1187
|
}
|
|
1163
1188
|
|
|
1164
1189
|
/** Represents the fundamental mechanics of a search. */
|
|
@@ -1229,6 +1254,12 @@ export interface BasicSearchOperation<
|
|
|
1229
1254
|
* for the window is exceeded.
|
|
1230
1255
|
*/
|
|
1231
1256
|
throttle?: ThrottleObject;
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1260
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1261
|
+
*/
|
|
1262
|
+
skipCleanArrayInputData?: boolean;
|
|
1232
1263
|
}
|
|
1233
1264
|
|
|
1234
1265
|
/** Represents the fundamental mechanics of a create. */
|
|
@@ -1299,6 +1330,12 @@ export interface BasicCreateOperation<
|
|
|
1299
1330
|
*/
|
|
1300
1331
|
throttle?: ThrottleObject;
|
|
1301
1332
|
|
|
1333
|
+
/**
|
|
1334
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1335
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1336
|
+
*/
|
|
1337
|
+
skipCleanArrayInputData?: boolean;
|
|
1338
|
+
|
|
1302
1339
|
/**
|
|
1303
1340
|
* Currently an **internal-only** feature. Zapier uses this
|
|
1304
1341
|
* configuration for creating objects in bulk with `performBuffer`.
|
|
@@ -1382,6 +1419,12 @@ export interface BasicOperation {
|
|
|
1382
1419
|
* for the window is exceeded.
|
|
1383
1420
|
*/
|
|
1384
1421
|
throttle?: ThrottleObject;
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1425
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1426
|
+
*/
|
|
1427
|
+
skipCleanArrayInputData?: boolean;
|
|
1385
1428
|
}
|
|
1386
1429
|
|
|
1387
1430
|
/**
|
|
@@ -1466,11 +1509,7 @@ export interface PlainOutputField {
|
|
|
1466
1509
|
}
|
|
1467
1510
|
|
|
1468
1511
|
/** An array or collection of input field groups. */
|
|
1469
|
-
export type InputFieldGroups =
|
|
1470
|
-
key: Key;
|
|
1471
|
-
label: string;
|
|
1472
|
-
emphasize: boolean;
|
|
1473
|
-
}[];
|
|
1512
|
+
export type InputFieldGroups = InputFieldGroup[];
|
|
1474
1513
|
|
|
1475
1514
|
/**
|
|
1476
1515
|
* Zapier uses this configuration to ensure this action is performed
|
|
@@ -1681,6 +1720,18 @@ export interface PlainInputField {
|
|
|
1681
1720
|
group?: Key;
|
|
1682
1721
|
}
|
|
1683
1722
|
|
|
1723
|
+
/** Object for visual grouping of input fields. */
|
|
1724
|
+
export interface InputFieldGroup {
|
|
1725
|
+
/** The unique identifier for this group. */
|
|
1726
|
+
key: Key;
|
|
1727
|
+
|
|
1728
|
+
/** The human-readable name for the group. */
|
|
1729
|
+
label?: string;
|
|
1730
|
+
|
|
1731
|
+
/** Whether this group should be visually emphasized in the UI. */
|
|
1732
|
+
emphasize?: boolean;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1684
1735
|
/**
|
|
1685
1736
|
* Reference a resource by key and the data it returns. In the
|
|
1686
1737
|
* format of: `{resource_key}.{foreign_key}(.{human_label_key})`.
|