@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.
- package/bin/cli.js +11 -7
- package/out/main/agents.js +1 -1
- package/out/main/chunks/environments-iM3SUM-4.js +28 -0
- package/out/main/chunks/{handle-C0IQL-Vl.js → handle-BCnNIZZr.js} +6 -2
- package/out/main/chunks/{import-C5Ngq8hk.js → import-DUKaMYGO.js} +4 -4
- package/out/main/chunks/{recorder-DFxJgn9c.js → recorder-0Ij921El.js} +1 -1
- package/out/main/chunks/request-collection-CBuPjBwa.js +118 -0
- package/out/main/chunks/{request-collection-8TOVNXE0.js → request-exec-AQD6cEgT.js} +602 -146
- package/out/main/chunks/snapshots-NGkGQNZ4.js +1658 -0
- package/out/main/chunks/{soap-handler-B9x_YCtj.js → soap-handler-h0dWIjOK.js} +3 -3
- package/out/main/contract.js +508 -91
- package/out/main/index.js +112 -77
- package/out/main/record.js +3 -3
- package/out/main/runner.js +23 -20
- package/out/main/wsdl.js +4 -4
- package/out/preload/index.js +10 -2
- package/out/renderer/assets/{index-BFxRlU1d.js → index-SXJZlbGN.js} +1342 -214
- package/out/renderer/assets/index-mDBOHLZ5.css +2 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/readme.md +2 -2
- package/out/main/chunks/auth-builder-CUs9yzOF.js +0 -623
- package/out/main/chunks/snapshots-UFd3XgSS.js +0 -928
- package/out/renderer/assets/index-DuMAjIiI.css +0 -2
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (let key of __getOwnPropNames(from))
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
-
}
|
|
14
|
-
return to;
|
|
15
|
-
};
|
|
16
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
-
mod
|
|
23
|
-
));
|
|
24
|
-
const crypto = require("crypto");
|
|
25
|
-
const http = require("http");
|
|
26
|
-
const promises = require("fs/promises");
|
|
27
|
-
const handle = require("./handle-C0IQL-Vl.js");
|
|
28
|
-
const path = require("path");
|
|
29
|
-
const dayjs = require("dayjs");
|
|
30
|
-
const vm = require("vm");
|
|
31
|
-
function _interopNamespaceDefault(e) {
|
|
32
|
-
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
33
|
-
if (e) {
|
|
34
|
-
for (const k in e) {
|
|
35
|
-
if (k !== "default") {
|
|
36
|
-
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
37
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
38
|
-
enumerable: true,
|
|
39
|
-
get: () => e[k]
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
n.default = e;
|
|
45
|
-
return Object.freeze(n);
|
|
46
|
-
}
|
|
47
|
-
const vm__namespace = /* @__PURE__ */ _interopNamespaceDefault(vm);
|
|
48
|
-
const MASTER_KEY_ENV = "API_SPECTOR_MASTER_KEY";
|
|
49
|
-
let secretStore = {};
|
|
50
|
-
let secretStorePath = null;
|
|
51
|
-
async function initSecretStore(userDataPath) {
|
|
52
|
-
secretStorePath = path.join(userDataPath, "secrets.json");
|
|
53
|
-
try {
|
|
54
|
-
const raw = await promises.readFile(secretStorePath, "utf8");
|
|
55
|
-
secretStore = JSON.parse(raw);
|
|
56
|
-
} catch {
|
|
57
|
-
secretStore = {};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async function persistSecretStore() {
|
|
61
|
-
if (!secretStorePath) return;
|
|
62
|
-
await promises.writeFile(secretStorePath, JSON.stringify(secretStore, null, 2), "utf8");
|
|
63
|
-
}
|
|
64
|
-
function getSafeStorage() {
|
|
65
|
-
try {
|
|
66
|
-
const { safeStorage } = require("electron");
|
|
67
|
-
if (typeof safeStorage?.isEncryptionAvailable === "function") return safeStorage;
|
|
68
|
-
return null;
|
|
69
|
-
} catch {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function registerSecretHandlers(ipc) {
|
|
74
|
-
handle.handleIpc(ipc, handle.IPC.secret.checkMasterKey, () => {
|
|
75
|
-
return { set: Boolean(process.env[MASTER_KEY_ENV]) };
|
|
76
|
-
});
|
|
77
|
-
handle.handleIpc(ipc, handle.IPC.secret.setMasterKey, (_e, value) => {
|
|
78
|
-
process.env[MASTER_KEY_ENV] = value;
|
|
79
|
-
});
|
|
80
|
-
handle.handleIpc(ipc, handle.IPC.secret.set, async (_e, ref, value) => {
|
|
81
|
-
const ss = getSafeStorage();
|
|
82
|
-
if (!ss || !ss.isEncryptionAvailable()) {
|
|
83
|
-
throw new Error("OS encryption is not available — set the secret via environment variable instead");
|
|
84
|
-
}
|
|
85
|
-
secretStore[ref] = ss.encryptString(value).toString("base64");
|
|
86
|
-
await persistSecretStore();
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
function decryptSecret(encrypted, salt, iv, password) {
|
|
90
|
-
const saltBuf = Buffer.from(salt, "base64");
|
|
91
|
-
const ivBuf = Buffer.from(iv, "base64");
|
|
92
|
-
const encBuf = Buffer.from(encrypted, "base64");
|
|
93
|
-
const key = crypto.pbkdf2Sync(password, saltBuf, 1e5, 32, "sha256");
|
|
94
|
-
const authTag = encBuf.subarray(encBuf.length - 16);
|
|
95
|
-
const ciphertext = encBuf.subarray(0, encBuf.length - 16);
|
|
96
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, ivBuf);
|
|
97
|
-
decipher.setAuthTag(authTag);
|
|
98
|
-
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
99
|
-
}
|
|
100
|
-
async function getSecret(ref) {
|
|
101
|
-
const stored = secretStore[ref];
|
|
102
|
-
if (stored) {
|
|
103
|
-
const ss = getSafeStorage();
|
|
104
|
-
if (ss && ss.isEncryptionAvailable()) {
|
|
105
|
-
try {
|
|
106
|
-
return ss.decryptString(Buffer.from(stored, "base64"));
|
|
107
|
-
} catch {
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return process.env[ref] ?? null;
|
|
112
|
-
}
|
|
113
|
-
let _fakerCache = null;
|
|
114
|
-
async function getFaker() {
|
|
115
|
-
if (!_fakerCache) _fakerCache = await import("@faker-js/faker");
|
|
116
|
-
return _fakerCache.faker;
|
|
117
|
-
}
|
|
118
|
-
let _exprContext = null;
|
|
119
|
-
async function buildDynamicVars() {
|
|
120
|
-
const faker = await getFaker();
|
|
121
|
-
const now = dayjs();
|
|
122
|
-
_exprContext = { faker, dayjs };
|
|
123
|
-
return {
|
|
124
|
-
$uuid: faker.string.uuid(),
|
|
125
|
-
$timestamp: String(Date.now()),
|
|
126
|
-
$isoTimestamp: now.toISOString(),
|
|
127
|
-
$randomInt: String(faker.number.int({ min: 0, max: 1e3 })),
|
|
128
|
-
$randomFloat: String(faker.number.float({ min: 0, max: 1e3, fractionDigits: 2 })),
|
|
129
|
-
$randomBoolean: String(faker.datatype.boolean()),
|
|
130
|
-
$randomEmail: faker.internet.email(),
|
|
131
|
-
$randomUsername: faker.internet.username(),
|
|
132
|
-
$randomPassword: faker.internet.password(),
|
|
133
|
-
$randomFullName: faker.person.fullName(),
|
|
134
|
-
$randomFirstName: faker.person.firstName(),
|
|
135
|
-
$randomLastName: faker.person.lastName(),
|
|
136
|
-
$randomWord: faker.lorem.word(),
|
|
137
|
-
$randomPhrase: faker.lorem.sentence(),
|
|
138
|
-
$randomUrl: faker.internet.url(),
|
|
139
|
-
$randomIp: faker.internet.ip(),
|
|
140
|
-
$randomHexColor: faker.color.rgb({ format: "hex", casing: "lower" })
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function interpolate(str, vars) {
|
|
144
|
-
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
145
|
-
const trimmed = key.trim();
|
|
146
|
-
if (trimmed in vars) return vars[trimmed];
|
|
147
|
-
if (_exprContext && (trimmed.includes(".") || trimmed.includes("("))) {
|
|
148
|
-
try {
|
|
149
|
-
const result = vm__namespace.runInNewContext(trimmed, _exprContext);
|
|
150
|
-
if (result !== void 0 && result !== null) return String(result);
|
|
151
|
-
} catch {
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return match;
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
function buildUrl(baseUrl, params, vars) {
|
|
158
|
-
const templateTokens = /* @__PURE__ */ new Set();
|
|
159
|
-
baseUrl.replace(/\{\{([^}]+)\}\}/g, (_m, name) => {
|
|
160
|
-
templateTokens.add(String(name).trim());
|
|
161
|
-
return "";
|
|
162
|
-
});
|
|
163
|
-
const enabled = (params ?? []).filter((p) => p.enabled && p.key);
|
|
164
|
-
const pathRows = [];
|
|
165
|
-
const queryRows = [];
|
|
166
|
-
for (const p of enabled) {
|
|
167
|
-
const isPath = p.paramType === "path" || templateTokens.has(p.key);
|
|
168
|
-
if (isPath) pathRows.push(p);
|
|
169
|
-
else queryRows.push(p);
|
|
170
|
-
}
|
|
171
|
-
const mergedVars = pathRows.length ? {
|
|
172
|
-
...vars,
|
|
173
|
-
...Object.fromEntries(pathRows.map((p) => [p.key, interpolate(p.value, vars)]))
|
|
174
|
-
} : vars;
|
|
175
|
-
const url = interpolate(baseUrl, mergedVars);
|
|
176
|
-
if (!queryRows.length) return url;
|
|
177
|
-
const sep = url.includes("?") ? "&" : "?";
|
|
178
|
-
const qs = queryRows.map((p) => `${encodeURIComponent(interpolate(p.key, vars))}=${encodeURIComponent(interpolate(p.value, vars))}`).join("&");
|
|
179
|
-
return url + sep + qs;
|
|
180
|
-
}
|
|
181
|
-
async function buildEnvVars(environment) {
|
|
182
|
-
const vars = {};
|
|
183
|
-
if (!environment) return vars;
|
|
184
|
-
const masterKey = process.env["API_SPECTOR_MASTER_KEY"];
|
|
185
|
-
for (const v of environment.variables) {
|
|
186
|
-
if (!v.enabled) continue;
|
|
187
|
-
if (v.envRef) {
|
|
188
|
-
const envValue = process.env[v.envRef];
|
|
189
|
-
if (envValue !== void 0) vars[v.key] = envValue;
|
|
190
|
-
} else if (v.secret && v.secretEncrypted && v.secretSalt && v.secretIv) {
|
|
191
|
-
if (masterKey) {
|
|
192
|
-
try {
|
|
193
|
-
vars[v.key] = decryptSecret(v.secretEncrypted, v.secretSalt, v.secretIv, masterKey);
|
|
194
|
-
} catch {
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (vars[v.key] === void 0 && process.env[v.key] !== void 0) {
|
|
198
|
-
vars[v.key] = process.env[v.key];
|
|
199
|
-
}
|
|
200
|
-
} else if (v.secret) {
|
|
201
|
-
if (process.env[v.key] !== void 0) {
|
|
202
|
-
vars[v.key] = process.env[v.key];
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
vars[v.key] = v.value;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return vars;
|
|
209
|
-
}
|
|
210
|
-
function mergeVars(envVars, collectionVars, globals, localVars = {}, dynamicVars = {}) {
|
|
211
|
-
return { ...dynamicVars, ...globals, ...collectionVars, ...envVars, ...localVars };
|
|
212
|
-
}
|
|
213
|
-
const SIGNATURE = Buffer.from("NTLMSSP\0", "latin1");
|
|
214
|
-
const F_UNICODE = 1;
|
|
215
|
-
const F_OEM = 2;
|
|
216
|
-
const F_REQUEST_TARGET = 4;
|
|
217
|
-
const F_NTLM = 512;
|
|
218
|
-
const F_ALWAYS_SIGN = 32768;
|
|
219
|
-
const F_EXT_SESSION = 524288;
|
|
220
|
-
const TYPE1_FLAGS = F_UNICODE | F_OEM | F_REQUEST_TARGET | F_NTLM | F_ALWAYS_SIGN | F_EXT_SESSION;
|
|
221
|
-
const TYPE3_FLAGS = F_UNICODE | F_NTLM | F_ALWAYS_SIGN | F_EXT_SESSION;
|
|
222
|
-
function rotl(x, n) {
|
|
223
|
-
return (x << n | x >>> 32 - n) >>> 0;
|
|
224
|
-
}
|
|
225
|
-
function md4(msg) {
|
|
226
|
-
const len = msg.length;
|
|
227
|
-
const bitLen = len * 8;
|
|
228
|
-
const padLen = (56 - (len + 1) % 64 + 64) % 64;
|
|
229
|
-
const total = len + 1 + padLen + 8;
|
|
230
|
-
const buf = Buffer.alloc(total);
|
|
231
|
-
msg.copy(buf, 0);
|
|
232
|
-
buf[len] = 128;
|
|
233
|
-
buf.writeUInt32LE(bitLen >>> 0, total - 8);
|
|
234
|
-
buf.writeUInt32LE(Math.floor(bitLen / 4294967296) >>> 0, total - 4);
|
|
235
|
-
let a = 1732584193, b = 4023233417, c = 2562383102, d = 271733878;
|
|
236
|
-
const X = new Array(16);
|
|
237
|
-
const F = (x, y, z) => x & y | ~x & z;
|
|
238
|
-
const G = (x, y, z) => x & y | x & z | y & z;
|
|
239
|
-
const H = (x, y, z) => x ^ y ^ z;
|
|
240
|
-
const FF = (aa, bb, cc, dd, k, s) => rotl(aa + F(bb, cc, dd) + X[k] >>> 0, s);
|
|
241
|
-
const GG = (aa, bb, cc, dd, k, s) => rotl(aa + G(bb, cc, dd) + X[k] + 1518500249 >>> 0, s);
|
|
242
|
-
const HH = (aa, bb, cc, dd, k, s) => rotl(aa + H(bb, cc, dd) + X[k] + 1859775393 >>> 0, s);
|
|
243
|
-
for (let i = 0; i < total; i += 64) {
|
|
244
|
-
for (let j = 0; j < 16; j++) X[j] = buf.readUInt32LE(i + j * 4);
|
|
245
|
-
const aa = a, bb = b, cc = c, dd = d;
|
|
246
|
-
for (let k = 0; k < 16; k += 4) {
|
|
247
|
-
a = FF(a, b, c, d, k, 3);
|
|
248
|
-
d = FF(d, a, b, c, k + 1, 7);
|
|
249
|
-
c = FF(c, d, a, b, k + 2, 11);
|
|
250
|
-
b = FF(b, c, d, a, k + 3, 19);
|
|
251
|
-
}
|
|
252
|
-
for (let k = 0; k < 4; k++) {
|
|
253
|
-
a = GG(a, b, c, d, k, 3);
|
|
254
|
-
d = GG(d, a, b, c, k + 4, 5);
|
|
255
|
-
c = GG(c, d, a, b, k + 8, 9);
|
|
256
|
-
b = GG(b, c, d, a, k + 12, 13);
|
|
257
|
-
}
|
|
258
|
-
const order = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
|
|
259
|
-
for (let k = 0; k < 16; k += 4) {
|
|
260
|
-
a = HH(a, b, c, d, order[k], 3);
|
|
261
|
-
d = HH(d, a, b, c, order[k + 1], 9);
|
|
262
|
-
c = HH(c, d, a, b, order[k + 2], 11);
|
|
263
|
-
b = HH(b, c, d, a, order[k + 3], 15);
|
|
264
|
-
}
|
|
265
|
-
a = a + aa >>> 0;
|
|
266
|
-
b = b + bb >>> 0;
|
|
267
|
-
c = c + cc >>> 0;
|
|
268
|
-
d = d + dd >>> 0;
|
|
269
|
-
}
|
|
270
|
-
const out = Buffer.alloc(16);
|
|
271
|
-
out.writeUInt32LE(a, 0);
|
|
272
|
-
out.writeUInt32LE(b, 4);
|
|
273
|
-
out.writeUInt32LE(c, 8);
|
|
274
|
-
out.writeUInt32LE(d, 12);
|
|
275
|
-
return out;
|
|
276
|
-
}
|
|
277
|
-
const utf16le = (s) => Buffer.from(s, "utf16le");
|
|
278
|
-
const hmacMd5 = (key, data) => crypto.createHmac("md5", key).update(data).digest();
|
|
279
|
-
function ntowfv2(user, domain, password) {
|
|
280
|
-
const ntHash = md4(utf16le(password));
|
|
281
|
-
return hmacMd5(ntHash, utf16le(user.toUpperCase() + domain));
|
|
282
|
-
}
|
|
283
|
-
function filetime(unixMs) {
|
|
284
|
-
const ticks = (BigInt(unixMs) + 11644473600000n) * 10000n;
|
|
285
|
-
const buf = Buffer.alloc(8);
|
|
286
|
-
buf.writeBigUInt64LE(ticks);
|
|
287
|
-
return buf;
|
|
288
|
-
}
|
|
289
|
-
function createType1Message() {
|
|
290
|
-
const msg = Buffer.alloc(32);
|
|
291
|
-
SIGNATURE.copy(msg, 0);
|
|
292
|
-
msg.writeUInt32LE(1, 8);
|
|
293
|
-
msg.writeUInt32LE(TYPE1_FLAGS, 12);
|
|
294
|
-
msg.writeUInt32LE(32, 16);
|
|
295
|
-
msg.writeUInt32LE(32, 24);
|
|
296
|
-
return msg.toString("base64");
|
|
297
|
-
}
|
|
298
|
-
function decodeType2Message(token) {
|
|
299
|
-
const buf = Buffer.isBuffer(token) ? token : Buffer.from(token, "base64");
|
|
300
|
-
if (buf.length < 32 || !buf.subarray(0, 8).equals(SIGNATURE)) {
|
|
301
|
-
throw new Error("Invalid NTLM Type 2 message");
|
|
302
|
-
}
|
|
303
|
-
const flags = buf.readUInt32LE(20);
|
|
304
|
-
const serverChallenge = Buffer.from(buf.subarray(24, 32));
|
|
305
|
-
let targetInfo = Buffer.alloc(0);
|
|
306
|
-
if (buf.length >= 48) {
|
|
307
|
-
const tiLen = buf.readUInt16LE(40);
|
|
308
|
-
const tiOff = buf.readUInt32LE(44);
|
|
309
|
-
if (tiLen > 0 && tiOff + tiLen <= buf.length) {
|
|
310
|
-
targetInfo = Buffer.from(buf.subarray(tiOff, tiOff + tiLen));
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return { serverChallenge, targetInfo, flags };
|
|
314
|
-
}
|
|
315
|
-
function computeNtlmV2Response(opts) {
|
|
316
|
-
const responseKey = ntowfv2(opts.user, opts.domain, opts.password);
|
|
317
|
-
const blob = Buffer.concat([
|
|
318
|
-
Buffer.from([1, 1, 0, 0]),
|
|
319
|
-
// RespType + HiRespType + reserved
|
|
320
|
-
Buffer.from([0, 0, 0, 0]),
|
|
321
|
-
opts.timestamp,
|
|
322
|
-
opts.clientChallenge,
|
|
323
|
-
Buffer.from([0, 0, 0, 0]),
|
|
324
|
-
opts.targetInfo,
|
|
325
|
-
Buffer.from([0, 0, 0, 0])
|
|
326
|
-
]);
|
|
327
|
-
const ntProof = hmacMd5(responseKey, Buffer.concat([opts.serverChallenge, blob]));
|
|
328
|
-
const ntResponse = Buffer.concat([ntProof, blob]);
|
|
329
|
-
const lmProof = hmacMd5(responseKey, Buffer.concat([opts.serverChallenge, opts.clientChallenge]));
|
|
330
|
-
const lmResponse = Buffer.concat([lmProof, opts.clientChallenge]);
|
|
331
|
-
return { ntResponse, lmResponse, ntProof };
|
|
332
|
-
}
|
|
333
|
-
function createType3Message(opts) {
|
|
334
|
-
const domain = opts.domain ?? "";
|
|
335
|
-
const workstation = opts.workstation ?? "";
|
|
336
|
-
const clientChallenge = opts.clientChallenge ?? crypto.randomBytes(8);
|
|
337
|
-
const timestamp = filetime(opts.timestamp ?? Date.now());
|
|
338
|
-
const { ntResponse, lmResponse } = computeNtlmV2Response({
|
|
339
|
-
user: opts.user,
|
|
340
|
-
domain,
|
|
341
|
-
password: opts.password,
|
|
342
|
-
serverChallenge: opts.challenge.serverChallenge,
|
|
343
|
-
targetInfo: opts.challenge.targetInfo,
|
|
344
|
-
clientChallenge,
|
|
345
|
-
timestamp
|
|
346
|
-
});
|
|
347
|
-
const domainBuf = utf16le(domain);
|
|
348
|
-
const userBuf = utf16le(opts.user);
|
|
349
|
-
const wsBuf = utf16le(workstation);
|
|
350
|
-
const HEADER = 64;
|
|
351
|
-
const payload = Buffer.concat([lmResponse, ntResponse, domainBuf, userBuf, wsBuf]);
|
|
352
|
-
const msg = Buffer.alloc(HEADER + payload.length);
|
|
353
|
-
SIGNATURE.copy(msg, 0);
|
|
354
|
-
msg.writeUInt32LE(3, 8);
|
|
355
|
-
let off = HEADER;
|
|
356
|
-
const writeField = (pos, buf) => {
|
|
357
|
-
msg.writeUInt16LE(buf.length, pos);
|
|
358
|
-
msg.writeUInt16LE(buf.length, pos + 2);
|
|
359
|
-
msg.writeUInt32LE(buf.length ? off : HEADER, pos + 4);
|
|
360
|
-
buf.copy(msg, off);
|
|
361
|
-
off += buf.length;
|
|
362
|
-
};
|
|
363
|
-
writeField(12, lmResponse);
|
|
364
|
-
writeField(20, ntResponse);
|
|
365
|
-
writeField(28, domainBuf);
|
|
366
|
-
writeField(36, userBuf);
|
|
367
|
-
writeField(44, wsBuf);
|
|
368
|
-
msg.writeUInt16LE(0, 52);
|
|
369
|
-
msg.writeUInt16LE(0, 54);
|
|
370
|
-
msg.writeUInt32LE(HEADER, 56);
|
|
371
|
-
msg.writeUInt32LE(TYPE3_FLAGS, 60);
|
|
372
|
-
return msg.toString("base64");
|
|
373
|
-
}
|
|
374
|
-
async function buildAuthHeaders(auth, vars) {
|
|
375
|
-
const headers = {};
|
|
376
|
-
if (auth.type === "bearer") {
|
|
377
|
-
let token = auth.token ?? "";
|
|
378
|
-
if (!token && auth.tokenSecretRef) token = await getSecret(auth.tokenSecretRef) ?? "";
|
|
379
|
-
token = interpolate(token, vars);
|
|
380
|
-
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
381
|
-
}
|
|
382
|
-
if (auth.type === "basic") {
|
|
383
|
-
let password = auth.password ?? "";
|
|
384
|
-
if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
|
|
385
|
-
password = interpolate(password, vars);
|
|
386
|
-
const username = interpolate(auth.username ?? "", vars);
|
|
387
|
-
headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
388
|
-
}
|
|
389
|
-
if (auth.type === "apikey" && auth.apiKeyIn === "header") {
|
|
390
|
-
let value = auth.apiKeyValue ?? "";
|
|
391
|
-
if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
|
|
392
|
-
value = interpolate(value, vars);
|
|
393
|
-
headers[auth.apiKeyName ?? "X-API-Key"] = value;
|
|
394
|
-
}
|
|
395
|
-
if (auth.type === "oauth2") {
|
|
396
|
-
const now = Date.now();
|
|
397
|
-
if (auth.oauth2CachedToken && auth.oauth2TokenExpiry && auth.oauth2TokenExpiry > now + 5e3) {
|
|
398
|
-
headers["Authorization"] = `Bearer ${auth.oauth2CachedToken}`;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return headers;
|
|
402
|
-
}
|
|
403
|
-
async function buildApiKeyParam(auth, vars) {
|
|
404
|
-
if (auth.type !== "apikey" || auth.apiKeyIn !== "query") return null;
|
|
405
|
-
let value = auth.apiKeyValue ?? "";
|
|
406
|
-
if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
|
|
407
|
-
value = interpolate(value, vars);
|
|
408
|
-
return { key: auth.apiKeyName ?? "apikey", value };
|
|
409
|
-
}
|
|
410
|
-
function parseDigestChallenge(wwwAuth) {
|
|
411
|
-
const extract = (key) => {
|
|
412
|
-
const m = new RegExp(`${key}="([^"]*)"`, "i").exec(wwwAuth);
|
|
413
|
-
return m ? m[1] : "";
|
|
414
|
-
};
|
|
415
|
-
const extractUnquoted = (key) => {
|
|
416
|
-
const m = new RegExp(`${key}=([^,\\s]+)`, "i").exec(wwwAuth);
|
|
417
|
-
return m ? m[1] : "";
|
|
418
|
-
};
|
|
419
|
-
return {
|
|
420
|
-
realm: extract("realm"),
|
|
421
|
-
nonce: extract("nonce"),
|
|
422
|
-
qop: extract("qop") || extractUnquoted("qop") || void 0,
|
|
423
|
-
algorithm: extract("algorithm") || extractUnquoted("algorithm") || "MD5",
|
|
424
|
-
opaque: extract("opaque") || void 0
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
function md5(s) {
|
|
428
|
-
return crypto.createHash("md5").update(s).digest("hex");
|
|
429
|
-
}
|
|
430
|
-
function buildDigestAuthHeader(challenge, username, password, method, uri) {
|
|
431
|
-
const { realm, nonce, qop, algorithm, opaque } = challenge;
|
|
432
|
-
const algo = (algorithm ?? "MD5").toUpperCase();
|
|
433
|
-
const ha1 = algo === "MD5-SESS" ? md5(`${md5(`${username}:${realm}:${password}`)}:${nonce}:`) : md5(`${username}:${realm}:${password}`);
|
|
434
|
-
const ha2 = md5(`${method}:${uri}`);
|
|
435
|
-
let response;
|
|
436
|
-
let nc;
|
|
437
|
-
let cnonce;
|
|
438
|
-
if (qop === "auth" || qop === "auth-int") {
|
|
439
|
-
nc = "00000001";
|
|
440
|
-
cnonce = crypto.randomBytes(8).toString("hex");
|
|
441
|
-
response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`);
|
|
442
|
-
} else {
|
|
443
|
-
response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
444
|
-
}
|
|
445
|
-
let header = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${response}"`;
|
|
446
|
-
if (qop) header += `, qop=${qop}`;
|
|
447
|
-
if (nc) header += `, nc=${nc}`;
|
|
448
|
-
if (cnonce) header += `, cnonce="${cnonce}"`;
|
|
449
|
-
if (opaque) header += `, opaque="${opaque}"`;
|
|
450
|
-
if (algo !== "MD5") header += `, algorithm=${algo}`;
|
|
451
|
-
return header;
|
|
452
|
-
}
|
|
453
|
-
async function performDigestAuth(url, method, auth, vars, fetchFn) {
|
|
454
|
-
const probeResp = await fetchFn(url, { method, headers: {} });
|
|
455
|
-
if (probeResp.status !== 401) return null;
|
|
456
|
-
const wwwAuth = probeResp.headers.get("www-authenticate") ?? "";
|
|
457
|
-
if (!wwwAuth.toLowerCase().startsWith("digest")) return null;
|
|
458
|
-
const challenge = parseDigestChallenge(wwwAuth);
|
|
459
|
-
let password = auth.password ?? "";
|
|
460
|
-
if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
|
|
461
|
-
password = interpolate(password, vars);
|
|
462
|
-
const username = interpolate(auth.username ?? "", vars);
|
|
463
|
-
let uri = "/";
|
|
464
|
-
try {
|
|
465
|
-
uri = new URL(url).pathname + (new URL(url).search ?? "");
|
|
466
|
-
} catch {
|
|
467
|
-
}
|
|
468
|
-
return buildDigestAuthHeader(challenge, username, password, method, uri);
|
|
469
|
-
}
|
|
470
|
-
function extractNtlmChallenge(value) {
|
|
471
|
-
if (!value) return null;
|
|
472
|
-
const values = Array.isArray(value) ? value : [value];
|
|
473
|
-
for (const v of values) {
|
|
474
|
-
for (const part of v.split(",")) {
|
|
475
|
-
const m = /^\s*NTLM\s+(.+)\s*$/i.exec(part);
|
|
476
|
-
if (m) return m[1].trim();
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
async function buildNtlmConnectOpts(tls) {
|
|
482
|
-
if (!tls) return void 0;
|
|
483
|
-
const connect = {};
|
|
484
|
-
if (tls.rejectUnauthorized !== void 0) connect["rejectUnauthorized"] = tls.rejectUnauthorized;
|
|
485
|
-
if (tls.caCertPath) {
|
|
486
|
-
try {
|
|
487
|
-
connect["ca"] = await promises.readFile(tls.caCertPath);
|
|
488
|
-
} catch {
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (tls.clientCertPath) {
|
|
492
|
-
try {
|
|
493
|
-
connect["cert"] = await promises.readFile(tls.clientCertPath);
|
|
494
|
-
} catch {
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (tls.clientKeyPath) {
|
|
498
|
-
try {
|
|
499
|
-
connect["key"] = await promises.readFile(tls.clientKeyPath);
|
|
500
|
-
} catch {
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return Object.keys(connect).length ? connect : void 0;
|
|
504
|
-
}
|
|
505
|
-
async function performNtlmRequest(opts) {
|
|
506
|
-
if (opts.proxy?.url) {
|
|
507
|
-
throw new Error("NTLM authentication through a proxy is not supported. Disable the proxy for this request, or target the server directly.");
|
|
508
|
-
}
|
|
509
|
-
const { Client } = await import("undici");
|
|
510
|
-
let password = opts.auth.password ?? "";
|
|
511
|
-
if (!password && opts.auth.passwordSecretRef) password = await getSecret(opts.auth.passwordSecretRef) ?? "";
|
|
512
|
-
password = interpolate(password, opts.vars);
|
|
513
|
-
const username = interpolate(opts.auth.username ?? "", opts.vars);
|
|
514
|
-
const domain = interpolate(opts.auth.ntlmDomain ?? "", opts.vars);
|
|
515
|
-
const workstation = interpolate(opts.auth.ntlmWorkstation ?? "", opts.vars);
|
|
516
|
-
const parsed = new URL(opts.url);
|
|
517
|
-
const origin = `${parsed.protocol}//${parsed.host}`;
|
|
518
|
-
const path2 = parsed.pathname + parsed.search;
|
|
519
|
-
const connect = await buildNtlmConnectOpts(opts.tls);
|
|
520
|
-
const client = new Client(origin, { pipelining: 1, ...connect ? { connect } : {} });
|
|
521
|
-
const toAdapter = (statusCode, headers, bodyText) => {
|
|
522
|
-
const entries = [];
|
|
523
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
524
|
-
if (v === void 0) continue;
|
|
525
|
-
entries.push([k, Array.isArray(v) ? v.join(", ") : String(v)]);
|
|
526
|
-
}
|
|
527
|
-
return {
|
|
528
|
-
status: statusCode,
|
|
529
|
-
statusText: http.STATUS_CODES[statusCode] ?? "",
|
|
530
|
-
headers: { forEach: (cb) => entries.forEach(([k, v]) => cb(v, k)) },
|
|
531
|
-
text: () => Promise.resolve(bodyText)
|
|
532
|
-
};
|
|
533
|
-
};
|
|
534
|
-
try {
|
|
535
|
-
const negotiate = await client.request({
|
|
536
|
-
path: path2,
|
|
537
|
-
method: opts.method,
|
|
538
|
-
headers: { authorization: `NTLM ${createType1Message()}` }
|
|
539
|
-
});
|
|
540
|
-
await negotiate.body.text();
|
|
541
|
-
const challengeToken = extractNtlmChallenge(negotiate.headers["www-authenticate"]);
|
|
542
|
-
if (negotiate.statusCode !== 401 || !challengeToken) {
|
|
543
|
-
const second = await client.request({ path: path2, method: opts.method, headers: opts.baseHeaders, body: opts.body });
|
|
544
|
-
const bodyText2 = await second.body.text();
|
|
545
|
-
return toAdapter(second.statusCode, second.headers, bodyText2);
|
|
546
|
-
}
|
|
547
|
-
const challenge = decodeType2Message(challengeToken);
|
|
548
|
-
const type3 = createType3Message({ user: username, password, domain, workstation, challenge });
|
|
549
|
-
const authed = await client.request({
|
|
550
|
-
path: path2,
|
|
551
|
-
method: opts.method,
|
|
552
|
-
headers: { ...opts.baseHeaders, authorization: `NTLM ${type3}` },
|
|
553
|
-
body: opts.body
|
|
554
|
-
});
|
|
555
|
-
const bodyText = await authed.body.text();
|
|
556
|
-
return toAdapter(authed.statusCode, authed.headers, bodyText);
|
|
557
|
-
} finally {
|
|
558
|
-
await client.close().catch(() => {
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
async function fetchOAuth2Token(auth, vars) {
|
|
563
|
-
const flow = auth.oauth2Flow ?? "client_credentials";
|
|
564
|
-
if (flow === "authorization_code") {
|
|
565
|
-
throw new Error("authorization_code flow requires the oauth2:startFlow IPC call from the renderer.");
|
|
566
|
-
}
|
|
567
|
-
if (flow === "implicit") {
|
|
568
|
-
throw new Error("implicit flow cannot be performed server-side — tokens must be obtained via the browser redirect.");
|
|
569
|
-
}
|
|
570
|
-
const tokenUrl = interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
571
|
-
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required.");
|
|
572
|
-
const clientId = interpolate(auth.oauth2ClientId ?? "", vars);
|
|
573
|
-
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
574
|
-
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
575
|
-
clientSecret = await getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
576
|
-
}
|
|
577
|
-
clientSecret = interpolate(clientSecret, vars);
|
|
578
|
-
const params = new URLSearchParams();
|
|
579
|
-
params.set("grant_type", flow === "password" ? "password" : "client_credentials");
|
|
580
|
-
params.set("client_id", clientId);
|
|
581
|
-
params.set("client_secret", clientSecret);
|
|
582
|
-
if (auth.oauth2Scopes) params.set("scope", auth.oauth2Scopes);
|
|
583
|
-
if (flow === "password") {
|
|
584
|
-
let password = auth.password ?? "";
|
|
585
|
-
if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
|
|
586
|
-
password = interpolate(password, vars);
|
|
587
|
-
params.set("username", interpolate(auth.username ?? "", vars));
|
|
588
|
-
params.set("password", password);
|
|
589
|
-
}
|
|
590
|
-
const { fetch: nodeFetch } = await import("undici");
|
|
591
|
-
const resp = await nodeFetch(tokenUrl, {
|
|
592
|
-
method: "POST",
|
|
593
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
594
|
-
body: params.toString()
|
|
595
|
-
});
|
|
596
|
-
if (!resp.ok) {
|
|
597
|
-
const body = await resp.text();
|
|
598
|
-
throw new Error(`OAuth 2.0 token request failed (${resp.status}): ${body}`);
|
|
599
|
-
}
|
|
600
|
-
const json = await resp.json();
|
|
601
|
-
const accessToken = String(json["access_token"] ?? "");
|
|
602
|
-
if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
|
|
603
|
-
const expiresIn = Number(json["expires_in"] ?? 3600);
|
|
604
|
-
const expiresAt = Date.now() + expiresIn * 1e3;
|
|
605
|
-
return {
|
|
606
|
-
accessToken,
|
|
607
|
-
expiresAt,
|
|
608
|
-
refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
exports.buildApiKeyParam = buildApiKeyParam;
|
|
612
|
-
exports.buildAuthHeaders = buildAuthHeaders;
|
|
613
|
-
exports.buildDynamicVars = buildDynamicVars;
|
|
614
|
-
exports.buildEnvVars = buildEnvVars;
|
|
615
|
-
exports.buildUrl = buildUrl;
|
|
616
|
-
exports.fetchOAuth2Token = fetchOAuth2Token;
|
|
617
|
-
exports.getSecret = getSecret;
|
|
618
|
-
exports.initSecretStore = initSecretStore;
|
|
619
|
-
exports.interpolate = interpolate;
|
|
620
|
-
exports.mergeVars = mergeVars;
|
|
621
|
-
exports.performDigestAuth = performDigestAuth;
|
|
622
|
-
exports.performNtlmRequest = performNtlmRequest;
|
|
623
|
-
exports.registerSecretHandlers = registerSecretHandlers;
|