zapier-platform-core 18.5.1 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/auth-template/get-auth-template.js +981 -0
- package/src/auth-template/http-capture.js +141 -0
- package/src/auth-template/legacy-scripting.js +219 -0
- package/src/auth-template/render-auth-template.js +336 -0
- package/src/create-command-handler.js +4 -0
- package/types/schemas.generated.d.ts +1 -1
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const applyMiddleware = require('../middleware');
|
|
4
|
+
const ensureArray = require('../tools/ensure-array');
|
|
5
|
+
|
|
6
|
+
// before middlewares
|
|
7
|
+
const addBasicAuthHeader = require('../http-middlewares/before/add-basic-auth-header');
|
|
8
|
+
const addQueryParams = require('../http-middlewares/before/add-query-params');
|
|
9
|
+
const createInjectInputMiddleware = require('../http-middlewares/before/inject-input');
|
|
10
|
+
const prepareRequest = require('../http-middlewares/before/prepare-request');
|
|
11
|
+
const sanitizeHeaders = require('../http-middlewares/before/sanatize-headers');
|
|
12
|
+
|
|
13
|
+
const { REPLACE_CURLIES } = require('../constants');
|
|
14
|
+
const { withHttpCapture } = require('./http-capture');
|
|
15
|
+
const { buildLegacyScripting, loadLegacyZap } = require('./legacy-scripting');
|
|
16
|
+
|
|
17
|
+
const errors = require('../errors');
|
|
18
|
+
|
|
19
|
+
// --- Helpers ---
|
|
20
|
+
|
|
21
|
+
// Opaque sentinels survive core's curly-stripping (normalizeEmptyParamFields)
|
|
22
|
+
// and any stringification middleware does. We embed them into placeholder
|
|
23
|
+
// authData / proxied process.env, then convert back to {{curlies}} on the
|
|
24
|
+
// way out (cleanTemplate). Lowercase-underscore markers are URL-safe in
|
|
25
|
+
// hostnames AND querystrings, so a sentinel survives substitution into
|
|
26
|
+
// any URL position without breaking `new URL(...)` parsing — which is
|
|
27
|
+
// what extractTemplate uses to recover params after addQueryParams.
|
|
28
|
+
const AUTH_SENTINEL_OPEN = '__placeholder_auth__';
|
|
29
|
+
const ENV_SENTINEL_OPEN = '__placeholder_env__';
|
|
30
|
+
const SENTINEL_CLOSE = '__end_placeholder__';
|
|
31
|
+
const wrapAuthSentinel = (key) =>
|
|
32
|
+
`${AUTH_SENTINEL_OPEN}${key}${SENTINEL_CLOSE}`;
|
|
33
|
+
const wrapEnvSentinel = (key) => `${ENV_SENTINEL_OPEN}${key}${SENTINEL_CLOSE}`;
|
|
34
|
+
const AUTH_SENTINEL_RE = /__placeholder_auth__(.+?)__end_placeholder__/g;
|
|
35
|
+
const ENV_SENTINEL_RE = /__placeholder_env__(.+?)__end_placeholder__/g;
|
|
36
|
+
|
|
37
|
+
// Walk a template and replace sentinels with their {{curly}} equivalents.
|
|
38
|
+
// Returns a new object; the input is not mutated.
|
|
39
|
+
const sentinelsToCurlies = (value) => {
|
|
40
|
+
if (typeof value === 'string') {
|
|
41
|
+
return value
|
|
42
|
+
.replace(AUTH_SENTINEL_RE, (_, k) => `{{bundle.authData.${k}}}`)
|
|
43
|
+
.replace(ENV_SENTINEL_RE, (_, k) => `{{process.env.${k}}}`);
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.map(sentinelsToCurlies);
|
|
47
|
+
}
|
|
48
|
+
if (value && typeof value === 'object') {
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const [k, v] of Object.entries(value)) {
|
|
51
|
+
out[k] = sentinelsToCurlies(v);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const hasAuthPlaceholders = (obj) => {
|
|
59
|
+
const s = JSON.stringify(obj);
|
|
60
|
+
return (
|
|
61
|
+
s.includes(AUTH_SENTINEL_OPEN) ||
|
|
62
|
+
s.includes(ENV_SENTINEL_OPEN) ||
|
|
63
|
+
/\{\{\s*bundle\.authData\./.test(s) ||
|
|
64
|
+
/\{\{\s*process\.env\./.test(s)
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Check if auth field placeholders were consumed by encoding (e.g.,
|
|
69
|
+
// base64). Returns true if the template has placeholders but none of
|
|
70
|
+
// them are bundle.authData, AND the app has declared auth fields that
|
|
71
|
+
// should have survived.
|
|
72
|
+
// Build a supported:true result, but check if auth fields were consumed
|
|
73
|
+
// by encoding (e.g., base64). If so, override to supported:false.
|
|
74
|
+
//
|
|
75
|
+
// We only flag as consumed when the developer *explicitly declared*
|
|
76
|
+
// auth.fields. Implicit standard fields (basic's username/password,
|
|
77
|
+
// oauth2's access_token, etc.) aren't a strong "I expect this in the
|
|
78
|
+
// request" signal — apps may use process.env exclusively and never
|
|
79
|
+
// reference standard fields, which would be a false positive here.
|
|
80
|
+
const supportedResult = (authType, source, template, auth) => {
|
|
81
|
+
if (template && Object.keys(template).length > 0) {
|
|
82
|
+
const s = JSON.stringify(template);
|
|
83
|
+
const hasAuthData = /\{\{\s*bundle\.authData\./.test(s);
|
|
84
|
+
const hasProcessEnv = /\{\{\s*process\.env\./.test(s);
|
|
85
|
+
const hasDeclaredFields =
|
|
86
|
+
auth && auth.fields && auth.fields.some((f) => f.key);
|
|
87
|
+
if (hasProcessEnv && !hasAuthData && hasDeclaredFields) {
|
|
88
|
+
return { supported: false, reason: 'auth_fields_consumed', authType };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { supported: true, authType, source, template };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Remove empty headers/params/body from a template object and convert
|
|
95
|
+
// any internal sentinels back to user-facing {{curly}} placeholders.
|
|
96
|
+
const cleanTemplate = (template) => {
|
|
97
|
+
const cleaned = {};
|
|
98
|
+
if (template.headers && Object.keys(template.headers).length > 0) {
|
|
99
|
+
cleaned.headers = sentinelsToCurlies(template.headers);
|
|
100
|
+
}
|
|
101
|
+
if (template.params && Object.keys(template.params).length > 0) {
|
|
102
|
+
cleaned.params = sentinelsToCurlies(template.params);
|
|
103
|
+
}
|
|
104
|
+
if (template.body && Object.keys(template.body).length > 0) {
|
|
105
|
+
cleaned.body = sentinelsToCurlies(template.body);
|
|
106
|
+
}
|
|
107
|
+
return cleaned;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Token-like authData keys that session-auth integrations populate at
|
|
111
|
+
// runtime via `sessionConfig.perform` without declaring them in
|
|
112
|
+
// `authentication.fields`. We add placeholders for these so middleware
|
|
113
|
+
// reading `bundle.authData.<key>` produces a template entry.
|
|
114
|
+
const SESSION_AUTH_COMMON_KEYS = [
|
|
115
|
+
'PHPSESSID',
|
|
116
|
+
'accessToken',
|
|
117
|
+
'access_token',
|
|
118
|
+
'apiToken',
|
|
119
|
+
'refresh_token',
|
|
120
|
+
'sessionKey',
|
|
121
|
+
'sessionToken',
|
|
122
|
+
'token',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// Build placeholder authData where each value is an opaque sentinel
|
|
126
|
+
// string. Sentinels survive core's normalize/curly-stripping and any
|
|
127
|
+
// stringification middleware does, so the captured request still
|
|
128
|
+
// contains them verbatim. cleanTemplate converts sentinels back to
|
|
129
|
+
// {{bundle.authData.X}} on the way out.
|
|
130
|
+
const buildPlaceholderAuthData = (auth) => {
|
|
131
|
+
const authData = {};
|
|
132
|
+
|
|
133
|
+
for (const field of auth.fields || []) {
|
|
134
|
+
if (field.key) {
|
|
135
|
+
authData[field.key] = wrapAuthSentinel(field.key);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Standard fields for known auth types (if not already declared)
|
|
140
|
+
if (auth.type === 'basic') {
|
|
141
|
+
authData.username = authData.username || wrapAuthSentinel('username');
|
|
142
|
+
authData.password = authData.password || wrapAuthSentinel('password');
|
|
143
|
+
}
|
|
144
|
+
if (auth.type === 'oauth2') {
|
|
145
|
+
authData.access_token =
|
|
146
|
+
authData.access_token || wrapAuthSentinel('access_token');
|
|
147
|
+
if (
|
|
148
|
+
auth.oauth2Config &&
|
|
149
|
+
auth.oauth2Config.autoRefresh &&
|
|
150
|
+
auth.oauth2Config.refreshAccessToken
|
|
151
|
+
) {
|
|
152
|
+
authData.refresh_token =
|
|
153
|
+
authData.refresh_token || wrapAuthSentinel('refresh_token');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (auth.type === 'oauth1') {
|
|
157
|
+
authData.oauth_token =
|
|
158
|
+
authData.oauth_token || wrapAuthSentinel('oauth_token');
|
|
159
|
+
authData.oauth_token_secret =
|
|
160
|
+
authData.oauth_token_secret || wrapAuthSentinel('oauth_token_secret');
|
|
161
|
+
}
|
|
162
|
+
if (
|
|
163
|
+
auth.type === 'custom' &&
|
|
164
|
+
auth.customConfig &&
|
|
165
|
+
auth.customConfig.sendCode != null
|
|
166
|
+
) {
|
|
167
|
+
authData.code = authData.code || wrapAuthSentinel('code');
|
|
168
|
+
}
|
|
169
|
+
// Session auth has no schema-defined standard fields, but some apps
|
|
170
|
+
// stash their token under conventional undeclared names (set at
|
|
171
|
+
// runtime by the session auth flow). Add placeholders for these so
|
|
172
|
+
// middleware that reads `bundle.authData.<key>` can still produce a
|
|
173
|
+
// template.
|
|
174
|
+
if (auth.type === 'session') {
|
|
175
|
+
for (const key of SESSION_AUTH_COMMON_KEYS) {
|
|
176
|
+
authData[key] = authData[key] || wrapAuthSentinel(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return authData;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Check if template A is a superset of template B (all keys in B exist in A
|
|
184
|
+
// with the same values, but A may have extra keys).
|
|
185
|
+
const isSuperset = (a, b) => {
|
|
186
|
+
if (!b || Object.keys(b).length === 0) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
for (const section of ['headers', 'params', 'body']) {
|
|
190
|
+
if (!b[section]) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (!a[section]) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
for (const [k, v] of Object.entries(b[section])) {
|
|
197
|
+
if (a[section][k] !== v) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Wrap placeholderAuthData in a Proxy that returns truthy values for any
|
|
206
|
+
// undeclared key accessed by middleware. Used for divergence detection:
|
|
207
|
+
// if middleware branches on undeclared authData fields, the Proxy run will
|
|
208
|
+
// produce a different template than the plain run.
|
|
209
|
+
const buildProxyAuthData = (placeholderAuthData) =>
|
|
210
|
+
new Proxy(placeholderAuthData, {
|
|
211
|
+
get(target, prop) {
|
|
212
|
+
if (prop in target) {
|
|
213
|
+
return target[prop];
|
|
214
|
+
}
|
|
215
|
+
// Symbol properties (e.g. Symbol.toPrimitive) and internal props should pass through
|
|
216
|
+
if (typeof prop === 'symbol') {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
return `__undeclared_${prop}__`;
|
|
220
|
+
},
|
|
221
|
+
has(target, prop) {
|
|
222
|
+
// Make `'key' in authData` return true for any string key
|
|
223
|
+
return typeof prop === 'string' || prop in target;
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Check if two templates are structurally equal (same keys and values).
|
|
228
|
+
const templatesEqual = (a, b) =>
|
|
229
|
+
JSON.stringify(cleanTemplate(a)) === JSON.stringify(cleanTemplate(b));
|
|
230
|
+
|
|
231
|
+
// Run fn with process.env proxied to return placeholders for unknown vars.
|
|
232
|
+
// Concurrent withProxiedEnv calls (e.g., parallel URL probe runs) must
|
|
233
|
+
// share the same proxy — naive "save current; restore current" would let
|
|
234
|
+
// the inner call save the outer's Proxy and "restore" to it, leaking the
|
|
235
|
+
// Proxy past the outermost finally.
|
|
236
|
+
let envProxyDepth = 0;
|
|
237
|
+
let realOrigEnv = null;
|
|
238
|
+
const withProxiedEnv = async (fn) => {
|
|
239
|
+
if (envProxyDepth === 0) {
|
|
240
|
+
realOrigEnv = process.env;
|
|
241
|
+
process.env = new Proxy(realOrigEnv, {
|
|
242
|
+
get(target, prop) {
|
|
243
|
+
if (prop in target) {
|
|
244
|
+
return target[prop];
|
|
245
|
+
}
|
|
246
|
+
if (typeof prop === 'symbol') {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
return wrapEnvSentinel(prop);
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
envProxyDepth++;
|
|
254
|
+
try {
|
|
255
|
+
return await fn();
|
|
256
|
+
} finally {
|
|
257
|
+
envProxyDepth--;
|
|
258
|
+
if (envProxyDepth === 0) {
|
|
259
|
+
process.env = realOrigEnv;
|
|
260
|
+
realOrigEnv = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const buildSyntheticInput = (input, placeholderAuthData) => ({
|
|
266
|
+
_zapier: {
|
|
267
|
+
...input._zapier,
|
|
268
|
+
event: {
|
|
269
|
+
...input._zapier.event,
|
|
270
|
+
bundle: {
|
|
271
|
+
authData: placeholderAuthData,
|
|
272
|
+
inputData: {},
|
|
273
|
+
meta: {},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Create a String-like object whose comparison methods always return a fixed
|
|
280
|
+
// truthy/falsy result. Used to detect middleware that branches on request.url.
|
|
281
|
+
const createUrlProbe = (baseUrl, matchAll) => {
|
|
282
|
+
const s = new String(baseUrl); // eslint-disable-line no-new-wrappers
|
|
283
|
+
s.includes = () => matchAll;
|
|
284
|
+
s.startsWith = () => matchAll;
|
|
285
|
+
s.endsWith = () => matchAll;
|
|
286
|
+
s.indexOf = () => (matchAll ? 0 : -1);
|
|
287
|
+
s.search = () => (matchAll ? 0 : -1);
|
|
288
|
+
s.match = () => (matchAll ? [baseUrl] : null);
|
|
289
|
+
return s;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Extract headers/params/body from a captured request, stripping defaults.
|
|
293
|
+
const extractTemplate = (req) => {
|
|
294
|
+
const template = {};
|
|
295
|
+
|
|
296
|
+
if (req.headers) {
|
|
297
|
+
const headers = { ...req.headers };
|
|
298
|
+
// Strip transport-level headers that shouldn't be in the auth template
|
|
299
|
+
for (const key of Object.keys(headers)) {
|
|
300
|
+
const lower = key.toLowerCase();
|
|
301
|
+
if (lower === 'content-length') {
|
|
302
|
+
delete headers[key];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (Object.keys(headers).length > 0) {
|
|
306
|
+
template.headers = headers;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Check explicit params first, then extract from URL query string
|
|
311
|
+
// (addQueryParams middleware moves params into the URL).
|
|
312
|
+
const params =
|
|
313
|
+
req.params && Object.keys(req.params).length > 0 ? { ...req.params } : {};
|
|
314
|
+
|
|
315
|
+
if (Object.keys(params).length === 0 && req.url) {
|
|
316
|
+
try {
|
|
317
|
+
const parsed = new URL(req.url);
|
|
318
|
+
for (const [k, v] of parsed.searchParams.entries()) {
|
|
319
|
+
// Keep params that carry auth placeholders (sentinels survive
|
|
320
|
+
// normalize; legacy {{curlies}} may also appear in user-written
|
|
321
|
+
// requestTemplates that bypass substitution). Skip non-auth
|
|
322
|
+
// literals like trigger-specific filter params.
|
|
323
|
+
if (
|
|
324
|
+
(typeof v === 'string' && v.includes(AUTH_SENTINEL_OPEN)) ||
|
|
325
|
+
(typeof v === 'string' && v.includes(ENV_SENTINEL_OPEN)) ||
|
|
326
|
+
/\{\{bundle\.authData\./.test(v) ||
|
|
327
|
+
/\{\{process\.env\./.test(v)
|
|
328
|
+
) {
|
|
329
|
+
params[k] = v;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} catch {
|
|
333
|
+
// URL might have unresolved placeholders
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (Object.keys(params).length > 0) {
|
|
338
|
+
template.params = params;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (req.body) {
|
|
342
|
+
template.body = req.body;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return template;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Stub z object used for pipeline capture and test function survival.
|
|
349
|
+
const createStubZ = (compiledApp, cachedZap) => {
|
|
350
|
+
const Zap = cachedZap !== undefined ? cachedZap : loadLegacyZap(compiledApp);
|
|
351
|
+
|
|
352
|
+
const stubRequest = async () => ({
|
|
353
|
+
status: 200,
|
|
354
|
+
headers: {},
|
|
355
|
+
data: {},
|
|
356
|
+
content: '{}',
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const stubZ = {
|
|
360
|
+
console: { log: () => {}, error: () => {}, warn: () => {} },
|
|
361
|
+
errors,
|
|
362
|
+
JSON: { parse: JSON.parse, stringify: JSON.stringify },
|
|
363
|
+
legacyScripting: buildLegacyScripting(
|
|
364
|
+
compiledApp,
|
|
365
|
+
(req) => stubZ.request(req),
|
|
366
|
+
Zap,
|
|
367
|
+
),
|
|
368
|
+
request: stubRequest,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
return stubZ;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// --- Survival routines ---
|
|
375
|
+
|
|
376
|
+
// Run placeholder authData through the beforeRequest middleware pipeline.
|
|
377
|
+
// Captures the prepared request right before it would be sent over HTTP.
|
|
378
|
+
// Returns { template, error? }.
|
|
379
|
+
const runMiddlewareSurvival = async (
|
|
380
|
+
compiledApp,
|
|
381
|
+
input,
|
|
382
|
+
auth,
|
|
383
|
+
placeholderAuthData,
|
|
384
|
+
{ url = 'https://example.com', urlProbe, reqOverrides = {}, cachedZap } = {},
|
|
385
|
+
) => {
|
|
386
|
+
const syntheticInput = buildSyntheticInput(input, placeholderAuthData);
|
|
387
|
+
|
|
388
|
+
const httpBefores = [
|
|
389
|
+
createInjectInputMiddleware(syntheticInput),
|
|
390
|
+
prepareRequest,
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
// When a urlProbe is provided, inject it after prepareRequest (which
|
|
394
|
+
// stringifies the URL) but before the app's beforeRequest middleware.
|
|
395
|
+
if (urlProbe) {
|
|
396
|
+
httpBefores.push((req) => {
|
|
397
|
+
req.url = urlProbe;
|
|
398
|
+
return req;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
httpBefores.push(...ensureArray(compiledApp.beforeRequest));
|
|
403
|
+
|
|
404
|
+
// After the app's beforeRequest runs, unwrap any urlProbe back to a plain
|
|
405
|
+
// string. The probe's overridden comparison methods are intended to perturb
|
|
406
|
+
// the app's beforeRequest only — leaving them in place perturbs downstream
|
|
407
|
+
// core middlewares too (e.g., addQueryParams checks `url.includes('?')` to
|
|
408
|
+
// pick its separator), which produces false-positive URL divergence.
|
|
409
|
+
if (urlProbe) {
|
|
410
|
+
httpBefores.push((req) => {
|
|
411
|
+
if (req.url && typeof req.url !== 'string') {
|
|
412
|
+
req.url = String(req.url);
|
|
413
|
+
}
|
|
414
|
+
return req;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (auth.type === 'basic') {
|
|
419
|
+
httpBefores.push(addBasicAuthHeader);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
httpBefores.push(sanitizeHeaders);
|
|
423
|
+
httpBefores.push(addQueryParams);
|
|
424
|
+
|
|
425
|
+
let capturedReq = null;
|
|
426
|
+
const captureFunction = (preparedReq) => {
|
|
427
|
+
capturedReq = preparedReq;
|
|
428
|
+
return Promise.resolve({
|
|
429
|
+
status: 200,
|
|
430
|
+
headers: {},
|
|
431
|
+
getHeader: () => undefined,
|
|
432
|
+
content: '{}',
|
|
433
|
+
data: {},
|
|
434
|
+
request: preparedReq,
|
|
435
|
+
});
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const stubZ = createStubZ(compiledApp, cachedZap);
|
|
439
|
+
const syntheticBundle = {
|
|
440
|
+
authData: placeholderAuthData,
|
|
441
|
+
inputData: {},
|
|
442
|
+
meta: {},
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const client = applyMiddleware(httpBefores, [], captureFunction, {
|
|
446
|
+
skipEnvelope: true,
|
|
447
|
+
extraArgs: [stubZ, syntheticBundle],
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
await withProxiedEnv(() =>
|
|
452
|
+
client({
|
|
453
|
+
method: 'GET',
|
|
454
|
+
headers: {},
|
|
455
|
+
params: {},
|
|
456
|
+
...reqOverrides,
|
|
457
|
+
url,
|
|
458
|
+
merge: true,
|
|
459
|
+
[REPLACE_CURLIES]: true,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return { template: {}, error: err.message };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (!capturedReq) {
|
|
467
|
+
return { template: {} };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return { template: extractTemplate(capturedReq) };
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Run placeholder authData through authentication.test (when it's a function).
|
|
474
|
+
// Stubs z.request AND monkey-patches http/https/fetch to capture outbound requests.
|
|
475
|
+
// Returns { template, requestMade, error? }.
|
|
476
|
+
const runTestFunctionSurvival = async (
|
|
477
|
+
testFn,
|
|
478
|
+
placeholderAuthData,
|
|
479
|
+
compiledApp,
|
|
480
|
+
input,
|
|
481
|
+
) => {
|
|
482
|
+
let capturedReq = null;
|
|
483
|
+
|
|
484
|
+
const capture = (req) => {
|
|
485
|
+
if (!capturedReq) {
|
|
486
|
+
capturedReq = req;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const auth = compiledApp.authentication || {};
|
|
491
|
+
const syntheticInput = buildSyntheticInput(input, placeholderAuthData);
|
|
492
|
+
|
|
493
|
+
const httpBefores = [
|
|
494
|
+
createInjectInputMiddleware(syntheticInput),
|
|
495
|
+
prepareRequest,
|
|
496
|
+
...ensureArray(compiledApp.beforeRequest),
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
if (auth.type === 'basic') {
|
|
500
|
+
httpBefores.push(addBasicAuthHeader);
|
|
501
|
+
}
|
|
502
|
+
httpBefores.push(sanitizeHeaders);
|
|
503
|
+
httpBefores.push(addQueryParams);
|
|
504
|
+
|
|
505
|
+
const captureFunction = (preparedReq) => {
|
|
506
|
+
capture(preparedReq);
|
|
507
|
+
return Promise.resolve({
|
|
508
|
+
status: 200,
|
|
509
|
+
headers: {},
|
|
510
|
+
getHeader: () => undefined,
|
|
511
|
+
content: '{}',
|
|
512
|
+
data: {},
|
|
513
|
+
request: preparedReq,
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const stubZ = createStubZ(compiledApp);
|
|
518
|
+
const syntheticBundle = {
|
|
519
|
+
authData: placeholderAuthData,
|
|
520
|
+
inputData: {},
|
|
521
|
+
meta: {},
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const client = applyMiddleware(httpBefores, [], captureFunction, {
|
|
525
|
+
skipEnvelope: true,
|
|
526
|
+
extraArgs: [stubZ, syntheticBundle],
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
stubZ.request = async (reqOrUrl) => {
|
|
530
|
+
const req =
|
|
531
|
+
typeof reqOrUrl === 'string' ? { url: reqOrUrl } : { ...reqOrUrl };
|
|
532
|
+
// Run through the beforeRequest middleware pipeline
|
|
533
|
+
const response = await client({
|
|
534
|
+
...req,
|
|
535
|
+
method: req.method || 'GET',
|
|
536
|
+
headers: req.headers || {},
|
|
537
|
+
params: req.params || {},
|
|
538
|
+
merge: true,
|
|
539
|
+
[REPLACE_CURLIES]: true,
|
|
540
|
+
});
|
|
541
|
+
return {
|
|
542
|
+
...response,
|
|
543
|
+
throwForStatus: () => {},
|
|
544
|
+
json: {},
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const bundle = {
|
|
549
|
+
authData: placeholderAuthData,
|
|
550
|
+
inputData: {},
|
|
551
|
+
meta: {},
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
await withHttpCapture(capture, () =>
|
|
556
|
+
withProxiedEnv(() => testFn(stubZ, bundle)),
|
|
557
|
+
);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
// If a request was captured before the error, use its template.
|
|
560
|
+
// Many test functions crash parsing the stub response (e.g.,
|
|
561
|
+
// accessing response.data.emails[0]) — that's fine, we already
|
|
562
|
+
// have what we need.
|
|
563
|
+
if (capturedReq) {
|
|
564
|
+
return { template: extractTemplate(capturedReq), requestMade: true };
|
|
565
|
+
}
|
|
566
|
+
return { template: {}, requestMade: false, error: err.message };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (!capturedReq) {
|
|
570
|
+
return { template: {}, requestMade: false };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return { template: extractTemplate(capturedReq), requestMade: true };
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// --- Main command handler ---
|
|
577
|
+
|
|
578
|
+
const getAuthTemplate = async (compiledApp, input) => {
|
|
579
|
+
const auth = compiledApp.authentication;
|
|
580
|
+
const authType = auth ? auth.type : null;
|
|
581
|
+
|
|
582
|
+
// No authentication defined — nothing to inject
|
|
583
|
+
if (!auth) {
|
|
584
|
+
return { supported: true, authType: null, source: 'none', template: {} };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Digest can't be expressed as a static template (per-request nonce).
|
|
588
|
+
// OAuth1 falls through — OAuth1 apps can implement a simplified static
|
|
589
|
+
// template (e.g., Trello).
|
|
590
|
+
if (authType === 'digest') {
|
|
591
|
+
return { supported: false, reason: 'digest', authType };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Basic auth always runs through addBasicAuthHeader, which base64-encodes
|
|
595
|
+
// username:password. The encoding consumes the placeholder strings, so no
|
|
596
|
+
// {{bundle.authData.X}} survives in the captured request. Our template
|
|
597
|
+
// format has no way to express "base64-encode these fields at render
|
|
598
|
+
// time," so basic auth is fundamentally unsupportable here.
|
|
599
|
+
if (authType === 'basic') {
|
|
600
|
+
return { supported: false, reason: 'basic', authType };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const placeholderAuthData = buildPlaceholderAuthData(auth);
|
|
604
|
+
let beforeRequestTemplate;
|
|
605
|
+
let beforeRequestFailed = false;
|
|
606
|
+
|
|
607
|
+
const beforeRequest = ensureArray(compiledApp.beforeRequest);
|
|
608
|
+
|
|
609
|
+
// --- Step 1: requestTemplate (only when there's no beforeRequest) ---
|
|
610
|
+
// If the app declares a requestTemplate AND has no beforeRequest, that IS
|
|
611
|
+
// the auth template — return it directly without running middleware.
|
|
612
|
+
// When beforeRequest also exists, fall through to Step 2: prepareRequest
|
|
613
|
+
// merges requestTemplate into the captured request, so the pipeline sees
|
|
614
|
+
// the union of both contributions.
|
|
615
|
+
const requestTemplate = compiledApp.requestTemplate;
|
|
616
|
+
if (
|
|
617
|
+
beforeRequest.length === 0 &&
|
|
618
|
+
requestTemplate &&
|
|
619
|
+
Object.keys(requestTemplate).length > 0
|
|
620
|
+
) {
|
|
621
|
+
const cleaned = cleanTemplate(requestTemplate);
|
|
622
|
+
// Return requestTemplate if it has auth placeholders or auth-like
|
|
623
|
+
// header names. Skip if it only has non-auth headers (Accept,
|
|
624
|
+
// Content-Type, User-Agent) — auth may come from beforeRequest or
|
|
625
|
+
// authentication.test.
|
|
626
|
+
const hasAuthContent =
|
|
627
|
+
hasAuthPlaceholders(cleaned) ||
|
|
628
|
+
(cleaned.headers &&
|
|
629
|
+
Object.keys(cleaned.headers).some((k) => {
|
|
630
|
+
const lower = k.toLowerCase();
|
|
631
|
+
return (
|
|
632
|
+
lower === 'authorization' ||
|
|
633
|
+
lower.includes('api-key') ||
|
|
634
|
+
lower.includes('apikey') ||
|
|
635
|
+
lower.includes('token')
|
|
636
|
+
);
|
|
637
|
+
})) ||
|
|
638
|
+
(cleaned.params && Object.keys(cleaned.params).length > 0);
|
|
639
|
+
if (Object.keys(cleaned).length > 0 && hasAuthContent) {
|
|
640
|
+
return supportedResult(authType, 'requestTemplate', cleaned, auth);
|
|
641
|
+
}
|
|
642
|
+
// requestTemplate has no auth content — fall through to Step 2
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// --- Step 2: beforeRequest middleware ---
|
|
646
|
+
// Run placeholder authData through the beforeRequest pipeline directly.
|
|
647
|
+
// This captures auth injected by middleware (most common pattern).
|
|
648
|
+
if (beforeRequest.length > 0) {
|
|
649
|
+
const { template, error } = await runMiddlewareSurvival(
|
|
650
|
+
compiledApp,
|
|
651
|
+
input,
|
|
652
|
+
auth,
|
|
653
|
+
placeholderAuthData,
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
if (error) {
|
|
657
|
+
if (!auth.test) {
|
|
658
|
+
return {
|
|
659
|
+
supported: false,
|
|
660
|
+
reason: 'beforeRequest_error',
|
|
661
|
+
authType,
|
|
662
|
+
error,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
// beforeRequest errored but auth.test exists — fall through
|
|
666
|
+
} else {
|
|
667
|
+
if (hasAuthPlaceholders(template)) {
|
|
668
|
+
// Divergence check: authData proxy
|
|
669
|
+
const proxyAuthData = buildProxyAuthData(placeholderAuthData);
|
|
670
|
+
const { template: proxyTemplate, error: proxyError } =
|
|
671
|
+
await runMiddlewareSurvival(compiledApp, input, auth, proxyAuthData);
|
|
672
|
+
|
|
673
|
+
if (proxyError || !templatesEqual(template, proxyTemplate)) {
|
|
674
|
+
if (!auth.test) {
|
|
675
|
+
return {
|
|
676
|
+
supported: false,
|
|
677
|
+
reason: 'beforeRequest_not_static',
|
|
678
|
+
authType,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
// else: fall through to authentication.test
|
|
682
|
+
} else {
|
|
683
|
+
// URL divergence check
|
|
684
|
+
const urlProbeTrue = createUrlProbe('https://example.com', true);
|
|
685
|
+
const urlProbeFalse = createUrlProbe('https://example.com', false);
|
|
686
|
+
const [
|
|
687
|
+
{ template: urlTrueTemplate, error: urlTrueError },
|
|
688
|
+
{ template: urlFalseTemplate, error: urlFalseError },
|
|
689
|
+
] = await Promise.all([
|
|
690
|
+
runMiddlewareSurvival(
|
|
691
|
+
compiledApp,
|
|
692
|
+
input,
|
|
693
|
+
auth,
|
|
694
|
+
placeholderAuthData,
|
|
695
|
+
{
|
|
696
|
+
urlProbe: urlProbeTrue,
|
|
697
|
+
},
|
|
698
|
+
),
|
|
699
|
+
runMiddlewareSurvival(
|
|
700
|
+
compiledApp,
|
|
701
|
+
input,
|
|
702
|
+
auth,
|
|
703
|
+
placeholderAuthData,
|
|
704
|
+
{
|
|
705
|
+
urlProbe: urlProbeFalse,
|
|
706
|
+
},
|
|
707
|
+
),
|
|
708
|
+
]);
|
|
709
|
+
|
|
710
|
+
if (
|
|
711
|
+
urlTrueError ||
|
|
712
|
+
urlFalseError ||
|
|
713
|
+
!templatesEqual(urlTrueTemplate, urlFalseTemplate)
|
|
714
|
+
) {
|
|
715
|
+
// URL-conditional middleware detected. If the app has
|
|
716
|
+
// authentication.test, fall through — the test function uses a
|
|
717
|
+
// real API URL where the middleware will behave normally.
|
|
718
|
+
if (!auth.test) {
|
|
719
|
+
return {
|
|
720
|
+
supported: false,
|
|
721
|
+
reason: 'beforeRequest_not_static',
|
|
722
|
+
authType,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
// else: fall through to authentication.test steps
|
|
726
|
+
} else {
|
|
727
|
+
// beforeRequest succeeded. Store the template — if authentication.test
|
|
728
|
+
// produces a superset (e.g., adds per-operation auth headers from
|
|
729
|
+
// legacy scripting hooks), we'll prefer that instead.
|
|
730
|
+
beforeRequestTemplate = cleanTemplate(template);
|
|
731
|
+
}
|
|
732
|
+
} // end else (proxy check passed)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// No auth placeholders survived (BR consumed them, e.g. base64
|
|
736
|
+
// encoding) — or divergence was detected and we'd fall through if
|
|
737
|
+
// auth.test were available.
|
|
738
|
+
if (!auth.test) {
|
|
739
|
+
return {
|
|
740
|
+
supported: false,
|
|
741
|
+
reason: 'auth_fields_consumed',
|
|
742
|
+
authType,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
} // end else (no error)
|
|
746
|
+
|
|
747
|
+
// beforeRequest couldn't produce a usable template — remember this so
|
|
748
|
+
// that if authentication.test also fails, we return not-supported.
|
|
749
|
+
beforeRequestFailed = !beforeRequestTemplate;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// --- Step 3: authentication.test is an object (request config) ---
|
|
753
|
+
// Run it through the beforeRequest pipeline just like core's
|
|
754
|
+
// executeRequest does, so auth headers/params are included.
|
|
755
|
+
if (auth.test && typeof auth.test !== 'function') {
|
|
756
|
+
const placeholderAuthData = buildPlaceholderAuthData(auth);
|
|
757
|
+
const testReq = auth.test;
|
|
758
|
+
const { template, error } = await runMiddlewareSurvival(
|
|
759
|
+
compiledApp,
|
|
760
|
+
input,
|
|
761
|
+
auth,
|
|
762
|
+
placeholderAuthData,
|
|
763
|
+
{
|
|
764
|
+
url: testReq.url || 'https://example.com',
|
|
765
|
+
reqOverrides: {
|
|
766
|
+
method: testReq.method || 'GET',
|
|
767
|
+
headers: testReq.headers || {},
|
|
768
|
+
params: testReq.params || {},
|
|
769
|
+
body: testReq.body,
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
if (error) {
|
|
775
|
+
return {
|
|
776
|
+
supported: false,
|
|
777
|
+
reason: 'beforeRequest_error',
|
|
778
|
+
authType,
|
|
779
|
+
error,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const testReqOverrides = {
|
|
784
|
+
method: testReq.method || 'GET',
|
|
785
|
+
headers: testReq.headers || {},
|
|
786
|
+
params: testReq.params || {},
|
|
787
|
+
body: testReq.body,
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
if (hasAuthPlaceholders(template)) {
|
|
791
|
+
// Divergence checks: authData proxy + URL probe
|
|
792
|
+
const proxyAuthData = buildProxyAuthData(placeholderAuthData);
|
|
793
|
+
const { template: proxyTemplate, error: proxyError } =
|
|
794
|
+
await runMiddlewareSurvival(compiledApp, input, auth, proxyAuthData, {
|
|
795
|
+
url: testReq.url || 'https://example.com',
|
|
796
|
+
reqOverrides: testReqOverrides,
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
if (proxyError || !templatesEqual(template, proxyTemplate)) {
|
|
800
|
+
return {
|
|
801
|
+
supported: false,
|
|
802
|
+
reason: 'beforeRequest_not_static',
|
|
803
|
+
authType,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// URL divergence check — only when there's beforeRequest middleware
|
|
808
|
+
// that could branch on URL. Skip if no beforeRequest (the test
|
|
809
|
+
// object's own URL/params are static by definition).
|
|
810
|
+
const hasBR = beforeRequest.length > 0;
|
|
811
|
+
const urlProbeTrue = createUrlProbe(
|
|
812
|
+
testReq.url || 'https://example.com',
|
|
813
|
+
true,
|
|
814
|
+
);
|
|
815
|
+
const urlProbeFalse = createUrlProbe(
|
|
816
|
+
testReq.url || 'https://example.com',
|
|
817
|
+
false,
|
|
818
|
+
);
|
|
819
|
+
const [
|
|
820
|
+
{ template: urlTrueTemplate, error: urlTrueError },
|
|
821
|
+
{ template: urlFalseTemplate, error: urlFalseError },
|
|
822
|
+
] = await Promise.all([
|
|
823
|
+
runMiddlewareSurvival(compiledApp, input, auth, placeholderAuthData, {
|
|
824
|
+
urlProbe: urlProbeTrue,
|
|
825
|
+
reqOverrides: testReqOverrides,
|
|
826
|
+
}),
|
|
827
|
+
runMiddlewareSurvival(compiledApp, input, auth, placeholderAuthData, {
|
|
828
|
+
urlProbe: urlProbeFalse,
|
|
829
|
+
reqOverrides: testReqOverrides,
|
|
830
|
+
}),
|
|
831
|
+
]);
|
|
832
|
+
|
|
833
|
+
if (
|
|
834
|
+
hasBR &&
|
|
835
|
+
(urlTrueError ||
|
|
836
|
+
urlFalseError ||
|
|
837
|
+
!templatesEqual(urlTrueTemplate, urlFalseTemplate))
|
|
838
|
+
) {
|
|
839
|
+
return {
|
|
840
|
+
supported: false,
|
|
841
|
+
reason: 'beforeRequest_not_static',
|
|
842
|
+
authType,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return supportedResult(
|
|
847
|
+
authType,
|
|
848
|
+
'authentication.test',
|
|
849
|
+
cleanTemplate(template),
|
|
850
|
+
auth,
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return {
|
|
855
|
+
supported: false,
|
|
856
|
+
reason: 'auth_fields_consumed',
|
|
857
|
+
authType,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// --- Step 4: authentication.test is a function ---
|
|
862
|
+
if (typeof auth.test === 'function') {
|
|
863
|
+
const { template, requestMade, error } = await runTestFunctionSurvival(
|
|
864
|
+
auth.test,
|
|
865
|
+
placeholderAuthData,
|
|
866
|
+
compiledApp,
|
|
867
|
+
input,
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
if (error && !requestMade) {
|
|
871
|
+
// Function crashed before making a request.
|
|
872
|
+
if (beforeRequestFailed) {
|
|
873
|
+
return {
|
|
874
|
+
supported: false,
|
|
875
|
+
reason: 'test_function_error',
|
|
876
|
+
authType,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
// beforeRequest may have a template — handled at the end
|
|
880
|
+
} else if (error) {
|
|
881
|
+
// Request was made but function crashed after. If beforeRequest
|
|
882
|
+
// has a template, prefer that over failing.
|
|
883
|
+
if (!beforeRequestTemplate) {
|
|
884
|
+
return {
|
|
885
|
+
supported: false,
|
|
886
|
+
reason: 'test_function_error',
|
|
887
|
+
authType,
|
|
888
|
+
error,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
} else if (!requestMade) {
|
|
892
|
+
if (beforeRequestFailed) {
|
|
893
|
+
return {
|
|
894
|
+
supported: false,
|
|
895
|
+
reason: 'test_function_no_request',
|
|
896
|
+
authType,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
// beforeRequest may have a template — handled at the end
|
|
900
|
+
} else {
|
|
901
|
+
if (hasAuthPlaceholders(template)) {
|
|
902
|
+
// Divergence check: run again with Proxy authData
|
|
903
|
+
const proxyAuthData = buildProxyAuthData(placeholderAuthData);
|
|
904
|
+
const { template: proxyTemplate, error: proxyError } =
|
|
905
|
+
await runTestFunctionSurvival(
|
|
906
|
+
auth.test,
|
|
907
|
+
proxyAuthData,
|
|
908
|
+
compiledApp,
|
|
909
|
+
input,
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
if (proxyError || !templatesEqual(template, proxyTemplate)) {
|
|
913
|
+
return {
|
|
914
|
+
supported: false,
|
|
915
|
+
reason: 'test_function_not_static',
|
|
916
|
+
authType,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// No URL divergence check here — the test function used a real API
|
|
921
|
+
// URL, so the captured template reflects normal request auth. URL
|
|
922
|
+
// divergence is only checked in the beforeRequest fallback path
|
|
923
|
+
// (which uses a synthetic URL).
|
|
924
|
+
|
|
925
|
+
const testTemplate = cleanTemplate(template);
|
|
926
|
+
|
|
927
|
+
// If beforeRequest also produced a template, pick the richer one.
|
|
928
|
+
// The test function may capture per-operation auth (e.g., legacy
|
|
929
|
+
// scripting hooks) that beforeRequest alone misses.
|
|
930
|
+
if (
|
|
931
|
+
beforeRequestTemplate &&
|
|
932
|
+
!isSuperset(testTemplate, beforeRequestTemplate)
|
|
933
|
+
) {
|
|
934
|
+
return supportedResult(
|
|
935
|
+
authType,
|
|
936
|
+
'beforeRequest',
|
|
937
|
+
beforeRequestTemplate,
|
|
938
|
+
auth,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return supportedResult(
|
|
943
|
+
authType,
|
|
944
|
+
'authentication.test',
|
|
945
|
+
testTemplate,
|
|
946
|
+
auth,
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (beforeRequestTemplate) {
|
|
951
|
+
return supportedResult(
|
|
952
|
+
authType,
|
|
953
|
+
'beforeRequest',
|
|
954
|
+
beforeRequestTemplate,
|
|
955
|
+
auth,
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return {
|
|
960
|
+
supported: false,
|
|
961
|
+
reason: 'auth_fields_consumed',
|
|
962
|
+
authType,
|
|
963
|
+
};
|
|
964
|
+
} // end else (requestMade)
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// No authentication.test captured a request. Use beforeRequestTemplate
|
|
968
|
+
// if available.
|
|
969
|
+
if (beforeRequestTemplate) {
|
|
970
|
+
return supportedResult(
|
|
971
|
+
authType,
|
|
972
|
+
'beforeRequest',
|
|
973
|
+
beforeRequestTemplate,
|
|
974
|
+
auth,
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return { supported: true, authType, source: 'none', template: {} };
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
module.exports = getAuthTemplate;
|