@testsmith/api-spector 0.2.2 → 0.2.3
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 -3
- package/out/main/chunks/auth-builder-B7-LgcGr.js +373 -0
- package/out/main/chunks/{request-collection-Dx0ZqB54.js → request-collection-DMVlm0PA.js} +23 -349
- package/out/main/chunks/snapshots-C7YbGHM7.js +588 -0
- package/out/main/contract.js +210 -0
- package/out/main/index.js +78 -507
- package/out/main/runner.js +17 -16
- package/out/preload/index.js +4 -0
- package/out/renderer/assets/{index-DVaubmCJ.css → index-CxdQOFBM.css} +1 -1
- package/out/renderer/assets/{index-C_vjoxxA.js → index-DdHHEKaz.js} +997 -843
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -12,9 +12,10 @@ function printHelp() {
|
|
|
12
12
|
console.log('')
|
|
13
13
|
console.log(' Usage:')
|
|
14
14
|
console.log(' api-spector ui Launch the app')
|
|
15
|
-
console.log(' api-spector run
|
|
16
|
-
console.log(' api-spector mock
|
|
17
|
-
console.log(' api-spector record
|
|
15
|
+
console.log(' api-spector run --workspace <path> Run tests from CLI')
|
|
16
|
+
console.log(' api-spector mock --workspace <path> Start mock servers from CLI')
|
|
17
|
+
console.log(' api-spector record --upstream <url> Record API traffic as mock stubs')
|
|
18
|
+
console.log(' api-spector contract list|run Manage & run pinned contract snapshots')
|
|
18
19
|
console.log('')
|
|
19
20
|
console.log(' Options:')
|
|
20
21
|
console.log(' api-spector agents init <name> Initialize AI agent files')
|
|
@@ -69,6 +70,13 @@ if (cmd === '--help' || cmd === '-h') {
|
|
|
69
70
|
env: process.env,
|
|
70
71
|
})
|
|
71
72
|
proc.on('close', code => process.exit(code ?? 0))
|
|
73
|
+
} else if (cmd === 'contract') {
|
|
74
|
+
const contractPath = path.join(__dirname, '..', 'out', 'main', 'contract.js')
|
|
75
|
+
const proc = spawn(process.execPath, [contractPath, ...rest], {
|
|
76
|
+
stdio: 'inherit',
|
|
77
|
+
env: process.env,
|
|
78
|
+
})
|
|
79
|
+
proc.on('close', code => process.exit(code ?? 0))
|
|
72
80
|
} else {
|
|
73
81
|
console.error(`API Spector — unknown command: "${cmd}"`)
|
|
74
82
|
printHelp()
|
|
@@ -0,0 +1,373 @@
|
|
|
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 promises = require("fs/promises");
|
|
26
|
+
const path = require("path");
|
|
27
|
+
const dayjs = require("dayjs");
|
|
28
|
+
const vm = require("vm");
|
|
29
|
+
function _interopNamespaceDefault(e) {
|
|
30
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
31
|
+
if (e) {
|
|
32
|
+
for (const k in e) {
|
|
33
|
+
if (k !== "default") {
|
|
34
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
35
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
get: () => e[k]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
n.default = e;
|
|
43
|
+
return Object.freeze(n);
|
|
44
|
+
}
|
|
45
|
+
const vm__namespace = /* @__PURE__ */ _interopNamespaceDefault(vm);
|
|
46
|
+
const MASTER_KEY_ENV = "API_SPECTOR_MASTER_KEY";
|
|
47
|
+
let secretStore = {};
|
|
48
|
+
let secretStorePath = null;
|
|
49
|
+
async function initSecretStore(userDataPath) {
|
|
50
|
+
secretStorePath = path.join(userDataPath, "secrets.json");
|
|
51
|
+
try {
|
|
52
|
+
const raw = await promises.readFile(secretStorePath, "utf8");
|
|
53
|
+
secretStore = JSON.parse(raw);
|
|
54
|
+
} catch {
|
|
55
|
+
secretStore = {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function persistSecretStore() {
|
|
59
|
+
if (!secretStorePath) return;
|
|
60
|
+
await promises.writeFile(secretStorePath, JSON.stringify(secretStore, null, 2), "utf8");
|
|
61
|
+
}
|
|
62
|
+
function getSafeStorage() {
|
|
63
|
+
try {
|
|
64
|
+
const { safeStorage } = require("electron");
|
|
65
|
+
if (typeof safeStorage?.isEncryptionAvailable === "function") return safeStorage;
|
|
66
|
+
return null;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function registerSecretHandlers(ipc) {
|
|
72
|
+
ipc.handle("secret:checkMasterKey", () => {
|
|
73
|
+
return { set: Boolean(process.env[MASTER_KEY_ENV]) };
|
|
74
|
+
});
|
|
75
|
+
ipc.handle("secret:setMasterKey", (_e, value) => {
|
|
76
|
+
process.env[MASTER_KEY_ENV] = value;
|
|
77
|
+
});
|
|
78
|
+
ipc.handle("secret:set", async (_e, ref, value) => {
|
|
79
|
+
const ss = getSafeStorage();
|
|
80
|
+
if (!ss || !ss.isEncryptionAvailable()) {
|
|
81
|
+
throw new Error("OS encryption is not available — set the secret via environment variable instead");
|
|
82
|
+
}
|
|
83
|
+
secretStore[ref] = ss.encryptString(value).toString("base64");
|
|
84
|
+
await persistSecretStore();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function decryptSecret(encrypted, salt, iv, password) {
|
|
88
|
+
const saltBuf = Buffer.from(salt, "base64");
|
|
89
|
+
const ivBuf = Buffer.from(iv, "base64");
|
|
90
|
+
const encBuf = Buffer.from(encrypted, "base64");
|
|
91
|
+
const key = crypto.pbkdf2Sync(password, saltBuf, 1e5, 32, "sha256");
|
|
92
|
+
const authTag = encBuf.subarray(encBuf.length - 16);
|
|
93
|
+
const ciphertext = encBuf.subarray(0, encBuf.length - 16);
|
|
94
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, ivBuf);
|
|
95
|
+
decipher.setAuthTag(authTag);
|
|
96
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
97
|
+
}
|
|
98
|
+
async function getSecret(ref) {
|
|
99
|
+
const stored = secretStore[ref];
|
|
100
|
+
if (stored) {
|
|
101
|
+
const ss = getSafeStorage();
|
|
102
|
+
if (ss && ss.isEncryptionAvailable()) {
|
|
103
|
+
try {
|
|
104
|
+
return ss.decryptString(Buffer.from(stored, "base64"));
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return process.env[ref] ?? null;
|
|
110
|
+
}
|
|
111
|
+
let _fakerCache = null;
|
|
112
|
+
async function getFaker() {
|
|
113
|
+
if (!_fakerCache) _fakerCache = await import("@faker-js/faker");
|
|
114
|
+
return _fakerCache.faker;
|
|
115
|
+
}
|
|
116
|
+
let _exprContext = null;
|
|
117
|
+
async function buildDynamicVars() {
|
|
118
|
+
const faker = await getFaker();
|
|
119
|
+
const now = dayjs();
|
|
120
|
+
_exprContext = { faker, dayjs };
|
|
121
|
+
return {
|
|
122
|
+
$uuid: faker.string.uuid(),
|
|
123
|
+
$timestamp: String(Date.now()),
|
|
124
|
+
$isoTimestamp: now.toISOString(),
|
|
125
|
+
$randomInt: String(faker.number.int({ min: 0, max: 1e3 })),
|
|
126
|
+
$randomFloat: String(faker.number.float({ min: 0, max: 1e3, fractionDigits: 2 })),
|
|
127
|
+
$randomBoolean: String(faker.datatype.boolean()),
|
|
128
|
+
$randomEmail: faker.internet.email(),
|
|
129
|
+
$randomUsername: faker.internet.username(),
|
|
130
|
+
$randomPassword: faker.internet.password(),
|
|
131
|
+
$randomFullName: faker.person.fullName(),
|
|
132
|
+
$randomFirstName: faker.person.firstName(),
|
|
133
|
+
$randomLastName: faker.person.lastName(),
|
|
134
|
+
$randomWord: faker.lorem.word(),
|
|
135
|
+
$randomPhrase: faker.lorem.sentence(),
|
|
136
|
+
$randomUrl: faker.internet.url(),
|
|
137
|
+
$randomIp: faker.internet.ip(),
|
|
138
|
+
$randomHexColor: faker.color.rgb({ format: "hex", casing: "lower" })
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function interpolate(str, vars) {
|
|
142
|
+
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
143
|
+
const trimmed = key.trim();
|
|
144
|
+
if (trimmed in vars) return vars[trimmed];
|
|
145
|
+
if (_exprContext && (trimmed.includes(".") || trimmed.includes("("))) {
|
|
146
|
+
try {
|
|
147
|
+
const result = vm__namespace.runInNewContext(trimmed, _exprContext);
|
|
148
|
+
if (result !== void 0 && result !== null) return String(result);
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return match;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function buildUrl(baseUrl, params, vars) {
|
|
156
|
+
const templateTokens = /* @__PURE__ */ new Set();
|
|
157
|
+
baseUrl.replace(/\{\{([^}]+)\}\}/g, (_m, name) => {
|
|
158
|
+
templateTokens.add(String(name).trim());
|
|
159
|
+
return "";
|
|
160
|
+
});
|
|
161
|
+
const enabled = (params ?? []).filter((p) => p.enabled && p.key);
|
|
162
|
+
const pathRows = [];
|
|
163
|
+
const queryRows = [];
|
|
164
|
+
for (const p of enabled) {
|
|
165
|
+
const isPath = p.paramType === "path" || templateTokens.has(p.key);
|
|
166
|
+
if (isPath) pathRows.push(p);
|
|
167
|
+
else queryRows.push(p);
|
|
168
|
+
}
|
|
169
|
+
const mergedVars = pathRows.length ? {
|
|
170
|
+
...vars,
|
|
171
|
+
...Object.fromEntries(pathRows.map((p) => [p.key, interpolate(p.value, vars)]))
|
|
172
|
+
} : vars;
|
|
173
|
+
const url = interpolate(baseUrl, mergedVars);
|
|
174
|
+
if (!queryRows.length) return url;
|
|
175
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
176
|
+
const qs = queryRows.map((p) => `${encodeURIComponent(interpolate(p.key, vars))}=${encodeURIComponent(interpolate(p.value, vars))}`).join("&");
|
|
177
|
+
return url + sep + qs;
|
|
178
|
+
}
|
|
179
|
+
async function buildEnvVars(environment) {
|
|
180
|
+
const vars = {};
|
|
181
|
+
if (!environment) return vars;
|
|
182
|
+
const masterKey = process.env["API_SPECTOR_MASTER_KEY"];
|
|
183
|
+
for (const v of environment.variables) {
|
|
184
|
+
if (!v.enabled) continue;
|
|
185
|
+
if (v.envRef) {
|
|
186
|
+
const envValue = process.env[v.envRef];
|
|
187
|
+
if (envValue !== void 0) vars[v.key] = envValue;
|
|
188
|
+
} else if (v.secret && v.secretEncrypted && v.secretSalt && v.secretIv) {
|
|
189
|
+
if (masterKey) {
|
|
190
|
+
try {
|
|
191
|
+
vars[v.key] = decryptSecret(v.secretEncrypted, v.secretSalt, v.secretIv, masterKey);
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (vars[v.key] === void 0 && process.env[v.key] !== void 0) {
|
|
196
|
+
vars[v.key] = process.env[v.key];
|
|
197
|
+
}
|
|
198
|
+
} else if (v.secret) {
|
|
199
|
+
if (process.env[v.key] !== void 0) {
|
|
200
|
+
vars[v.key] = process.env[v.key];
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
vars[v.key] = v.value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return vars;
|
|
207
|
+
}
|
|
208
|
+
function mergeVars(envVars, collectionVars, globals, localVars = {}, dynamicVars = {}) {
|
|
209
|
+
return { ...dynamicVars, ...globals, ...collectionVars, ...envVars, ...localVars };
|
|
210
|
+
}
|
|
211
|
+
async function buildAuthHeaders(auth, vars) {
|
|
212
|
+
const headers = {};
|
|
213
|
+
if (auth.type === "bearer") {
|
|
214
|
+
let token = auth.token ?? "";
|
|
215
|
+
if (!token && auth.tokenSecretRef) token = await getSecret(auth.tokenSecretRef) ?? "";
|
|
216
|
+
token = interpolate(token, vars);
|
|
217
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
218
|
+
}
|
|
219
|
+
if (auth.type === "basic") {
|
|
220
|
+
let password = auth.password ?? "";
|
|
221
|
+
if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
|
|
222
|
+
password = interpolate(password, vars);
|
|
223
|
+
const username = interpolate(auth.username ?? "", vars);
|
|
224
|
+
headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
225
|
+
}
|
|
226
|
+
if (auth.type === "apikey" && auth.apiKeyIn === "header") {
|
|
227
|
+
let value = auth.apiKeyValue ?? "";
|
|
228
|
+
if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
|
|
229
|
+
value = interpolate(value, vars);
|
|
230
|
+
headers[auth.apiKeyName ?? "X-API-Key"] = value;
|
|
231
|
+
}
|
|
232
|
+
if (auth.type === "oauth2") {
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
if (auth.oauth2CachedToken && auth.oauth2TokenExpiry && auth.oauth2TokenExpiry > now + 5e3) {
|
|
235
|
+
headers["Authorization"] = `Bearer ${auth.oauth2CachedToken}`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return headers;
|
|
239
|
+
}
|
|
240
|
+
async function buildApiKeyParam(auth, vars) {
|
|
241
|
+
if (auth.type !== "apikey" || auth.apiKeyIn !== "query") return null;
|
|
242
|
+
let value = auth.apiKeyValue ?? "";
|
|
243
|
+
if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
|
|
244
|
+
value = interpolate(value, vars);
|
|
245
|
+
return { key: auth.apiKeyName ?? "apikey", value };
|
|
246
|
+
}
|
|
247
|
+
function parseDigestChallenge(wwwAuth) {
|
|
248
|
+
const extract = (key) => {
|
|
249
|
+
const m = new RegExp(`${key}="([^"]*)"`, "i").exec(wwwAuth);
|
|
250
|
+
return m ? m[1] : "";
|
|
251
|
+
};
|
|
252
|
+
const extractUnquoted = (key) => {
|
|
253
|
+
const m = new RegExp(`${key}=([^,\\s]+)`, "i").exec(wwwAuth);
|
|
254
|
+
return m ? m[1] : "";
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
realm: extract("realm"),
|
|
258
|
+
nonce: extract("nonce"),
|
|
259
|
+
qop: extract("qop") || extractUnquoted("qop") || void 0,
|
|
260
|
+
algorithm: extract("algorithm") || extractUnquoted("algorithm") || "MD5",
|
|
261
|
+
opaque: extract("opaque") || void 0
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function md5(s) {
|
|
265
|
+
return crypto.createHash("md5").update(s).digest("hex");
|
|
266
|
+
}
|
|
267
|
+
function buildDigestAuthHeader(challenge, username, password, method, uri) {
|
|
268
|
+
const { realm, nonce, qop, algorithm, opaque } = challenge;
|
|
269
|
+
const algo = (algorithm ?? "MD5").toUpperCase();
|
|
270
|
+
const ha1 = algo === "MD5-SESS" ? md5(`${md5(`${username}:${realm}:${password}`)}:${nonce}:`) : md5(`${username}:${realm}:${password}`);
|
|
271
|
+
const ha2 = md5(`${method}:${uri}`);
|
|
272
|
+
let response;
|
|
273
|
+
let nc;
|
|
274
|
+
let cnonce;
|
|
275
|
+
if (qop === "auth" || qop === "auth-int") {
|
|
276
|
+
nc = "00000001";
|
|
277
|
+
cnonce = crypto.randomBytes(8).toString("hex");
|
|
278
|
+
response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`);
|
|
279
|
+
} else {
|
|
280
|
+
response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
281
|
+
}
|
|
282
|
+
let header = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${response}"`;
|
|
283
|
+
if (qop) header += `, qop=${qop}`;
|
|
284
|
+
if (nc) header += `, nc=${nc}`;
|
|
285
|
+
if (cnonce) header += `, cnonce="${cnonce}"`;
|
|
286
|
+
if (opaque) header += `, opaque="${opaque}"`;
|
|
287
|
+
if (algo !== "MD5") header += `, algorithm=${algo}`;
|
|
288
|
+
return header;
|
|
289
|
+
}
|
|
290
|
+
async function performDigestAuth(url, method, auth, vars, fetchFn) {
|
|
291
|
+
const probeResp = await fetchFn(url, { method, headers: {} });
|
|
292
|
+
if (probeResp.status !== 401) return null;
|
|
293
|
+
const wwwAuth = probeResp.headers.get("www-authenticate") ?? "";
|
|
294
|
+
if (!wwwAuth.toLowerCase().startsWith("digest")) return null;
|
|
295
|
+
const challenge = parseDigestChallenge(wwwAuth);
|
|
296
|
+
let password = auth.password ?? "";
|
|
297
|
+
if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
|
|
298
|
+
password = interpolate(password, vars);
|
|
299
|
+
const username = interpolate(auth.username ?? "", vars);
|
|
300
|
+
let uri = "/";
|
|
301
|
+
try {
|
|
302
|
+
uri = new URL(url).pathname + (new URL(url).search ?? "");
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
return buildDigestAuthHeader(challenge, username, password, method, uri);
|
|
306
|
+
}
|
|
307
|
+
async function performNtlmRequest(_url, _method, _auth, _vars) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
'NTLM auth is not yet implemented. Add "httpntlm" to package.json dependencies and implement performNtlmRequest in auth-builder.ts.'
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
async function fetchOAuth2Token(auth, vars) {
|
|
313
|
+
const flow = auth.oauth2Flow ?? "client_credentials";
|
|
314
|
+
if (flow === "authorization_code") {
|
|
315
|
+
throw new Error("authorization_code flow requires the oauth2:startFlow IPC call from the renderer.");
|
|
316
|
+
}
|
|
317
|
+
if (flow === "implicit") {
|
|
318
|
+
throw new Error("implicit flow cannot be performed server-side — tokens must be obtained via the browser redirect.");
|
|
319
|
+
}
|
|
320
|
+
const tokenUrl = interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
321
|
+
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required.");
|
|
322
|
+
const clientId = interpolate(auth.oauth2ClientId ?? "", vars);
|
|
323
|
+
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
324
|
+
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
325
|
+
clientSecret = await getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
326
|
+
}
|
|
327
|
+
clientSecret = interpolate(clientSecret, vars);
|
|
328
|
+
const params = new URLSearchParams();
|
|
329
|
+
params.set("grant_type", flow === "password" ? "password" : "client_credentials");
|
|
330
|
+
params.set("client_id", clientId);
|
|
331
|
+
params.set("client_secret", clientSecret);
|
|
332
|
+
if (auth.oauth2Scopes) params.set("scope", auth.oauth2Scopes);
|
|
333
|
+
if (flow === "password") {
|
|
334
|
+
let password = auth.password ?? "";
|
|
335
|
+
if (!password && auth.passwordSecretRef) password = await getSecret(auth.passwordSecretRef) ?? "";
|
|
336
|
+
password = interpolate(password, vars);
|
|
337
|
+
params.set("username", interpolate(auth.username ?? "", vars));
|
|
338
|
+
params.set("password", password);
|
|
339
|
+
}
|
|
340
|
+
const { fetch: nodeFetch } = await import("undici");
|
|
341
|
+
const resp = await nodeFetch(tokenUrl, {
|
|
342
|
+
method: "POST",
|
|
343
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
344
|
+
body: params.toString()
|
|
345
|
+
});
|
|
346
|
+
if (!resp.ok) {
|
|
347
|
+
const body = await resp.text();
|
|
348
|
+
throw new Error(`OAuth 2.0 token request failed (${resp.status}): ${body}`);
|
|
349
|
+
}
|
|
350
|
+
const json = await resp.json();
|
|
351
|
+
const accessToken = String(json["access_token"] ?? "");
|
|
352
|
+
if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
|
|
353
|
+
const expiresIn = Number(json["expires_in"] ?? 3600);
|
|
354
|
+
const expiresAt = Date.now() + expiresIn * 1e3;
|
|
355
|
+
return {
|
|
356
|
+
accessToken,
|
|
357
|
+
expiresAt,
|
|
358
|
+
refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
exports.buildApiKeyParam = buildApiKeyParam;
|
|
362
|
+
exports.buildAuthHeaders = buildAuthHeaders;
|
|
363
|
+
exports.buildDynamicVars = buildDynamicVars;
|
|
364
|
+
exports.buildEnvVars = buildEnvVars;
|
|
365
|
+
exports.buildUrl = buildUrl;
|
|
366
|
+
exports.fetchOAuth2Token = fetchOAuth2Token;
|
|
367
|
+
exports.getSecret = getSecret;
|
|
368
|
+
exports.initSecretStore = initSecretStore;
|
|
369
|
+
exports.interpolate = interpolate;
|
|
370
|
+
exports.mergeVars = mergeVars;
|
|
371
|
+
exports.performDigestAuth = performDigestAuth;
|
|
372
|
+
exports.performNtlmRequest = performNtlmRequest;
|
|
373
|
+
exports.registerSecretHandlers = registerSecretHandlers;
|