@testsmith/api-spector 0.3.5 → 0.3.7

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.
@@ -23,14 +23,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  const undici = require("undici");
25
25
  const promises = require("fs/promises");
26
- const authBuilder = require("./auth-builder-CUs9yzOF.js");
27
- const vm = require("vm");
26
+ const handle = require("./handle-BCnNIZZr.js");
28
27
  const crypto = require("crypto");
28
+ const path = require("path");
29
29
  const dayjs = require("dayjs");
30
+ const vm = require("vm");
30
31
  const tv4 = require("tv4");
31
32
  const jsonpathPlus = require("jsonpath-plus");
32
33
  const xmldom = require("@xmldom/xmldom");
33
- const path = require("path");
34
+ const http = require("http");
34
35
  const Ajv = require("ajv");
35
36
  function _interopNamespaceDefault(e) {
36
37
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -48,8 +49,8 @@ function _interopNamespaceDefault(e) {
48
49
  n.default = e;
49
50
  return Object.freeze(n);
50
51
  }
51
- const vm__namespace = /* @__PURE__ */ _interopNamespaceDefault(vm);
52
52
  const crypto__namespace = /* @__PURE__ */ _interopNamespaceDefault(crypto);
53
+ const vm__namespace = /* @__PURE__ */ _interopNamespaceDefault(vm);
53
54
  let globals = {};
54
55
  let currentDir = null;
55
56
  function globalsPath(dir) {
@@ -78,6 +79,171 @@ function setGlobals(next) {
78
79
  function patchGlobals(patch) {
79
80
  globals = { ...globals, ...patch };
80
81
  }
82
+ const MASTER_KEY_ENV = "API_SPECTOR_MASTER_KEY";
83
+ let secretStore = {};
84
+ let secretStorePath = null;
85
+ async function initSecretStore(userDataPath) {
86
+ secretStorePath = path.join(userDataPath, "secrets.json");
87
+ try {
88
+ const raw = await promises.readFile(secretStorePath, "utf8");
89
+ secretStore = JSON.parse(raw);
90
+ } catch {
91
+ secretStore = {};
92
+ }
93
+ }
94
+ async function persistSecretStore() {
95
+ if (!secretStorePath) return;
96
+ await promises.writeFile(secretStorePath, JSON.stringify(secretStore, null, 2), "utf8");
97
+ }
98
+ function getSafeStorage() {
99
+ try {
100
+ const { safeStorage } = require("electron");
101
+ if (typeof safeStorage?.isEncryptionAvailable === "function") return safeStorage;
102
+ return null;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+ function registerSecretHandlers(ipc) {
108
+ handle.handleIpc(ipc, handle.IPC.secret.checkMasterKey, () => {
109
+ return { set: Boolean(process.env[MASTER_KEY_ENV]) };
110
+ });
111
+ handle.handleIpc(ipc, handle.IPC.secret.setMasterKey, (_e, value) => {
112
+ process.env[MASTER_KEY_ENV] = value;
113
+ });
114
+ handle.handleIpc(ipc, handle.IPC.secret.set, async (_e, ref, value) => {
115
+ const ss = getSafeStorage();
116
+ if (!ss || !ss.isEncryptionAvailable()) {
117
+ throw new Error("OS encryption is not available - set the secret via environment variable instead");
118
+ }
119
+ secretStore[ref] = ss.encryptString(value).toString("base64");
120
+ await persistSecretStore();
121
+ });
122
+ }
123
+ function decryptSecret(encrypted, salt, iv, password) {
124
+ const saltBuf = Buffer.from(salt, "base64");
125
+ const ivBuf = Buffer.from(iv, "base64");
126
+ const encBuf = Buffer.from(encrypted, "base64");
127
+ const key = crypto.pbkdf2Sync(password, saltBuf, 1e5, 32, "sha256");
128
+ const authTag = encBuf.subarray(encBuf.length - 16);
129
+ const ciphertext = encBuf.subarray(0, encBuf.length - 16);
130
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, ivBuf);
131
+ decipher.setAuthTag(authTag);
132
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
133
+ }
134
+ async function getSecret(ref) {
135
+ const stored = secretStore[ref];
136
+ if (stored) {
137
+ const ss = getSafeStorage();
138
+ if (ss && ss.isEncryptionAvailable()) {
139
+ try {
140
+ return ss.decryptString(Buffer.from(stored, "base64"));
141
+ } catch {
142
+ }
143
+ }
144
+ }
145
+ return process.env[ref] ?? null;
146
+ }
147
+ let _fakerCache$1 = null;
148
+ async function getFaker$1() {
149
+ if (!_fakerCache$1) _fakerCache$1 = await import("@faker-js/faker");
150
+ return _fakerCache$1.faker;
151
+ }
152
+ let _exprContext = null;
153
+ async function buildDynamicVars() {
154
+ const faker = await getFaker$1();
155
+ const now = dayjs();
156
+ _exprContext = { faker, dayjs };
157
+ return {
158
+ $uuid: faker.string.uuid(),
159
+ $timestamp: String(Date.now()),
160
+ $isoTimestamp: now.toISOString(),
161
+ $randomInt: String(faker.number.int({ min: 0, max: 1e3 })),
162
+ $randomFloat: String(faker.number.float({ min: 0, max: 1e3, fractionDigits: 2 })),
163
+ $randomBoolean: String(faker.datatype.boolean()),
164
+ $randomEmail: faker.internet.email(),
165
+ $randomUsername: faker.internet.username(),
166
+ $randomPassword: faker.internet.password(),
167
+ $randomFullName: faker.person.fullName(),
168
+ $randomFirstName: faker.person.firstName(),
169
+ $randomLastName: faker.person.lastName(),
170
+ $randomWord: faker.lorem.word(),
171
+ $randomPhrase: faker.lorem.sentence(),
172
+ $randomUrl: faker.internet.url(),
173
+ $randomIp: faker.internet.ip(),
174
+ $randomHexColor: faker.color.rgb({ format: "hex", casing: "lower" })
175
+ };
176
+ }
177
+ function interpolate(str, vars) {
178
+ return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
179
+ const trimmed = key.trim();
180
+ if (trimmed in vars) return vars[trimmed];
181
+ if (_exprContext && (trimmed.includes(".") || trimmed.includes("("))) {
182
+ try {
183
+ const result = vm__namespace.runInNewContext(trimmed, _exprContext);
184
+ if (result !== void 0 && result !== null) return String(result);
185
+ } catch {
186
+ }
187
+ }
188
+ return match;
189
+ });
190
+ }
191
+ function buildUrl(baseUrl, params, vars) {
192
+ const templateTokens = /* @__PURE__ */ new Set();
193
+ baseUrl.replace(/\{\{([^}]+)\}\}/g, (_m, name) => {
194
+ templateTokens.add(String(name).trim());
195
+ return "";
196
+ });
197
+ const enabled = (params ?? []).filter((p) => p.enabled && p.key);
198
+ const pathRows = [];
199
+ const queryRows = [];
200
+ for (const p of enabled) {
201
+ const isPath = p.paramType === "path" || templateTokens.has(p.key);
202
+ if (isPath) pathRows.push(p);
203
+ else queryRows.push(p);
204
+ }
205
+ const mergedVars = pathRows.length ? {
206
+ ...vars,
207
+ ...Object.fromEntries(pathRows.map((p) => [p.key, interpolate(p.value, vars)]))
208
+ } : vars;
209
+ const url = interpolate(baseUrl, mergedVars);
210
+ if (!queryRows.length) return url;
211
+ const sep = url.includes("?") ? "&" : "?";
212
+ const qs = queryRows.map((p) => `${encodeURIComponent(interpolate(p.key, vars))}=${encodeURIComponent(interpolate(p.value, vars))}`).join("&");
213
+ return url + sep + qs;
214
+ }
215
+ async function buildEnvVars(environment) {
216
+ const vars = {};
217
+ if (!environment) return vars;
218
+ const masterKey = process.env["API_SPECTOR_MASTER_KEY"];
219
+ for (const v of environment.variables) {
220
+ if (!v.enabled) continue;
221
+ if (v.envRef) {
222
+ const envValue = process.env[v.envRef];
223
+ if (envValue !== void 0) vars[v.key] = envValue;
224
+ } else if (v.secret && v.secretEncrypted && v.secretSalt && v.secretIv) {
225
+ if (masterKey) {
226
+ try {
227
+ vars[v.key] = decryptSecret(v.secretEncrypted, v.secretSalt, v.secretIv, masterKey);
228
+ } catch {
229
+ }
230
+ }
231
+ if (vars[v.key] === void 0 && process.env[v.key] !== void 0) {
232
+ vars[v.key] = process.env[v.key];
233
+ }
234
+ } else if (v.secret) {
235
+ if (process.env[v.key] !== void 0) {
236
+ vars[v.key] = process.env[v.key];
237
+ }
238
+ } else {
239
+ vars[v.key] = v.value;
240
+ }
241
+ }
242
+ return vars;
243
+ }
244
+ function mergeVars(envVars, collectionVars, globals2, localVars = {}, dynamicVars = {}) {
245
+ return { ...dynamicVars, ...globals2, ...collectionVars, ...envVars, ...localVars };
246
+ }
81
247
  function xmlFindAll(node, tag, nth) {
82
248
  const results = [];
83
249
  const siblings = Array.from(node.childNodes).filter((c) => c.nodeType === 1 && c.tagName === tag);
@@ -469,6 +635,404 @@ function normalizeProxyInput(input) {
469
635
  function ensureScheme(value) {
470
636
  return /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(value) ? value : `http://${value}`;
471
637
  }
638
+ const SIGNATURE = Buffer.from("NTLMSSP\0", "latin1");
639
+ const F_UNICODE = 1;
640
+ const F_OEM = 2;
641
+ const F_REQUEST_TARGET = 4;
642
+ const F_NTLM = 512;
643
+ const F_ALWAYS_SIGN = 32768;
644
+ const F_EXT_SESSION = 524288;
645
+ const TYPE1_FLAGS = F_UNICODE | F_OEM | F_REQUEST_TARGET | F_NTLM | F_ALWAYS_SIGN | F_EXT_SESSION;
646
+ const TYPE3_FLAGS = F_UNICODE | F_NTLM | F_ALWAYS_SIGN | F_EXT_SESSION;
647
+ function rotl(x, n) {
648
+ return (x << n | x >>> 32 - n) >>> 0;
649
+ }
650
+ function md4(msg) {
651
+ const len = msg.length;
652
+ const bitLen = len * 8;
653
+ const padLen = (56 - (len + 1) % 64 + 64) % 64;
654
+ const total = len + 1 + padLen + 8;
655
+ const buf = Buffer.alloc(total);
656
+ msg.copy(buf, 0);
657
+ buf[len] = 128;
658
+ buf.writeUInt32LE(bitLen >>> 0, total - 8);
659
+ buf.writeUInt32LE(Math.floor(bitLen / 4294967296) >>> 0, total - 4);
660
+ let a = 1732584193, b = 4023233417, c = 2562383102, d = 271733878;
661
+ const X = new Array(16);
662
+ const F = (x, y, z) => x & y | ~x & z;
663
+ const G = (x, y, z) => x & y | x & z | y & z;
664
+ const H = (x, y, z) => x ^ y ^ z;
665
+ const FF = (aa, bb, cc, dd, k, s) => rotl(aa + F(bb, cc, dd) + X[k] >>> 0, s);
666
+ const GG = (aa, bb, cc, dd, k, s) => rotl(aa + G(bb, cc, dd) + X[k] + 1518500249 >>> 0, s);
667
+ const HH = (aa, bb, cc, dd, k, s) => rotl(aa + H(bb, cc, dd) + X[k] + 1859775393 >>> 0, s);
668
+ for (let i = 0; i < total; i += 64) {
669
+ for (let j = 0; j < 16; j++) X[j] = buf.readUInt32LE(i + j * 4);
670
+ const aa = a, bb = b, cc = c, dd = d;
671
+ for (let k = 0; k < 16; k += 4) {
672
+ a = FF(a, b, c, d, k, 3);
673
+ d = FF(d, a, b, c, k + 1, 7);
674
+ c = FF(c, d, a, b, k + 2, 11);
675
+ b = FF(b, c, d, a, k + 3, 19);
676
+ }
677
+ for (let k = 0; k < 4; k++) {
678
+ a = GG(a, b, c, d, k, 3);
679
+ d = GG(d, a, b, c, k + 4, 5);
680
+ c = GG(c, d, a, b, k + 8, 9);
681
+ b = GG(b, c, d, a, k + 12, 13);
682
+ }
683
+ const order = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
684
+ for (let k = 0; k < 16; k += 4) {
685
+ a = HH(a, b, c, d, order[k], 3);
686
+ d = HH(d, a, b, c, order[k + 1], 9);
687
+ c = HH(c, d, a, b, order[k + 2], 11);
688
+ b = HH(b, c, d, a, order[k + 3], 15);
689
+ }
690
+ a = a + aa >>> 0;
691
+ b = b + bb >>> 0;
692
+ c = c + cc >>> 0;
693
+ d = d + dd >>> 0;
694
+ }
695
+ const out = Buffer.alloc(16);
696
+ out.writeUInt32LE(a, 0);
697
+ out.writeUInt32LE(b, 4);
698
+ out.writeUInt32LE(c, 8);
699
+ out.writeUInt32LE(d, 12);
700
+ return out;
701
+ }
702
+ const utf16le = (s) => Buffer.from(s, "utf16le");
703
+ const hmacMd5 = (key, data) => crypto.createHmac("md5", key).update(data).digest();
704
+ function ntowfv2(user, domain, password) {
705
+ const ntHash = md4(utf16le(password));
706
+ return hmacMd5(ntHash, utf16le(user.toUpperCase() + domain));
707
+ }
708
+ function filetime(unixMs) {
709
+ const ticks = (BigInt(unixMs) + 11644473600000n) * 10000n;
710
+ const buf = Buffer.alloc(8);
711
+ buf.writeBigUInt64LE(ticks);
712
+ return buf;
713
+ }
714
+ function createType1Message() {
715
+ const msg = Buffer.alloc(32);
716
+ SIGNATURE.copy(msg, 0);
717
+ msg.writeUInt32LE(1, 8);
718
+ msg.writeUInt32LE(TYPE1_FLAGS, 12);
719
+ msg.writeUInt32LE(32, 16);
720
+ msg.writeUInt32LE(32, 24);
721
+ return msg.toString("base64");
722
+ }
723
+ function decodeType2Message(token) {
724
+ const buf = Buffer.isBuffer(token) ? token : Buffer.from(token, "base64");
725
+ if (buf.length < 32 || !buf.subarray(0, 8).equals(SIGNATURE)) {
726
+ throw new Error("Invalid NTLM Type 2 message");
727
+ }
728
+ const flags = buf.readUInt32LE(20);
729
+ const serverChallenge = Buffer.from(buf.subarray(24, 32));
730
+ let targetInfo = Buffer.alloc(0);
731
+ if (buf.length >= 48) {
732
+ const tiLen = buf.readUInt16LE(40);
733
+ const tiOff = buf.readUInt32LE(44);
734
+ if (tiLen > 0 && tiOff + tiLen <= buf.length) {
735
+ targetInfo = Buffer.from(buf.subarray(tiOff, tiOff + tiLen));
736
+ }
737
+ }
738
+ return { serverChallenge, targetInfo, flags };
739
+ }
740
+ function computeNtlmV2Response(opts) {
741
+ const responseKey = ntowfv2(opts.user, opts.domain, opts.password);
742
+ const blob = Buffer.concat([
743
+ Buffer.from([1, 1, 0, 0]),
744
+ // RespType + HiRespType + reserved
745
+ Buffer.from([0, 0, 0, 0]),
746
+ opts.timestamp,
747
+ opts.clientChallenge,
748
+ Buffer.from([0, 0, 0, 0]),
749
+ opts.targetInfo,
750
+ Buffer.from([0, 0, 0, 0])
751
+ ]);
752
+ const ntProof = hmacMd5(responseKey, Buffer.concat([opts.serverChallenge, blob]));
753
+ const ntResponse = Buffer.concat([ntProof, blob]);
754
+ const lmProof = hmacMd5(responseKey, Buffer.concat([opts.serverChallenge, opts.clientChallenge]));
755
+ const lmResponse = Buffer.concat([lmProof, opts.clientChallenge]);
756
+ return { ntResponse, lmResponse, ntProof };
757
+ }
758
+ function createType3Message(opts) {
759
+ const domain = opts.domain ?? "";
760
+ const workstation = opts.workstation ?? "";
761
+ const clientChallenge = opts.clientChallenge ?? crypto.randomBytes(8);
762
+ const timestamp = filetime(opts.timestamp ?? Date.now());
763
+ const { ntResponse, lmResponse } = computeNtlmV2Response({
764
+ user: opts.user,
765
+ domain,
766
+ password: opts.password,
767
+ serverChallenge: opts.challenge.serverChallenge,
768
+ targetInfo: opts.challenge.targetInfo,
769
+ clientChallenge,
770
+ timestamp
771
+ });
772
+ const domainBuf = utf16le(domain);
773
+ const userBuf = utf16le(opts.user);
774
+ const wsBuf = utf16le(workstation);
775
+ const HEADER = 64;
776
+ const payload = Buffer.concat([lmResponse, ntResponse, domainBuf, userBuf, wsBuf]);
777
+ const msg = Buffer.alloc(HEADER + payload.length);
778
+ SIGNATURE.copy(msg, 0);
779
+ msg.writeUInt32LE(3, 8);
780
+ let off = HEADER;
781
+ const writeField = (pos, buf) => {
782
+ msg.writeUInt16LE(buf.length, pos);
783
+ msg.writeUInt16LE(buf.length, pos + 2);
784
+ msg.writeUInt32LE(buf.length ? off : HEADER, pos + 4);
785
+ buf.copy(msg, off);
786
+ off += buf.length;
787
+ };
788
+ writeField(12, lmResponse);
789
+ writeField(20, ntResponse);
790
+ writeField(28, domainBuf);
791
+ writeField(36, userBuf);
792
+ writeField(44, wsBuf);
793
+ msg.writeUInt16LE(0, 52);
794
+ msg.writeUInt16LE(0, 54);
795
+ msg.writeUInt32LE(HEADER, 56);
796
+ msg.writeUInt32LE(TYPE3_FLAGS, 60);
797
+ return msg.toString("base64");
798
+ }
799
+ async function buildAuthHeaders(auth, vars) {
800
+ const headers = {};
801
+ if (auth.type === "bearer") {
802
+ let token = auth.token ?? "";
803
+ if (!token && auth.tokenSecretRef) token = await getSecret(auth.tokenSecretRef) ?? "";
804
+ token = interpolate(token, vars);
805
+ if (token) headers["Authorization"] = `Bearer ${token}`;
806
+ }
807
+ if (auth.type === "basic") {
808
+ let password = auth.password ?? "";
809
+ if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
810
+ password = interpolate(password, vars);
811
+ const username = interpolate(auth.username ?? "", vars);
812
+ headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
813
+ }
814
+ if (auth.type === "apikey" && auth.apiKeyIn === "header") {
815
+ let value = auth.apiKeyValue ?? "";
816
+ if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
817
+ value = interpolate(value, vars);
818
+ headers[auth.apiKeyName ?? "X-API-Key"] = value;
819
+ }
820
+ if (auth.type === "oauth2") {
821
+ const now = Date.now();
822
+ if (auth.oauth2CachedToken && auth.oauth2TokenExpiry && auth.oauth2TokenExpiry > now + 5e3) {
823
+ headers["Authorization"] = `Bearer ${auth.oauth2CachedToken}`;
824
+ }
825
+ }
826
+ return headers;
827
+ }
828
+ async function buildApiKeyParam(auth, vars) {
829
+ if (auth.type !== "apikey" || auth.apiKeyIn !== "query") return null;
830
+ let value = auth.apiKeyValue ?? "";
831
+ if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
832
+ value = interpolate(value, vars);
833
+ return { key: auth.apiKeyName ?? "apikey", value };
834
+ }
835
+ function parseDigestChallenge(wwwAuth) {
836
+ const extract = (key) => {
837
+ const m = new RegExp(`${key}="([^"]*)"`, "i").exec(wwwAuth);
838
+ return m ? m[1] : "";
839
+ };
840
+ const extractUnquoted = (key) => {
841
+ const m = new RegExp(`${key}=([^,\\s]+)`, "i").exec(wwwAuth);
842
+ return m ? m[1] : "";
843
+ };
844
+ return {
845
+ realm: extract("realm"),
846
+ nonce: extract("nonce"),
847
+ qop: extract("qop") || extractUnquoted("qop") || void 0,
848
+ algorithm: extract("algorithm") || extractUnquoted("algorithm") || "MD5",
849
+ opaque: extract("opaque") || void 0
850
+ };
851
+ }
852
+ function md5(s) {
853
+ return crypto.createHash("md5").update(s).digest("hex");
854
+ }
855
+ function buildDigestAuthHeader(challenge, username, password, method, uri) {
856
+ const { realm, nonce, qop, algorithm, opaque } = challenge;
857
+ const algo = (algorithm ?? "MD5").toUpperCase();
858
+ const ha1 = algo === "MD5-SESS" ? md5(`${md5(`${username}:${realm}:${password}`)}:${nonce}:`) : md5(`${username}:${realm}:${password}`);
859
+ const ha2 = md5(`${method}:${uri}`);
860
+ let response;
861
+ let nc;
862
+ let cnonce;
863
+ if (qop === "auth" || qop === "auth-int") {
864
+ nc = "00000001";
865
+ cnonce = crypto.randomBytes(8).toString("hex");
866
+ response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`);
867
+ } else {
868
+ response = md5(`${ha1}:${nonce}:${ha2}`);
869
+ }
870
+ let header = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${response}"`;
871
+ if (qop) header += `, qop=${qop}`;
872
+ if (nc) header += `, nc=${nc}`;
873
+ if (cnonce) header += `, cnonce="${cnonce}"`;
874
+ if (opaque) header += `, opaque="${opaque}"`;
875
+ if (algo !== "MD5") header += `, algorithm=${algo}`;
876
+ return header;
877
+ }
878
+ async function performDigestAuth(url, method, auth, vars, fetchFn) {
879
+ const probeResp = await fetchFn(url, { method, headers: {} });
880
+ if (probeResp.status !== 401) return null;
881
+ const wwwAuth = probeResp.headers.get("www-authenticate") ?? "";
882
+ if (!wwwAuth.toLowerCase().startsWith("digest")) return null;
883
+ const challenge = parseDigestChallenge(wwwAuth);
884
+ let password = auth.password ?? "";
885
+ if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
886
+ password = interpolate(password, vars);
887
+ const username = interpolate(auth.username ?? "", vars);
888
+ let uri = "/";
889
+ try {
890
+ uri = new URL(url).pathname + (new URL(url).search ?? "");
891
+ } catch {
892
+ }
893
+ return buildDigestAuthHeader(challenge, username, password, method, uri);
894
+ }
895
+ function extractNtlmChallenge(value) {
896
+ if (!value) return null;
897
+ const values = Array.isArray(value) ? value : [value];
898
+ for (const v of values) {
899
+ for (const part of v.split(",")) {
900
+ const m = /^\s*NTLM\s+(.+)\s*$/i.exec(part);
901
+ if (m) return m[1].trim();
902
+ }
903
+ }
904
+ return null;
905
+ }
906
+ async function buildNtlmConnectOpts(tls) {
907
+ if (!tls) return void 0;
908
+ const connect = {};
909
+ if (tls.rejectUnauthorized !== void 0) connect["rejectUnauthorized"] = tls.rejectUnauthorized;
910
+ if (tls.caCertPath) {
911
+ try {
912
+ connect["ca"] = await promises.readFile(tls.caCertPath);
913
+ } catch {
914
+ }
915
+ }
916
+ if (tls.clientCertPath) {
917
+ try {
918
+ connect["cert"] = await promises.readFile(tls.clientCertPath);
919
+ } catch {
920
+ }
921
+ }
922
+ if (tls.clientKeyPath) {
923
+ try {
924
+ connect["key"] = await promises.readFile(tls.clientKeyPath);
925
+ } catch {
926
+ }
927
+ }
928
+ return Object.keys(connect).length ? connect : void 0;
929
+ }
930
+ async function performNtlmRequest(opts) {
931
+ if (opts.proxy?.url) {
932
+ throw new Error("NTLM authentication through a proxy is not supported. Disable the proxy for this request, or target the server directly.");
933
+ }
934
+ const { Client } = await import("undici");
935
+ let password = opts.auth.password ?? "";
936
+ if (!password && opts.auth.passwordSecretRef) password = await getSecret(opts.auth.passwordSecretRef) ?? "";
937
+ password = interpolate(password, opts.vars);
938
+ const username = interpolate(opts.auth.username ?? "", opts.vars);
939
+ const domain = interpolate(opts.auth.ntlmDomain ?? "", opts.vars);
940
+ const workstation = interpolate(opts.auth.ntlmWorkstation ?? "", opts.vars);
941
+ const parsed = new URL(opts.url);
942
+ const origin = `${parsed.protocol}//${parsed.host}`;
943
+ const path2 = parsed.pathname + parsed.search;
944
+ const connect = await buildNtlmConnectOpts(opts.tls);
945
+ const client = new Client(origin, { pipelining: 1, ...connect ? { connect } : {} });
946
+ const toAdapter = (statusCode, headers, bodyText) => {
947
+ const entries = [];
948
+ for (const [k, v] of Object.entries(headers)) {
949
+ if (v === void 0) continue;
950
+ entries.push([k, Array.isArray(v) ? v.join(", ") : String(v)]);
951
+ }
952
+ return {
953
+ status: statusCode,
954
+ statusText: http.STATUS_CODES[statusCode] ?? "",
955
+ headers: { forEach: (cb) => entries.forEach(([k, v]) => cb(v, k)) },
956
+ text: () => Promise.resolve(bodyText)
957
+ };
958
+ };
959
+ try {
960
+ const negotiate = await client.request({
961
+ path: path2,
962
+ method: opts.method,
963
+ headers: { authorization: `NTLM ${createType1Message()}` }
964
+ });
965
+ await negotiate.body.text();
966
+ const challengeToken = extractNtlmChallenge(negotiate.headers["www-authenticate"]);
967
+ if (negotiate.statusCode !== 401 || !challengeToken) {
968
+ const second = await client.request({ path: path2, method: opts.method, headers: opts.baseHeaders, body: opts.body });
969
+ const bodyText2 = await second.body.text();
970
+ return toAdapter(second.statusCode, second.headers, bodyText2);
971
+ }
972
+ const challenge = decodeType2Message(challengeToken);
973
+ const type3 = createType3Message({ user: username, password, domain, workstation, challenge });
974
+ const authed = await client.request({
975
+ path: path2,
976
+ method: opts.method,
977
+ headers: { ...opts.baseHeaders, authorization: `NTLM ${type3}` },
978
+ body: opts.body
979
+ });
980
+ const bodyText = await authed.body.text();
981
+ return toAdapter(authed.statusCode, authed.headers, bodyText);
982
+ } finally {
983
+ await client.close().catch(() => {
984
+ });
985
+ }
986
+ }
987
+ async function fetchOAuth2Token(auth, vars) {
988
+ const flow = auth.oauth2Flow ?? "client_credentials";
989
+ if (flow === "authorization_code") {
990
+ throw new Error("authorization_code flow requires the oauth2:startFlow IPC call from the renderer.");
991
+ }
992
+ if (flow === "implicit") {
993
+ throw new Error("implicit flow cannot be performed server-side - tokens must be obtained via the browser redirect.");
994
+ }
995
+ const tokenUrl = interpolate(auth.oauth2TokenUrl ?? "", vars);
996
+ if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required.");
997
+ const clientId = interpolate(auth.oauth2ClientId ?? "", vars);
998
+ let clientSecret = auth.oauth2ClientSecret ?? "";
999
+ if (!clientSecret && auth.oauth2ClientSecretRef) {
1000
+ clientSecret = await getSecret(auth.oauth2ClientSecretRef) ?? "";
1001
+ }
1002
+ clientSecret = interpolate(clientSecret, vars);
1003
+ const params = new URLSearchParams();
1004
+ params.set("grant_type", flow === "password" ? "password" : "client_credentials");
1005
+ params.set("client_id", clientId);
1006
+ params.set("client_secret", clientSecret);
1007
+ if (auth.oauth2Scopes) params.set("scope", auth.oauth2Scopes);
1008
+ if (flow === "password") {
1009
+ let password = auth.password ?? "";
1010
+ if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
1011
+ password = interpolate(password, vars);
1012
+ params.set("username", interpolate(auth.username ?? "", vars));
1013
+ params.set("password", password);
1014
+ }
1015
+ const { fetch: nodeFetch } = await import("undici");
1016
+ const resp = await nodeFetch(tokenUrl, {
1017
+ method: "POST",
1018
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1019
+ body: params.toString()
1020
+ });
1021
+ if (!resp.ok) {
1022
+ const body = await resp.text();
1023
+ throw new Error(`OAuth 2.0 token request failed (${resp.status}): ${body}`);
1024
+ }
1025
+ const json = await resp.json();
1026
+ const accessToken = String(json["access_token"] ?? "");
1027
+ if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
1028
+ const expiresIn = Number(json["expires_in"] ?? 3600);
1029
+ const expiresAt = Date.now() + expiresIn * 1e3;
1030
+ return {
1031
+ accessToken,
1032
+ expiresAt,
1033
+ refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
1034
+ };
1035
+ }
472
1036
  function maskPii(data, patterns) {
473
1037
  if (!patterns.length) return data;
474
1038
  try {
@@ -529,7 +1093,7 @@ function buildSchemaTestResults(schemaText, body) {
529
1093
  return [{
530
1094
  name: "[schema] body matches schema",
531
1095
  passed: false,
532
- error: "Response body is not valid JSON — cannot validate against schema"
1096
+ error: "Response body is not valid JSON - cannot validate against schema"
533
1097
  }];
534
1098
  }
535
1099
  let validate;
@@ -633,21 +1197,21 @@ async function buildDispatcher(proxy, tls) {
633
1197
  function buildBodyAndApplyHeaders(req, vars, headers) {
634
1198
  let body;
635
1199
  if (req.body.mode === "json" && req.body.json) {
636
- body = authBuilder.interpolate(req.body.json, vars);
1200
+ body = interpolate(req.body.json, vars);
637
1201
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
638
1202
  } else if (req.body.mode === "form" && req.body.form) {
639
- body = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${encodeURIComponent(authBuilder.interpolate(p.key, vars))}=${encodeURIComponent(authBuilder.interpolate(p.value, vars))}`).join("&");
1203
+ body = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${encodeURIComponent(interpolate(p.key, vars))}=${encodeURIComponent(interpolate(p.value, vars))}`).join("&");
640
1204
  if (!headers.has("content-type")) headers.set("Content-Type", "application/x-www-form-urlencoded");
641
1205
  } else if (req.body.mode === "raw" && req.body.raw) {
642
- body = authBuilder.interpolate(req.body.raw, vars);
1206
+ body = interpolate(req.body.raw, vars);
643
1207
  if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
644
1208
  } else if (req.body.mode === "graphql" && req.body.graphql) {
645
1209
  const gql = req.body.graphql;
646
- const gqlBody = { query: authBuilder.interpolate(gql.query, vars) };
1210
+ const gqlBody = { query: interpolate(gql.query, vars) };
647
1211
  const rawVars = gql.variables?.trim();
648
1212
  if (rawVars) {
649
1213
  try {
650
- gqlBody.variables = JSON.parse(authBuilder.interpolate(rawVars, vars));
1214
+ gqlBody.variables = JSON.parse(interpolate(rawVars, vars));
651
1215
  } catch {
652
1216
  }
653
1217
  }
@@ -655,7 +1219,7 @@ function buildBodyAndApplyHeaders(req, vars, headers) {
655
1219
  body = JSON.stringify(gqlBody);
656
1220
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
657
1221
  } else if (req.body.mode === "soap" && req.body.soap) {
658
- body = authBuilder.interpolate(req.body.soap.envelope, vars);
1222
+ body = interpolate(req.body.soap.envelope, vars);
659
1223
  if (!headers.has("content-type")) headers.set("Content-Type", "text/xml; charset=utf-8");
660
1224
  if (req.body.soap.soapAction && !headers.has("soapaction")) {
661
1225
  headers.set("SOAPAction", req.body.soap.soapAction);
@@ -671,13 +1235,13 @@ async function performHttpExchange(opts) {
671
1235
  const tokenMissing = !req.auth.oauth2CachedToken;
672
1236
  const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
673
1237
  if (tokenMissing || tokenExpired) {
674
- const result = await authBuilder.fetchOAuth2Token(req.auth, vars);
1238
+ const result = await fetchOAuth2Token(req.auth, vars);
675
1239
  req.auth.oauth2CachedToken = result.accessToken;
676
1240
  req.auth.oauth2TokenExpiry = result.expiresAt;
677
1241
  }
678
1242
  }
679
- const authHeaders = await authBuilder.buildAuthHeaders(req.auth, vars);
680
- const apiKeyParam = await authBuilder.buildApiKeyParam(req.auth, vars);
1243
+ const authHeaders = await buildAuthHeaders(req.auth, vars);
1244
+ const apiKeyParam = await buildApiKeyParam(req.auth, vars);
681
1245
  let finalUrl = resolvedUrl;
682
1246
  if (apiKeyParam) {
683
1247
  const sep = finalUrl.includes("?") ? "&" : "?";
@@ -685,7 +1249,7 @@ async function performHttpExchange(opts) {
685
1249
  }
686
1250
  const headers = new undici.Headers();
687
1251
  for (const h of req.headers) {
688
- if (h.enabled && h.key) headers.set(authBuilder.interpolate(h.key, vars), authBuilder.interpolate(h.value, vars));
1252
+ if (h.enabled && h.key) headers.set(interpolate(h.key, vars), interpolate(h.value, vars));
689
1253
  }
690
1254
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
691
1255
  const body = buildBodyAndApplyHeaders(req, vars, headers);
@@ -709,7 +1273,7 @@ async function performHttpExchange(opts) {
709
1273
  let fetchResp;
710
1274
  if (req.auth.type === "ntlm") {
711
1275
  sentHeaders = captureSent(headers);
712
- fetchResp = await authBuilder.performNtlmRequest({
1276
+ fetchResp = await performNtlmRequest({
713
1277
  url: finalUrl,
714
1278
  method: req.method,
715
1279
  auth: req.auth,
@@ -724,7 +1288,7 @@ async function performHttpExchange(opts) {
724
1288
  ...init,
725
1289
  dispatcher
726
1290
  });
727
- const digestHeader = await authBuilder.performDigestAuth(finalUrl, req.method, req.auth, vars, probeFetch);
1291
+ const digestHeader = await performDigestAuth(finalUrl, req.method, req.auth, vars, probeFetch);
728
1292
  if (digestHeader) headers.set("Authorization", digestHeader);
729
1293
  sentHeaders = captureSent(headers);
730
1294
  fetchResp = await doFetch(headers);
@@ -758,7 +1322,7 @@ function syntheticHttpFailure(status, statusText) {
758
1322
  return {
759
1323
  name: `HTTP status ${status} ${statusText}`.trim(),
760
1324
  passed: false,
761
- error: `Request returned ${status} — no assertion was defined to verify the status code.`
1325
+ error: `Request returned ${status} - no assertion was defined to verify the status code.`
762
1326
  };
763
1327
  }
764
1328
  async function executeRunnerRequest(opts) {
@@ -772,14 +1336,14 @@ async function executeRunnerRequest(opts) {
772
1336
  resolvedUrl: req.url,
773
1337
  status: "running"
774
1338
  };
775
- const dynamicVars = await authBuilder.buildDynamicVars();
776
- let vars = authBuilder.mergeVars(envVars, collectionVars, globals2, localVars, dynamicVars);
1339
+ const dynamicVars = await buildDynamicVars();
1340
+ let vars = mergeVars(envVars, collectionVars, globals2, localVars, dynamicVars);
777
1341
  let updatedEnvVars = { ...envVars };
778
1342
  let updatedCollectionVars = { ...collectionVars };
779
1343
  let updatedGlobals = { ...globals2 };
780
1344
  let preScriptError;
781
1345
  if (req.preRequestScript?.trim()) {
782
- const r = await runScript(authBuilder.interpolate(req.preRequestScript, vars), {
1346
+ const r = await runScript(interpolate(req.preRequestScript, vars), {
783
1347
  envVars: { ...envVars },
784
1348
  collectionVars: { ...collectionVars },
785
1349
  globals: { ...globals2 },
@@ -794,9 +1358,9 @@ async function executeRunnerRequest(opts) {
794
1358
  patchGlobals(r.updatedGlobals);
795
1359
  await persistGlobals();
796
1360
  onScriptOutput?.("pre", r.consoleOutput, r.error);
797
- vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
1361
+ vars = mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
798
1362
  }
799
- const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
1363
+ const resolvedUrl = buildUrl(req.url, req.params, vars);
800
1364
  base.resolvedUrl = resolvedUrl;
801
1365
  const start = Date.now();
802
1366
  try {
@@ -817,7 +1381,7 @@ async function executeRunnerRequest(opts) {
817
1381
  let consoleOutput = [];
818
1382
  let postScriptError;
819
1383
  if (req.postRequestScript?.trim()) {
820
- const r = await runScript(authBuilder.interpolate(req.postRequestScript, vars), {
1384
+ const r = await runScript(interpolate(req.postRequestScript, vars), {
821
1385
  envVars: updatedEnvVars,
822
1386
  collectionVars: updatedCollectionVars,
823
1387
  globals: updatedGlobals,
@@ -890,25 +1454,25 @@ class HookSkipTracker {
890
1454
  if (isHook) {
891
1455
  if (hookType === "beforeAll") {
892
1456
  if ((scopeAncestors ?? []).some((id) => this.failedScopes.has(id))) {
893
- return "Skipped — outer scope hook failed";
1457
+ return "Skipped - outer scope hook failed";
894
1458
  }
895
1459
  } else if (hookType === "before") {
896
1460
  const allScopes2 = [...scopeAncestors ?? [], scopeId].filter(Boolean);
897
1461
  if (allScopes2.some((id) => this.failedScopes.has(id))) {
898
- return "Skipped — scope hook failed";
1462
+ return "Skipped - scope hook failed";
899
1463
  }
900
1464
  if (mainRequestId && this.skipRequests.has(mainRequestId)) {
901
- return "Skipped — before hook failed";
1465
+ return "Skipped - before hook failed";
902
1466
  }
903
1467
  }
904
1468
  return void 0;
905
1469
  }
906
1470
  const allScopes = [...scopeAncestors ?? [], scopeId].filter(Boolean);
907
1471
  if (allScopes.some((id) => this.failedScopes.has(id))) {
908
- return "Skipped — beforeAll hook failed";
1472
+ return "Skipped - beforeAll hook failed";
909
1473
  }
910
1474
  if (this.skipRequests.has(item.request.id)) {
911
- return "Skipped — before hook failed";
1475
+ return "Skipped - before hook failed";
912
1476
  }
913
1477
  return void 0;
914
1478
  }
@@ -925,135 +1489,27 @@ class HookSkipTracker {
925
1489
  }
926
1490
  }
927
1491
  }
928
- function makeHook(req, collectionVars, hookType, scopeId, scopeAncestors, scopePath, mainRequestId) {
929
- return { request: req, collectionVars, isHook: true, hookType, scopeId, scopeAncestors, scopePath, mainRequestId };
930
- }
931
- function buildFolderPlan(folder, requests, collectionVars, filterTags, scopeId, ancestorIds, parentPath, wrappers, isRoot) {
932
- const result = [];
933
- const scopePath = isRoot ? [] : [...parentPath, folder.name];
934
- const folderReqs = folder.requestIds.map((id) => requests[id]).filter((r) => r && !r.disabled);
935
- const beforeAllHooks = folderReqs.filter((r) => r.hookType === "beforeAll");
936
- const beforeHooks = folderReqs.filter((r) => r.hookType === "before");
937
- const afterHooks = folderReqs.filter((r) => r.hookType === "after");
938
- const afterAllHooks = folderReqs.filter((r) => r.hookType === "afterAll");
939
- const regularReqs = folderReqs.filter((r) => !r.hookType);
940
- const myWrapper = { scopeId, ancestors: ancestorIds, scopePath, before: beforeHooks, after: afterHooks };
941
- const allWrappers = [...wrappers, myWrapper];
942
- for (const req of beforeAllHooks) {
943
- result.push(makeHook(req, collectionVars, "beforeAll", scopeId, ancestorIds, scopePath));
944
- }
945
- for (const req of regularReqs) {
946
- const tags = req.meta?.tags ?? [];
947
- if (filterTags.length > 0 && !filterTags.some((t) => tags.includes(t))) continue;
948
- for (const w of allWrappers) {
949
- for (const hookReq of w.before) {
950
- result.push(makeHook(hookReq, collectionVars, "before", w.scopeId, w.ancestors, w.scopePath, req.id));
951
- }
952
- }
953
- result.push({ request: req, collectionVars, scopeId, scopeAncestors: ancestorIds, scopePath });
954
- for (const w of [...allWrappers].reverse()) {
955
- for (const hookReq of w.after) {
956
- result.push(makeHook(hookReq, collectionVars, "after", w.scopeId, w.ancestors, w.scopePath, req.id));
957
- }
958
- }
959
- }
960
- for (const sub of folder.folders) {
961
- const folderTags = sub.tags ?? [];
962
- const effectiveFilter = filterTags.length === 0 ? filterTags : folderTags.some((t) => filterTags.includes(t)) ? [] : filterTags;
963
- result.push(...buildFolderPlan(
964
- sub,
965
- requests,
966
- collectionVars,
967
- effectiveFilter,
968
- sub.id,
969
- [...ancestorIds, scopeId],
970
- scopePath,
971
- allWrappers,
972
- false
973
- ));
974
- }
975
- for (const req of afterAllHooks) {
976
- result.push(makeHook(req, collectionVars, "afterAll", scopeId, ancestorIds, scopePath));
977
- }
978
- return result;
979
- }
980
- function folderPathTo(root, requestId) {
981
- if (root.requestIds.includes(requestId)) return [root];
982
- for (const sub of root.folders) {
983
- const path2 = folderPathTo(sub, requestId);
984
- if (path2.length > 0) return [root, ...path2];
985
- }
986
- return [];
987
- }
988
- function getAllApplicableHooks(folderId, collection) {
989
- function chainToFolder(root, targetId) {
990
- if (root.id === targetId) return [root];
991
- for (const sub of root.folders) {
992
- const chain2 = chainToFolder(sub, targetId);
993
- if (chain2.length) return [root, ...chain2];
994
- }
995
- return [];
996
- }
997
- const chain = chainToFolder(collection.rootFolder, folderId);
998
- const beforeAll = [];
999
- const before = [];
1000
- const after = [];
1001
- const afterAll = [];
1002
- for (const folder of chain) {
1003
- const reqs = folder.requestIds.map((id) => collection.requests[id]).filter((r) => r && !r.disabled);
1004
- beforeAll.push(...reqs.filter((r) => r.hookType === "beforeAll"));
1005
- before.push(...reqs.filter((r) => r.hookType === "before"));
1006
- }
1007
- for (const folder of [...chain].reverse()) {
1008
- const reqs = folder.requestIds.map((id) => collection.requests[id]).filter((r) => r && !r.disabled);
1009
- after.push(...reqs.filter((r) => r.hookType === "after"));
1010
- afterAll.push(...reqs.filter((r) => r.hookType === "afterAll"));
1011
- }
1012
- return { beforeAll, before, after, afterAll };
1013
- }
1014
- function resolveInheritedAuthAndHeaders(requestId, collection) {
1015
- let inheritedAuth = collection.auth && collection.auth.type !== "none" ? collection.auth : null;
1016
- let inheritedHeaders = collection.headers?.filter((h) => h.enabled && h.key) ?? [];
1017
- const path2 = folderPathTo(collection.rootFolder, requestId);
1018
- for (const folder of path2) {
1019
- if (folder.auth && folder.auth.type !== "none") inheritedAuth = folder.auth;
1020
- if (folder.headers?.length) {
1021
- inheritedHeaders = [...inheritedHeaders, ...folder.headers.filter((h) => h.enabled && h.key)];
1022
- }
1023
- }
1024
- return { auth: inheritedAuth, headers: inheritedHeaders };
1025
- }
1026
- function buildRunPlan(collection, folderId, filterTags) {
1027
- const collectionVars = collection.collectionVariables ?? {};
1028
- {
1029
- return buildFolderPlan(
1030
- collection.rootFolder,
1031
- collection.requests,
1032
- collectionVars,
1033
- filterTags,
1034
- collection.rootFolder.id,
1035
- [],
1036
- [],
1037
- [],
1038
- true
1039
- );
1040
- }
1041
- }
1042
1492
  exports.HookSkipTracker = HookSkipTracker;
1043
1493
  exports.applyRequestDefaults = applyRequestDefaults;
1494
+ exports.buildAuthHeaders = buildAuthHeaders;
1044
1495
  exports.buildDispatcher = buildDispatcher;
1496
+ exports.buildDynamicVars = buildDynamicVars;
1497
+ exports.buildEnvVars = buildEnvVars;
1045
1498
  exports.buildProxyUri = buildProxyUri;
1046
- exports.buildRunPlan = buildRunPlan;
1047
1499
  exports.buildSchemaTestResults = buildSchemaTestResults;
1500
+ exports.buildUrl = buildUrl;
1048
1501
  exports.executeRunnerRequest = executeRunnerRequest;
1049
- exports.getAllApplicableHooks = getAllApplicableHooks;
1050
1502
  exports.getGlobals = getGlobals;
1503
+ exports.getSecret = getSecret;
1504
+ exports.initSecretStore = initSecretStore;
1505
+ exports.interpolate = interpolate;
1051
1506
  exports.loadGlobals = loadGlobals;
1052
1507
  exports.maskHeaders = maskHeaders;
1053
1508
  exports.maskPii = maskPii;
1509
+ exports.mergeVars = mergeVars;
1054
1510
  exports.patchGlobals = patchGlobals;
1055
1511
  exports.performHttpExchange = performHttpExchange;
1056
1512
  exports.persistGlobals = persistGlobals;
1057
- exports.resolveInheritedAuthAndHeaders = resolveInheritedAuthAndHeaders;
1513
+ exports.registerSecretHandlers = registerSecretHandlers;
1058
1514
  exports.runScript = runScript;
1059
1515
  exports.setGlobals = setGlobals;