@fedify/webfinger 2.3.14 → 2.3.15
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/deno.json +1 -1
- package/dist/lookup.test.cjs +558 -218
- package/dist/lookup.test.js +559 -218
- package/dist/mod.cjs +224 -153
- package/dist/mod.d.cts +27 -1
- package/dist/mod.d.ts +28 -2
- package/dist/mod.js +225 -155
- package/package.json +3 -3
- package/src/lookup-impl.ts +324 -0
- package/src/lookup.test.ts +515 -69
- package/src/lookup.ts +20 -219
package/dist/lookup.test.cjs
CHANGED
|
@@ -26,9 +26,9 @@ let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
|
26
26
|
let _logtape_logtape = require("@logtape/logtape");
|
|
27
27
|
let es_toolkit = require("es-toolkit");
|
|
28
28
|
let node_assert_strict = require("node:assert/strict");
|
|
29
|
-
let node_dns_promises = require("node:dns/promises");
|
|
30
|
-
node_dns_promises = __toESM(node_dns_promises, 1);
|
|
31
29
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
30
|
+
let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
|
|
31
|
+
let node_test = require("node:test");
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
|
|
34
34
|
var import_glob_to_regexp = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -1230,7 +1230,7 @@ var esm_default = new class FetchMock {
|
|
|
1230
1230
|
//#endregion
|
|
1231
1231
|
//#region deno.json
|
|
1232
1232
|
var name = "@fedify/webfinger";
|
|
1233
|
-
var version = "2.3.
|
|
1233
|
+
var version = "2.3.15";
|
|
1234
1234
|
//#endregion
|
|
1235
1235
|
//#region src/body.ts
|
|
1236
1236
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -1306,13 +1306,223 @@ async function readBoundedText(message, maxBytes, url) {
|
|
|
1306
1306
|
}
|
|
1307
1307
|
}
|
|
1308
1308
|
//#endregion
|
|
1309
|
-
//#region src/lookup.ts
|
|
1309
|
+
//#region src/lookup-impl.ts
|
|
1310
1310
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
1311
1311
|
"fedify",
|
|
1312
1312
|
"webfinger",
|
|
1313
1313
|
"lookup"
|
|
1314
1314
|
]);
|
|
1315
1315
|
const DEFAULT_MAX_REDIRECTION = 5;
|
|
1316
|
+
/**
|
|
1317
|
+
* Looks up a WebFinger resource, opening a fetch session per hop. This is
|
|
1318
|
+
* the implementation of {@link lookupWebFinger}, which is not exported from
|
|
1319
|
+
* the package entry point.
|
|
1320
|
+
* @param resource The resource URL to look up.
|
|
1321
|
+
* @param options Extra options for looking up the resource.
|
|
1322
|
+
* @param deps The dependencies.
|
|
1323
|
+
* @returns The resource descriptor and terminal lookup classification.
|
|
1324
|
+
* @internal
|
|
1325
|
+
*/
|
|
1326
|
+
async function lookupWebFingerInternal(resource, options = {}, deps = {}) {
|
|
1327
|
+
if (typeof resource === "string") resource = new URL(resource);
|
|
1328
|
+
let protocol = "https:";
|
|
1329
|
+
let server;
|
|
1330
|
+
if (resource.protocol === "acct:" || resource.protocol === "mailto:") {
|
|
1331
|
+
const atPos = resource.pathname.lastIndexOf("@");
|
|
1332
|
+
if (atPos < 0) return {
|
|
1333
|
+
resource: null,
|
|
1334
|
+
result: "invalid"
|
|
1335
|
+
};
|
|
1336
|
+
server = resource.pathname.substring(atPos + 1);
|
|
1337
|
+
if (server === "" || /[/?#]/.test(server)) return {
|
|
1338
|
+
resource: null,
|
|
1339
|
+
result: "invalid"
|
|
1340
|
+
};
|
|
1341
|
+
if (resource.protocol === "acct:" && (resource.search !== "" || resource.hash !== "")) return {
|
|
1342
|
+
resource: null,
|
|
1343
|
+
result: "invalid"
|
|
1344
|
+
};
|
|
1345
|
+
} else {
|
|
1346
|
+
protocol = resource.protocol;
|
|
1347
|
+
server = resource.host;
|
|
1348
|
+
}
|
|
1349
|
+
let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
|
|
1350
|
+
url.searchParams.set("resource", resource.href);
|
|
1351
|
+
const openHop = deps.openHop ?? ((hopUrl) => options.allowPrivateAddress === true ? (0, _fedify_vocab_runtime_internal.openDirectFetch)(hopUrl) : (0, _fedify_vocab_runtime_internal.openPublicFetch)(hopUrl, { signal: options.signal }));
|
|
1352
|
+
let redirected = 0;
|
|
1353
|
+
while (true) {
|
|
1354
|
+
logger.debug("Fetching WebFinger resource descriptor from {url}...", { url: url.href });
|
|
1355
|
+
const remoteHost = url.host;
|
|
1356
|
+
const session = await openSession(url, openHop, options.signal);
|
|
1357
|
+
if (session == null) return {
|
|
1358
|
+
resource: null,
|
|
1359
|
+
result: "network_error",
|
|
1360
|
+
remoteHost
|
|
1361
|
+
};
|
|
1362
|
+
let hop;
|
|
1363
|
+
try {
|
|
1364
|
+
hop = await fetchHop(session, url, options);
|
|
1365
|
+
} finally {
|
|
1366
|
+
await session.close();
|
|
1367
|
+
}
|
|
1368
|
+
if ("outcome" in hop) return hop.outcome;
|
|
1369
|
+
redirected++;
|
|
1370
|
+
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
1371
|
+
if (redirected > maxRedirection) {
|
|
1372
|
+
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
1373
|
+
return {
|
|
1374
|
+
resource: null,
|
|
1375
|
+
result: "invalid",
|
|
1376
|
+
statusCode: hop.statusCode,
|
|
1377
|
+
remoteHost
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
let redirectedUrl;
|
|
1381
|
+
try {
|
|
1382
|
+
redirectedUrl = new URL(hop.redirect, url);
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
logger.error("Invalid Location header while following WebFinger redirect: {error}", {
|
|
1385
|
+
url: url.href,
|
|
1386
|
+
error
|
|
1387
|
+
});
|
|
1388
|
+
return {
|
|
1389
|
+
resource: null,
|
|
1390
|
+
result: "invalid",
|
|
1391
|
+
statusCode: hop.statusCode,
|
|
1392
|
+
remoteHost
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
if (redirectedUrl.protocol !== url.protocol) {
|
|
1396
|
+
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
1397
|
+
protocol: url.protocol,
|
|
1398
|
+
redirectedProtocol: redirectedUrl.protocol
|
|
1399
|
+
});
|
|
1400
|
+
return {
|
|
1401
|
+
resource: null,
|
|
1402
|
+
result: "invalid",
|
|
1403
|
+
statusCode: hop.statusCode,
|
|
1404
|
+
remoteHost
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
url = redirectedUrl;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Opens a fetch session for a hop. A disallowed URL, a failed DNS lookup,
|
|
1412
|
+
* or an aborted lookup yields `null` instead of an error.
|
|
1413
|
+
*/
|
|
1414
|
+
async function openSession(url, openHop, signal) {
|
|
1415
|
+
try {
|
|
1416
|
+
return await openHop(url.href);
|
|
1417
|
+
} catch (e) {
|
|
1418
|
+
if (e instanceof _fedify_vocab_runtime.UrlError) {
|
|
1419
|
+
if (e.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
1420
|
+
url: url.href,
|
|
1421
|
+
error: e
|
|
1422
|
+
});
|
|
1423
|
+
else logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
if (signal?.aborted) {
|
|
1427
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
1428
|
+
url: url.href,
|
|
1429
|
+
error: e
|
|
1430
|
+
});
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
throw e;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Fetches a hop with the given session, and consumes the response body or
|
|
1438
|
+
* starts cancelling it before returning. The caller closes the session.
|
|
1439
|
+
*/
|
|
1440
|
+
async function fetchHop(session, url, options) {
|
|
1441
|
+
let response;
|
|
1442
|
+
try {
|
|
1443
|
+
response = await session.fetch(url, {
|
|
1444
|
+
headers: {
|
|
1445
|
+
Accept: "application/jrd+json",
|
|
1446
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : (0, _fedify_vocab_runtime.getUserAgent)(options.userAgent)
|
|
1447
|
+
},
|
|
1448
|
+
redirect: "manual",
|
|
1449
|
+
signal: options.signal
|
|
1450
|
+
});
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
1453
|
+
url: url.href,
|
|
1454
|
+
error
|
|
1455
|
+
});
|
|
1456
|
+
return { outcome: {
|
|
1457
|
+
resource: null,
|
|
1458
|
+
result: "network_error",
|
|
1459
|
+
remoteHost: url.host
|
|
1460
|
+
} };
|
|
1461
|
+
}
|
|
1462
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1463
|
+
cancelBody(response, url);
|
|
1464
|
+
return {
|
|
1465
|
+
redirect: response.headers.get("Location"),
|
|
1466
|
+
statusCode: response.status
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
if (!response.ok) {
|
|
1470
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
1471
|
+
url: url.href,
|
|
1472
|
+
status: response.status,
|
|
1473
|
+
statusText: response.statusText
|
|
1474
|
+
});
|
|
1475
|
+
cancelBody(response, url);
|
|
1476
|
+
return { outcome: {
|
|
1477
|
+
resource: null,
|
|
1478
|
+
result: response.status === 404 || response.status === 410 ? "not_found" : "error",
|
|
1479
|
+
statusCode: response.status,
|
|
1480
|
+
remoteHost: url.host
|
|
1481
|
+
} };
|
|
1482
|
+
}
|
|
1483
|
+
try {
|
|
1484
|
+
return { outcome: {
|
|
1485
|
+
resource: JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url)),
|
|
1486
|
+
result: "found",
|
|
1487
|
+
statusCode: response.status,
|
|
1488
|
+
remoteHost: url.host
|
|
1489
|
+
} };
|
|
1490
|
+
} catch (e) {
|
|
1491
|
+
if (e instanceof BodyTooLargeError) return { outcome: {
|
|
1492
|
+
resource: null,
|
|
1493
|
+
result: "invalid",
|
|
1494
|
+
statusCode: response.status,
|
|
1495
|
+
remoteHost: url.host
|
|
1496
|
+
} };
|
|
1497
|
+
if (e instanceof SyntaxError) {
|
|
1498
|
+
logger.debug("Failed to parse WebFinger resource descriptor as JSON: {error}", { error: e });
|
|
1499
|
+
return { outcome: {
|
|
1500
|
+
resource: null,
|
|
1501
|
+
result: "invalid",
|
|
1502
|
+
statusCode: response.status,
|
|
1503
|
+
remoteHost: url.host
|
|
1504
|
+
} };
|
|
1505
|
+
}
|
|
1506
|
+
throw e;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Starts cancelling a response body that is not read, without waiting for it
|
|
1511
|
+
* to settle. The cancellation of a branch of a tee (e.g., a body cloned by
|
|
1512
|
+
* a fetch mock) settles only after the other branch is cancelled too, per
|
|
1513
|
+
* the Streams standard, so awaiting it could block forever. A failure is
|
|
1514
|
+
* only logged.
|
|
1515
|
+
*/
|
|
1516
|
+
function cancelBody(response, url) {
|
|
1517
|
+
response.body?.cancel().catch((error) => {
|
|
1518
|
+
logger.debug("Failed to cancel the response body from {url}: {error}", {
|
|
1519
|
+
url: url.href,
|
|
1520
|
+
error
|
|
1521
|
+
});
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
//#endregion
|
|
1525
|
+
//#region src/lookup.ts
|
|
1316
1526
|
const WEBFINGER_HISTOGRAM_BUCKETS = [
|
|
1317
1527
|
5,
|
|
1318
1528
|
10,
|
|
@@ -1366,13 +1576,11 @@ function getMetricResourceScheme(scheme) {
|
|
|
1366
1576
|
return WEBFINGER_LOOKUP_SCHEME_WHITELIST.has(scheme) ? scheme : "other";
|
|
1367
1577
|
}
|
|
1368
1578
|
/**
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
* @
|
|
1372
|
-
* @returns The resource descriptor, or `null` if not found.
|
|
1373
|
-
* @since 0.2.0
|
|
1579
|
+
* The lookup implementation with injectable hop sessions for tests.
|
|
1580
|
+
* Not exported from the package entry point.
|
|
1581
|
+
* @internal
|
|
1374
1582
|
*/
|
|
1375
|
-
async function
|
|
1583
|
+
async function lookupWebFingerWithDependencies(resource, options = {}, deps = {}) {
|
|
1376
1584
|
const tracer = (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version);
|
|
1377
1585
|
const scheme = getResourceScheme(resource);
|
|
1378
1586
|
return await tracer.startActiveSpan("webfinger.lookup", {
|
|
@@ -1389,7 +1597,7 @@ async function lookupWebFinger(resource, options = {}) {
|
|
|
1389
1597
|
result: "error"
|
|
1390
1598
|
};
|
|
1391
1599
|
try {
|
|
1392
|
-
outcome = await lookupWebFingerInternal(resource, options);
|
|
1600
|
+
outcome = await lookupWebFingerInternal(resource, options, deps);
|
|
1393
1601
|
span.setStatus({ code: outcome.resource === null ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
|
|
1394
1602
|
return outcome.resource;
|
|
1395
1603
|
} catch (error) {
|
|
@@ -1415,181 +1623,56 @@ function recordWebFingerLookup(meterProvider, durationMs, scheme, outcome) {
|
|
|
1415
1623
|
instruments.lookup.add(1, attributes);
|
|
1416
1624
|
instruments.lookupDuration.record(durationMs, attributes);
|
|
1417
1625
|
}
|
|
1418
|
-
async function lookupWebFingerInternal(resource, options = {}) {
|
|
1419
|
-
if (typeof resource === "string") resource = new URL(resource);
|
|
1420
|
-
let protocol = "https:";
|
|
1421
|
-
let server;
|
|
1422
|
-
if (resource.protocol === "acct:" || resource.protocol === "mailto:") {
|
|
1423
|
-
const atPos = resource.pathname.lastIndexOf("@");
|
|
1424
|
-
if (atPos < 0) return {
|
|
1425
|
-
resource: null,
|
|
1426
|
-
result: "invalid"
|
|
1427
|
-
};
|
|
1428
|
-
server = resource.pathname.substring(atPos + 1);
|
|
1429
|
-
if (server === "" || /[/?#]/.test(server)) return {
|
|
1430
|
-
resource: null,
|
|
1431
|
-
result: "invalid"
|
|
1432
|
-
};
|
|
1433
|
-
if (resource.protocol === "acct:" && (resource.search !== "" || resource.hash !== "")) return {
|
|
1434
|
-
resource: null,
|
|
1435
|
-
result: "invalid"
|
|
1436
|
-
};
|
|
1437
|
-
} else {
|
|
1438
|
-
protocol = resource.protocol;
|
|
1439
|
-
server = resource.host;
|
|
1440
|
-
}
|
|
1441
|
-
let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
|
|
1442
|
-
url.searchParams.set("resource", resource.href);
|
|
1443
|
-
let redirected = 0;
|
|
1444
|
-
while (true) {
|
|
1445
|
-
const remoteHost = url.host;
|
|
1446
|
-
logger.debug("Fetching WebFinger resource descriptor from {url}...", { url: url.href });
|
|
1447
|
-
let response;
|
|
1448
|
-
if (options.allowPrivateAddress !== true) try {
|
|
1449
|
-
await (0, _fedify_vocab_runtime.validatePublicUrl)(url.href);
|
|
1450
|
-
} catch (e) {
|
|
1451
|
-
if (e instanceof _fedify_vocab_runtime.UrlError) {
|
|
1452
|
-
if (e.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
1453
|
-
url: url.href,
|
|
1454
|
-
error: e
|
|
1455
|
-
});
|
|
1456
|
-
else logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
1457
|
-
return {
|
|
1458
|
-
resource: null,
|
|
1459
|
-
result: "network_error",
|
|
1460
|
-
remoteHost
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
throw e;
|
|
1464
|
-
}
|
|
1465
|
-
try {
|
|
1466
|
-
response = await fetch(url, {
|
|
1467
|
-
headers: {
|
|
1468
|
-
Accept: "application/jrd+json",
|
|
1469
|
-
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : (0, _fedify_vocab_runtime.getUserAgent)(options.userAgent)
|
|
1470
|
-
},
|
|
1471
|
-
redirect: "manual",
|
|
1472
|
-
signal: options.signal
|
|
1473
|
-
});
|
|
1474
|
-
} catch (error) {
|
|
1475
|
-
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
1476
|
-
url: url.href,
|
|
1477
|
-
error
|
|
1478
|
-
});
|
|
1479
|
-
return {
|
|
1480
|
-
resource: null,
|
|
1481
|
-
result: "network_error",
|
|
1482
|
-
remoteHost
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1486
|
-
redirected++;
|
|
1487
|
-
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
1488
|
-
if (redirected > maxRedirection) {
|
|
1489
|
-
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
1490
|
-
return {
|
|
1491
|
-
resource: null,
|
|
1492
|
-
result: "invalid",
|
|
1493
|
-
statusCode: response.status,
|
|
1494
|
-
remoteHost
|
|
1495
|
-
};
|
|
1496
|
-
}
|
|
1497
|
-
let redirectedUrl;
|
|
1498
|
-
try {
|
|
1499
|
-
redirectedUrl = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? url : response.url);
|
|
1500
|
-
} catch (e) {
|
|
1501
|
-
logger.error("Invalid Location header while following WebFinger redirect: {error}", {
|
|
1502
|
-
url: url.href,
|
|
1503
|
-
error: e
|
|
1504
|
-
});
|
|
1505
|
-
return {
|
|
1506
|
-
resource: null,
|
|
1507
|
-
result: "invalid",
|
|
1508
|
-
statusCode: response.status,
|
|
1509
|
-
remoteHost
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
if (redirectedUrl.protocol !== url.protocol) {
|
|
1513
|
-
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
1514
|
-
protocol: url.protocol,
|
|
1515
|
-
redirectedProtocol: redirectedUrl.protocol
|
|
1516
|
-
});
|
|
1517
|
-
return {
|
|
1518
|
-
resource: null,
|
|
1519
|
-
result: "invalid",
|
|
1520
|
-
statusCode: response.status,
|
|
1521
|
-
remoteHost
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
url = redirectedUrl;
|
|
1525
|
-
continue;
|
|
1526
|
-
}
|
|
1527
|
-
if (!response.ok) {
|
|
1528
|
-
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
1529
|
-
url: url.href,
|
|
1530
|
-
status: response.status,
|
|
1531
|
-
statusText: response.statusText
|
|
1532
|
-
});
|
|
1533
|
-
return {
|
|
1534
|
-
resource: null,
|
|
1535
|
-
result: response.status === 404 || response.status === 410 ? "not_found" : "error",
|
|
1536
|
-
statusCode: response.status,
|
|
1537
|
-
remoteHost
|
|
1538
|
-
};
|
|
1539
|
-
}
|
|
1540
|
-
try {
|
|
1541
|
-
return {
|
|
1542
|
-
resource: JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url)),
|
|
1543
|
-
result: "found",
|
|
1544
|
-
statusCode: response.status,
|
|
1545
|
-
remoteHost
|
|
1546
|
-
};
|
|
1547
|
-
} catch (e) {
|
|
1548
|
-
if (e instanceof BodyTooLargeError) return {
|
|
1549
|
-
resource: null,
|
|
1550
|
-
result: "invalid",
|
|
1551
|
-
statusCode: response.status,
|
|
1552
|
-
remoteHost
|
|
1553
|
-
};
|
|
1554
|
-
if (e instanceof SyntaxError) {
|
|
1555
|
-
logger.debug("Failed to parse WebFinger resource descriptor as JSON: {error}", { error: e });
|
|
1556
|
-
return {
|
|
1557
|
-
resource: null,
|
|
1558
|
-
result: "invalid",
|
|
1559
|
-
statusCode: response.status,
|
|
1560
|
-
remoteHost
|
|
1561
|
-
};
|
|
1562
|
-
}
|
|
1563
|
-
throw e;
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
1626
|
//#endregion
|
|
1568
1627
|
//#region src/lookup.test.ts
|
|
1628
|
+
function lookupMock(resource, options = {}) {
|
|
1629
|
+
if (resource.toString().includes("fedify-test.internal")) return lookupWebFingerWithDependencies(resource, options, { openHop: (url) => Promise.resolve({
|
|
1630
|
+
url: new URL(url),
|
|
1631
|
+
fetch: () => Promise.reject(/* @__PURE__ */ new TypeError("Connection refused")),
|
|
1632
|
+
close: () => Promise.resolve()
|
|
1633
|
+
}) });
|
|
1634
|
+
return lookupWebFingerWithDependencies(resource, options, { openHop: options.allowPrivateAddress === true ? _fedify_vocab_runtime_internal.openDirectFetch : openExampleDirectly });
|
|
1635
|
+
}
|
|
1636
|
+
function lookupDirect(resource, options) {
|
|
1637
|
+
return lookupWebFingerWithDependencies(resource, options, { openHop: _fedify_vocab_runtime_internal.openDirectFetch });
|
|
1638
|
+
}
|
|
1639
|
+
function lookupResolving(addresses) {
|
|
1640
|
+
return () => Promise.resolve(addresses);
|
|
1641
|
+
}
|
|
1642
|
+
function openExampleDirectly(url) {
|
|
1643
|
+
return new URL(url).hostname === "example.com" ? (0, _fedify_vocab_runtime_internal.openDirectFetch)(url) : (0, _fedify_vocab_runtime_internal.openPublicFetch)(url, { lookup: lookupResolving([{
|
|
1644
|
+
address: "93.184.215.14",
|
|
1645
|
+
family: 4
|
|
1646
|
+
}]) });
|
|
1647
|
+
}
|
|
1648
|
+
async function lookupResourceWithDependencies(resource, options, deps) {
|
|
1649
|
+
return (await lookupWebFingerInternal(resource, options, deps)).resource;
|
|
1650
|
+
}
|
|
1569
1651
|
(0, _fedify_fixture.test)({
|
|
1570
1652
|
name: "lookupWebFinger()",
|
|
1571
1653
|
sanitizeOps: false,
|
|
1572
1654
|
sanitizeResources: false,
|
|
1573
1655
|
async fn(t) {
|
|
1656
|
+
const responseTimers = [];
|
|
1574
1657
|
await t.step("invalid resource", async () => {
|
|
1575
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1576
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1577
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1578
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1579
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1580
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1581
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1658
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe"), null);
|
|
1659
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock(new URL("acct:johndoe")), null);
|
|
1660
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@"), null);
|
|
1661
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock(new URL("acct:johndoe@")), null);
|
|
1662
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com/exploit"), null);
|
|
1663
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com?x=1"), null);
|
|
1664
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com#frag"), null);
|
|
1582
1665
|
});
|
|
1583
1666
|
await t.step("connection refused", async () => {
|
|
1584
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1585
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1667
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@fedify-test.internal"), null);
|
|
1668
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("https://fedify-test.internal/foo"), null);
|
|
1586
1669
|
});
|
|
1587
1670
|
esm_default.spyGlobal();
|
|
1588
1671
|
try {
|
|
1589
1672
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 404 });
|
|
1590
1673
|
await t.step("not found", async () => {
|
|
1591
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1592
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1674
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), null);
|
|
1675
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("https://example.com/foo"), null);
|
|
1593
1676
|
});
|
|
1594
1677
|
const expected = {
|
|
1595
1678
|
subject: "acct:johndoe@example.com",
|
|
@@ -1598,7 +1681,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1598
1681
|
esm_default.removeRoutes();
|
|
1599
1682
|
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
1600
1683
|
await t.step("acct", async () => {
|
|
1601
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1684
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), expected);
|
|
1602
1685
|
});
|
|
1603
1686
|
const expected2 = {
|
|
1604
1687
|
subject: "https://example.com/foo",
|
|
@@ -1607,7 +1690,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1607
1690
|
esm_default.removeRoutes();
|
|
1608
1691
|
esm_default.get("https://example.com/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%2Ffoo", { body: expected2 });
|
|
1609
1692
|
await t.step("https", async () => {
|
|
1610
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1693
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("https://example.com/foo"), expected2);
|
|
1611
1694
|
});
|
|
1612
1695
|
const mailtoExpected = {
|
|
1613
1696
|
subject: "mailto:juliet@example.com",
|
|
@@ -1616,7 +1699,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1616
1699
|
esm_default.removeRoutes();
|
|
1617
1700
|
esm_default.get("https://example.com/.well-known/webfinger?resource=mailto%3Ajuliet%40example.com", { body: mailtoExpected });
|
|
1618
1701
|
await t.step("mailto", async () => {
|
|
1619
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1702
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("mailto:juliet@example.com"), mailtoExpected);
|
|
1620
1703
|
});
|
|
1621
1704
|
const mailtoQueryExpected = {
|
|
1622
1705
|
subject: "mailto:juliet@example.com?subject=Hi",
|
|
@@ -1625,12 +1708,12 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1625
1708
|
esm_default.removeRoutes();
|
|
1626
1709
|
esm_default.get("https://example.com/.well-known/webfinger?resource=mailto%3Ajuliet%40example.com%3Fsubject%3DHi", { body: mailtoQueryExpected });
|
|
1627
1710
|
await t.step("mailto with hfields", async () => {
|
|
1628
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1711
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("mailto:juliet@example.com?subject=Hi"), mailtoQueryExpected);
|
|
1629
1712
|
});
|
|
1630
1713
|
esm_default.removeRoutes();
|
|
1631
1714
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "not json" });
|
|
1632
1715
|
await t.step("invalid response", async () => {
|
|
1633
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1716
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), null);
|
|
1634
1717
|
});
|
|
1635
1718
|
esm_default.removeRoutes();
|
|
1636
1719
|
esm_default.get("begin:https://localhost/.well-known/webfinger?", {
|
|
@@ -1642,8 +1725,8 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1642
1725
|
}]
|
|
1643
1726
|
});
|
|
1644
1727
|
await t.step("private address", async () => {
|
|
1645
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1646
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1728
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:test@localhost"), null);
|
|
1729
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:test@localhost", { allowPrivateAddress: true }), {
|
|
1647
1730
|
subject: "acct:test@localhost",
|
|
1648
1731
|
links: [{
|
|
1649
1732
|
rel: "self",
|
|
@@ -1659,7 +1742,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1659
1742
|
});
|
|
1660
1743
|
esm_default.get("begin:https://example.com/.well-known/webfinger2", { body: expected });
|
|
1661
1744
|
await t.step("redirection", async () => {
|
|
1662
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1745
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), expected);
|
|
1663
1746
|
});
|
|
1664
1747
|
esm_default.removeRoutes();
|
|
1665
1748
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
@@ -1667,7 +1750,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1667
1750
|
headers: { Location: "/.well-known/webfinger" }
|
|
1668
1751
|
});
|
|
1669
1752
|
await t.step("infinite redirection", async () => {
|
|
1670
|
-
(0, node_assert_strict.deepStrictEqual)(await (0, es_toolkit.withTimeout)(() =>
|
|
1753
|
+
(0, node_assert_strict.deepStrictEqual)(await (0, es_toolkit.withTimeout)(() => lookupMock("acct:johndoe@example.com"), 2e3), null);
|
|
1671
1754
|
});
|
|
1672
1755
|
esm_default.removeRoutes();
|
|
1673
1756
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
@@ -1675,7 +1758,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1675
1758
|
headers: { Location: "ftp://example.com/" }
|
|
1676
1759
|
});
|
|
1677
1760
|
await t.step("redirection to different protocol", async () => {
|
|
1678
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1761
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), null);
|
|
1679
1762
|
});
|
|
1680
1763
|
esm_default.removeRoutes();
|
|
1681
1764
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
@@ -1683,7 +1766,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1683
1766
|
headers: { Location: "https://localhost/" }
|
|
1684
1767
|
});
|
|
1685
1768
|
await t.step("redirection to private address", async () => {
|
|
1686
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1769
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), null);
|
|
1687
1770
|
});
|
|
1688
1771
|
esm_default.removeRoutes();
|
|
1689
1772
|
let redirectCount = 0;
|
|
@@ -1697,13 +1780,13 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1697
1780
|
});
|
|
1698
1781
|
await t.step("custom maxRedirection", async () => {
|
|
1699
1782
|
redirectCount = 0;
|
|
1700
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1783
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { maxRedirection: 1 }), null);
|
|
1701
1784
|
redirectCount = 0;
|
|
1702
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1785
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { maxRedirection: 2 }), expected);
|
|
1703
1786
|
redirectCount = 0;
|
|
1704
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1787
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { maxRedirection: 3 }), expected);
|
|
1705
1788
|
redirectCount = 0;
|
|
1706
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1789
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com"), expected);
|
|
1707
1790
|
});
|
|
1708
1791
|
await t.step("maxRedirection: 1 follows exactly one redirect", async () => {
|
|
1709
1792
|
esm_default.removeRoutes();
|
|
@@ -1715,7 +1798,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1715
1798
|
headers: { Location: "/.well-known/webfinger?after=1" }
|
|
1716
1799
|
} : { body: expected };
|
|
1717
1800
|
});
|
|
1718
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1801
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { maxRedirection: 1 }), expected);
|
|
1719
1802
|
esm_default.removeRoutes();
|
|
1720
1803
|
count = 0;
|
|
1721
1804
|
esm_default.get("begin:https://example.com/.well-known/webfinger", () => {
|
|
@@ -1725,18 +1808,18 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1725
1808
|
headers: { Location: `/.well-known/webfinger?after=${count}` }
|
|
1726
1809
|
} : { body: expected };
|
|
1727
1810
|
});
|
|
1728
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1811
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { maxRedirection: 1 }), null);
|
|
1729
1812
|
});
|
|
1730
1813
|
esm_default.removeRoutes();
|
|
1731
1814
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", () => new Promise((resolve) => {
|
|
1732
1815
|
const timeoutId = setTimeout(() => {
|
|
1733
1816
|
resolve({ body: expected });
|
|
1734
1817
|
}, 1e3);
|
|
1735
|
-
|
|
1818
|
+
responseTimers.push(timeoutId);
|
|
1736
1819
|
}));
|
|
1737
1820
|
await t.step("request cancellation", async () => {
|
|
1738
1821
|
const controller = new AbortController();
|
|
1739
|
-
const promise =
|
|
1822
|
+
const promise = lookupMock("acct:johndoe@example.com", { signal: controller.signal });
|
|
1740
1823
|
controller.abort();
|
|
1741
1824
|
(0, node_assert_strict.deepStrictEqual)(await promise, null);
|
|
1742
1825
|
});
|
|
@@ -1752,12 +1835,12 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1752
1835
|
const timeoutId = setTimeout(() => {
|
|
1753
1836
|
resolve({ body: expected });
|
|
1754
1837
|
}, 1e3);
|
|
1755
|
-
|
|
1838
|
+
responseTimers.push(timeoutId);
|
|
1756
1839
|
});
|
|
1757
1840
|
});
|
|
1758
1841
|
await t.step("cancellation during redirection", async () => {
|
|
1759
1842
|
const controller = new AbortController();
|
|
1760
|
-
const promise =
|
|
1843
|
+
const promise = lookupMock("acct:johndoe@example.com", { signal: controller.signal });
|
|
1761
1844
|
setTimeout(() => controller.abort(), 100);
|
|
1762
1845
|
(0, node_assert_strict.deepStrictEqual)(await promise, null);
|
|
1763
1846
|
});
|
|
@@ -1766,19 +1849,20 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1766
1849
|
const timeoutId = setTimeout(() => {
|
|
1767
1850
|
resolve({ body: expected });
|
|
1768
1851
|
}, 500);
|
|
1769
|
-
|
|
1852
|
+
responseTimers.push(timeoutId);
|
|
1770
1853
|
}));
|
|
1771
1854
|
await t.step("cancellation with immediate abort", async () => {
|
|
1772
1855
|
const controller = new AbortController();
|
|
1773
1856
|
controller.abort();
|
|
1774
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1857
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { signal: controller.signal }), null);
|
|
1775
1858
|
});
|
|
1776
1859
|
esm_default.removeRoutes();
|
|
1777
1860
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: expected });
|
|
1778
1861
|
await t.step("successful request with signal", async () => {
|
|
1779
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1862
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { signal: new AbortController().signal }), expected);
|
|
1780
1863
|
});
|
|
1781
1864
|
} finally {
|
|
1865
|
+
for (const timer of responseTimers) clearTimeout(timer);
|
|
1782
1866
|
esm_default.removeRoutes();
|
|
1783
1867
|
esm_default.hardReset();
|
|
1784
1868
|
}
|
|
@@ -1798,7 +1882,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1798
1882
|
esm_default.removeRoutes();
|
|
1799
1883
|
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
1800
1884
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1801
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1885
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { meterProvider }), expected);
|
|
1802
1886
|
const counters = recorder.getMeasurements("webfinger.lookup");
|
|
1803
1887
|
(0, node_assert_strict.deepStrictEqual)(counters.length, 1);
|
|
1804
1888
|
(0, node_assert_strict.deepStrictEqual)(counters[0].type, "counter");
|
|
@@ -1821,7 +1905,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1821
1905
|
links: []
|
|
1822
1906
|
} });
|
|
1823
1907
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1824
|
-
await
|
|
1908
|
+
await lookupMock("https://example.com/foo", { meterProvider });
|
|
1825
1909
|
const counters = recorder.getMeasurements("webfinger.lookup");
|
|
1826
1910
|
(0, node_assert_strict.deepStrictEqual)(counters.length, 1);
|
|
1827
1911
|
(0, node_assert_strict.deepStrictEqual)(counters[0].attributes["webfinger.resource.scheme"], "https");
|
|
@@ -1834,7 +1918,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1834
1918
|
links: []
|
|
1835
1919
|
} });
|
|
1836
1920
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1837
|
-
await
|
|
1921
|
+
await lookupMock("https://example.com:8443/foo", { meterProvider });
|
|
1838
1922
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1839
1923
|
(0, node_assert_strict.ok)(counter != null);
|
|
1840
1924
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["activitypub.remote.host"], "example.com:8443");
|
|
@@ -1843,7 +1927,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1843
1927
|
esm_default.removeRoutes();
|
|
1844
1928
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 404 });
|
|
1845
1929
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1846
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1930
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@example.com", { meterProvider }), null);
|
|
1847
1931
|
const counters = recorder.getMeasurements("webfinger.lookup");
|
|
1848
1932
|
(0, node_assert_strict.deepStrictEqual)(counters.length, 1);
|
|
1849
1933
|
(0, node_assert_strict.deepStrictEqual)(counters[0].attributes["webfinger.lookup.result"], "not_found");
|
|
@@ -1857,7 +1941,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1857
1941
|
esm_default.removeRoutes();
|
|
1858
1942
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 410 });
|
|
1859
1943
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1860
|
-
await
|
|
1944
|
+
await lookupMock("acct:johndoe@example.com", { meterProvider });
|
|
1861
1945
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1862
1946
|
(0, node_assert_strict.ok)(counter != null);
|
|
1863
1947
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "not_found");
|
|
@@ -1867,7 +1951,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1867
1951
|
esm_default.removeRoutes();
|
|
1868
1952
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 500 });
|
|
1869
1953
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1870
|
-
await
|
|
1954
|
+
await lookupMock("acct:johndoe@example.com", { meterProvider });
|
|
1871
1955
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1872
1956
|
(0, node_assert_strict.ok)(counter != null);
|
|
1873
1957
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "error");
|
|
@@ -1877,7 +1961,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1877
1961
|
esm_default.removeRoutes();
|
|
1878
1962
|
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "not json" });
|
|
1879
1963
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1880
|
-
await
|
|
1964
|
+
await lookupMock("acct:johndoe@example.com", { meterProvider });
|
|
1881
1965
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1882
1966
|
(0, node_assert_strict.ok)(counter != null);
|
|
1883
1967
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "invalid");
|
|
@@ -1886,7 +1970,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1886
1970
|
await t.step("records result=network_error when fetch never reaches the remote", async () => {
|
|
1887
1971
|
esm_default.removeRoutes();
|
|
1888
1972
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1889
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1973
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe@fedify-test.internal", { meterProvider }), null);
|
|
1890
1974
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1891
1975
|
(0, node_assert_strict.ok)(counter != null);
|
|
1892
1976
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "network_error");
|
|
@@ -1895,7 +1979,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1895
1979
|
});
|
|
1896
1980
|
await t.step("records result=invalid for malformed acct: resources", async () => {
|
|
1897
1981
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1898
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
1982
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:johndoe", { meterProvider }), null);
|
|
1899
1983
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1900
1984
|
(0, node_assert_strict.ok)(counter != null);
|
|
1901
1985
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "invalid");
|
|
@@ -1909,7 +1993,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1909
1993
|
headers: { Location: "/.well-known/webfinger" }
|
|
1910
1994
|
});
|
|
1911
1995
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1912
|
-
(0, node_assert_strict.deepStrictEqual)(await (0, es_toolkit.withTimeout)(() =>
|
|
1996
|
+
(0, node_assert_strict.deepStrictEqual)(await (0, es_toolkit.withTimeout)(() => lookupMock("acct:johndoe@example.com", {
|
|
1913
1997
|
meterProvider,
|
|
1914
1998
|
maxRedirection: 3
|
|
1915
1999
|
}), 2e3), null);
|
|
@@ -1926,7 +2010,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1926
2010
|
headers: { Location: "ftp://example.com/" }
|
|
1927
2011
|
});
|
|
1928
2012
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1929
|
-
await
|
|
2013
|
+
await lookupMock("acct:johndoe@example.com", { meterProvider });
|
|
1930
2014
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1931
2015
|
(0, node_assert_strict.ok)(counter != null);
|
|
1932
2016
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "invalid");
|
|
@@ -1939,7 +2023,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1939
2023
|
headers: { Location: "https://localhost/" }
|
|
1940
2024
|
});
|
|
1941
2025
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1942
|
-
await
|
|
2026
|
+
await lookupMock("acct:johndoe@example.com", { meterProvider });
|
|
1943
2027
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1944
2028
|
(0, node_assert_strict.ok)(counter != null);
|
|
1945
2029
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "network_error");
|
|
@@ -1952,7 +2036,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1952
2036
|
headers: { Location: "http://[bad" }
|
|
1953
2037
|
});
|
|
1954
2038
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1955
|
-
await
|
|
2039
|
+
await lookupMock("acct:johndoe@example.com", { meterProvider });
|
|
1956
2040
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1957
2041
|
(0, node_assert_strict.ok)(counter != null);
|
|
1958
2042
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "invalid");
|
|
@@ -1962,7 +2046,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1962
2046
|
await t.step("buckets unknown resource schemes as 'other' to keep metric cardinality bounded", async () => {
|
|
1963
2047
|
esm_default.removeRoutes();
|
|
1964
2048
|
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1965
|
-
await
|
|
2049
|
+
await lookupMock("ssh://example.com/foo", { meterProvider });
|
|
1966
2050
|
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
1967
2051
|
(0, node_assert_strict.ok)(counter != null);
|
|
1968
2052
|
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.resource.scheme"], "other");
|
|
@@ -1971,7 +2055,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1971
2055
|
esm_default.removeRoutes();
|
|
1972
2056
|
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
1973
2057
|
const [_unused, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
1974
|
-
await
|
|
2058
|
+
await lookupMock("acct:johndoe@example.com");
|
|
1975
2059
|
(0, node_assert_strict.deepStrictEqual)(recorder.getMeasurements("webfinger.lookup").length, 0);
|
|
1976
2060
|
(0, node_assert_strict.deepStrictEqual)(recorder.getMeasurements("webfinger.lookup.duration").length, 0);
|
|
1977
2061
|
});
|
|
@@ -1988,25 +2072,23 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1988
2072
|
"Content-Type": "application/jrd+json",
|
|
1989
2073
|
"Content-Length": "1"
|
|
1990
2074
|
} }));
|
|
1991
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
2075
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:alice@example.com", { allowPrivateAddress: true }), null);
|
|
1992
2076
|
oversized = false;
|
|
1993
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
2077
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupMock("acct:alice@example.com", { allowPrivateAddress: true }), { subject: "acct:alice@example.com" });
|
|
1994
2078
|
} finally {
|
|
1995
2079
|
esm_default.hardReset();
|
|
1996
2080
|
}
|
|
1997
2081
|
});
|
|
1998
2082
|
(0, _fedify_fixture.test)("lookupWebFinger() logs DNS failures as such", async (t) => {
|
|
1999
|
-
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") return;
|
|
2000
2083
|
for (const result of [
|
|
2001
2084
|
"throws",
|
|
2002
2085
|
"empty",
|
|
2003
2086
|
"private"
|
|
2004
2087
|
]) await t.step(result, async () => {
|
|
2005
|
-
const
|
|
2006
|
-
node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
2088
|
+
const lookup = () => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
2007
2089
|
address: "127.0.0.1",
|
|
2008
2090
|
family: 4
|
|
2009
|
-
}])
|
|
2091
|
+
}]);
|
|
2010
2092
|
const records = [];
|
|
2011
2093
|
await (0, _logtape_logtape.configure)({
|
|
2012
2094
|
sinks: { buffer: (record) => records.push(record) },
|
|
@@ -2021,7 +2103,7 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
2021
2103
|
reset: true
|
|
2022
2104
|
});
|
|
2023
2105
|
try {
|
|
2024
|
-
(0, node_assert_strict.deepStrictEqual)(await
|
|
2106
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupResourceWithDependencies("acct:alice@dns-failure.invalid", {}, { openHop: (url) => (0, _fedify_vocab_runtime_internal.openPublicFetch)(url, { lookup }) }), null);
|
|
2025
2107
|
const failures = records.filter((r) => r.rawMessage !== "Fetching WebFinger resource descriptor from {url}...");
|
|
2026
2108
|
(0, node_assert_strict.deepStrictEqual)(failures.map((r) => [r.level, r.rawMessage]), [result === "private" ? ["error", "Invalid URL for WebFinger resource descriptor: {error}"] : ["debug", "DNS lookup failed for {url}"]]);
|
|
2027
2109
|
const { error } = failures[0].properties;
|
|
@@ -2029,8 +2111,266 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
2029
2111
|
(0, node_assert_strict.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
|
|
2030
2112
|
} finally {
|
|
2031
2113
|
await (0, _logtape_logtape.reset)();
|
|
2032
|
-
node_dns_promises.default.lookup = originalLookup;
|
|
2033
2114
|
}
|
|
2034
2115
|
});
|
|
2035
2116
|
});
|
|
2117
|
+
function recordHops(respond) {
|
|
2118
|
+
const events = [];
|
|
2119
|
+
const unsettled = [];
|
|
2120
|
+
let opens = 0;
|
|
2121
|
+
let closes = 0;
|
|
2122
|
+
let responses = 0;
|
|
2123
|
+
return {
|
|
2124
|
+
events,
|
|
2125
|
+
async openHop(url) {
|
|
2126
|
+
opens++;
|
|
2127
|
+
events.push(`open ${url}`);
|
|
2128
|
+
const states = [];
|
|
2129
|
+
let closed = false;
|
|
2130
|
+
return {
|
|
2131
|
+
url: new URL(url),
|
|
2132
|
+
async fetch(input) {
|
|
2133
|
+
if (closed) throw new TypeError("The fetch session is closed.");
|
|
2134
|
+
const target = new URL(input instanceof Request ? input.url : input);
|
|
2135
|
+
const id = ++responses;
|
|
2136
|
+
events.push(`fetch ${target.href} #${id}`);
|
|
2137
|
+
const fake = respond(target);
|
|
2138
|
+
const state = {
|
|
2139
|
+
id,
|
|
2140
|
+
settled: false
|
|
2141
|
+
};
|
|
2142
|
+
states.push(state);
|
|
2143
|
+
let sent = false;
|
|
2144
|
+
const body = new ReadableStream({
|
|
2145
|
+
pull(controller) {
|
|
2146
|
+
if (!sent && fake.body != null && fake.body !== "") {
|
|
2147
|
+
sent = true;
|
|
2148
|
+
controller.enqueue(new TextEncoder().encode(fake.body));
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
state.settled = true;
|
|
2152
|
+
events.push(`consumed #${id}`);
|
|
2153
|
+
controller.close();
|
|
2154
|
+
},
|
|
2155
|
+
cancel() {
|
|
2156
|
+
state.settled = true;
|
|
2157
|
+
events.push(`cancel #${id}`);
|
|
2158
|
+
if (fake.cancelFails) throw new Error("Cancellation failed");
|
|
2159
|
+
if (fake.cancelNeverSettles) return new Promise(() => {});
|
|
2160
|
+
}
|
|
2161
|
+
}, { highWaterMark: 0 });
|
|
2162
|
+
const response = new Response(body, {
|
|
2163
|
+
status: fake.status ?? 200,
|
|
2164
|
+
headers: fake.headers
|
|
2165
|
+
});
|
|
2166
|
+
if (fake.url != null) Object.defineProperty(response, "url", { value: fake.url });
|
|
2167
|
+
return response;
|
|
2168
|
+
},
|
|
2169
|
+
async close() {
|
|
2170
|
+
if (closed) return;
|
|
2171
|
+
closed = true;
|
|
2172
|
+
closes++;
|
|
2173
|
+
for (const state of states) if (!state.settled) unsettled.push(`#${state.id}`);
|
|
2174
|
+
events.push(`close ${url}`);
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
},
|
|
2178
|
+
assertInvariants() {
|
|
2179
|
+
(0, node_assert_strict.deepStrictEqual)(closes, opens);
|
|
2180
|
+
(0, node_assert_strict.deepStrictEqual)(unsettled, []);
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
(0, _fedify_fixture.test)("lookupWebFinger() closes each hop after cleaning up its body", async (t) => {
|
|
2185
|
+
const descriptor = {
|
|
2186
|
+
subject: "acct:johndoe@example.com",
|
|
2187
|
+
links: []
|
|
2188
|
+
};
|
|
2189
|
+
const first = "https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com";
|
|
2190
|
+
const second = "https://example.com/.well-known/webfinger2";
|
|
2191
|
+
await t.step("redirect", async () => {
|
|
2192
|
+
const recorder = recordHops((url) => url.pathname === "/.well-known/webfinger" ? {
|
|
2193
|
+
status: 302,
|
|
2194
|
+
headers: { Location: "/.well-known/webfinger2" },
|
|
2195
|
+
body: "redirecting",
|
|
2196
|
+
url: url.href.replace("example.com", "93.184.215.14")
|
|
2197
|
+
} : { body: JSON.stringify(descriptor) });
|
|
2198
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupResourceWithDependencies("acct:johndoe@example.com", {}, { openHop: recorder.openHop }), descriptor);
|
|
2199
|
+
(0, node_assert_strict.deepStrictEqual)(recorder.events, [
|
|
2200
|
+
`open ${first}`,
|
|
2201
|
+
`fetch ${first} #1`,
|
|
2202
|
+
"cancel #1",
|
|
2203
|
+
`close ${first}`,
|
|
2204
|
+
`open ${second}`,
|
|
2205
|
+
`fetch ${second} #2`,
|
|
2206
|
+
"consumed #2",
|
|
2207
|
+
`close ${second}`
|
|
2208
|
+
]);
|
|
2209
|
+
recorder.assertInvariants();
|
|
2210
|
+
});
|
|
2211
|
+
await t.step("non-2xx response", async () => {
|
|
2212
|
+
const recorder = recordHops(() => ({
|
|
2213
|
+
status: 404,
|
|
2214
|
+
body: "not found"
|
|
2215
|
+
}));
|
|
2216
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupResourceWithDependencies("acct:johndoe@example.com", {}, { openHop: recorder.openHop }), null);
|
|
2217
|
+
(0, node_assert_strict.deepStrictEqual)(recorder.events, [
|
|
2218
|
+
`open ${first}`,
|
|
2219
|
+
`fetch ${first} #1`,
|
|
2220
|
+
"cancel #1",
|
|
2221
|
+
`close ${first}`
|
|
2222
|
+
]);
|
|
2223
|
+
recorder.assertInvariants();
|
|
2224
|
+
});
|
|
2225
|
+
await t.step("too many redirections", async () => {
|
|
2226
|
+
const recorder = recordHops(() => ({
|
|
2227
|
+
status: 302,
|
|
2228
|
+
headers: { Location: "/.well-known/webfinger2" },
|
|
2229
|
+
body: "redirecting"
|
|
2230
|
+
}));
|
|
2231
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupResourceWithDependencies("acct:johndoe@example.com", { maxRedirection: 1 }, { openHop: recorder.openHop }), null);
|
|
2232
|
+
(0, node_assert_strict.deepStrictEqual)(recorder.events, [
|
|
2233
|
+
`open ${first}`,
|
|
2234
|
+
`fetch ${first} #1`,
|
|
2235
|
+
"cancel #1",
|
|
2236
|
+
`close ${first}`,
|
|
2237
|
+
`open ${second}`,
|
|
2238
|
+
`fetch ${second} #2`,
|
|
2239
|
+
"cancel #2",
|
|
2240
|
+
`close ${second}`
|
|
2241
|
+
]);
|
|
2242
|
+
recorder.assertInvariants();
|
|
2243
|
+
});
|
|
2244
|
+
await t.step("failed redirect body cancellation", async () => {
|
|
2245
|
+
const recorder = recordHops((url) => url.pathname === "/.well-known/webfinger" ? {
|
|
2246
|
+
status: 302,
|
|
2247
|
+
headers: { Location: "/.well-known/webfinger2" },
|
|
2248
|
+
body: "redirecting",
|
|
2249
|
+
cancelFails: true
|
|
2250
|
+
} : { body: JSON.stringify(descriptor) });
|
|
2251
|
+
const records = [];
|
|
2252
|
+
await (0, _logtape_logtape.configure)({
|
|
2253
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
2254
|
+
loggers: [{
|
|
2255
|
+
category: "fedify",
|
|
2256
|
+
sinks: ["buffer"],
|
|
2257
|
+
lowestLevel: "debug"
|
|
2258
|
+
}, {
|
|
2259
|
+
category: ["logtape", "meta"],
|
|
2260
|
+
sinks: []
|
|
2261
|
+
}],
|
|
2262
|
+
reset: true
|
|
2263
|
+
});
|
|
2264
|
+
try {
|
|
2265
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupResourceWithDependencies("acct:johndoe@example.com", {}, { openHop: recorder.openHop }), descriptor);
|
|
2266
|
+
const failures = records.filter((r) => r.rawMessage === "Failed to cancel the response body from {url}: {error}");
|
|
2267
|
+
(0, node_assert_strict.deepStrictEqual)(failures.map((r) => [r.level, r.properties.url]), [["debug", first]]);
|
|
2268
|
+
const { error } = failures[0].properties;
|
|
2269
|
+
(0, node_assert_strict.ok)(error instanceof Error);
|
|
2270
|
+
(0, node_assert_strict.deepStrictEqual)(error.message, "Cancellation failed");
|
|
2271
|
+
} finally {
|
|
2272
|
+
await (0, _logtape_logtape.reset)();
|
|
2273
|
+
}
|
|
2274
|
+
(0, node_assert_strict.deepStrictEqual)(recorder.events, [
|
|
2275
|
+
`open ${first}`,
|
|
2276
|
+
`fetch ${first} #1`,
|
|
2277
|
+
"cancel #1",
|
|
2278
|
+
`close ${first}`,
|
|
2279
|
+
`open ${second}`,
|
|
2280
|
+
`fetch ${second} #2`,
|
|
2281
|
+
"consumed #2",
|
|
2282
|
+
`close ${second}`
|
|
2283
|
+
]);
|
|
2284
|
+
recorder.assertInvariants();
|
|
2285
|
+
});
|
|
2286
|
+
await t.step("never-settling body cancellation", async () => {
|
|
2287
|
+
const recorder = recordHops((url) => url.pathname === "/.well-known/webfinger" ? {
|
|
2288
|
+
status: 302,
|
|
2289
|
+
headers: { Location: "/.well-known/webfinger2" },
|
|
2290
|
+
body: "redirecting",
|
|
2291
|
+
cancelNeverSettles: true
|
|
2292
|
+
} : {
|
|
2293
|
+
status: 404,
|
|
2294
|
+
body: "not found",
|
|
2295
|
+
cancelNeverSettles: true
|
|
2296
|
+
});
|
|
2297
|
+
(0, node_assert_strict.deepStrictEqual)(await settleWithin(lookupResourceWithDependencies("acct:johndoe@example.com", {}, { openHop: recorder.openHop })), null);
|
|
2298
|
+
(0, node_assert_strict.deepStrictEqual)(recorder.events, [
|
|
2299
|
+
`open ${first}`,
|
|
2300
|
+
`fetch ${first} #1`,
|
|
2301
|
+
"cancel #1",
|
|
2302
|
+
`close ${first}`,
|
|
2303
|
+
`open ${second}`,
|
|
2304
|
+
`fetch ${second} #2`,
|
|
2305
|
+
"cancel #2",
|
|
2306
|
+
`close ${second}`
|
|
2307
|
+
]);
|
|
2308
|
+
recorder.assertInvariants();
|
|
2309
|
+
});
|
|
2310
|
+
});
|
|
2311
|
+
/**
|
|
2312
|
+
* Rejects if the given promise does not settle within the timeout, so that
|
|
2313
|
+
* a hang fails the test instead of blocking it.
|
|
2314
|
+
*/
|
|
2315
|
+
async function settleWithin(promise, ms = 5e3) {
|
|
2316
|
+
let timer;
|
|
2317
|
+
try {
|
|
2318
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
2319
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Did not settle within ${ms} ms.`)), ms);
|
|
2320
|
+
})]);
|
|
2321
|
+
} finally {
|
|
2322
|
+
clearTimeout(timer);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function neverCancellableResponse(init) {
|
|
2326
|
+
return new Response(new ReadableStream({
|
|
2327
|
+
start(controller) {
|
|
2328
|
+
controller.enqueue(new TextEncoder().encode("never cancelled"));
|
|
2329
|
+
},
|
|
2330
|
+
cancel: () => new Promise(() => {})
|
|
2331
|
+
}), init);
|
|
2332
|
+
}
|
|
2333
|
+
(0, _fedify_fixture.test)("lookupWebFinger() does not wait for cancelling response bodies", async (t) => {
|
|
2334
|
+
const descriptor = {
|
|
2335
|
+
subject: "acct:johndoe@example.com",
|
|
2336
|
+
links: []
|
|
2337
|
+
};
|
|
2338
|
+
esm_default.mockGlobal();
|
|
2339
|
+
try {
|
|
2340
|
+
await t.step("redirect", async () => {
|
|
2341
|
+
esm_default.removeRoutes();
|
|
2342
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", () => neverCancellableResponse({
|
|
2343
|
+
status: 302,
|
|
2344
|
+
headers: { Location: "/.well-known/webfinger2" }
|
|
2345
|
+
}));
|
|
2346
|
+
esm_default.get("https://example.com/.well-known/webfinger2", () => new Response(JSON.stringify(descriptor), { headers: { "Content-Type": "application/jrd+json" } }));
|
|
2347
|
+
(0, node_assert_strict.deepStrictEqual)(await settleWithin(lookupDirect("acct:johndoe@example.com")), descriptor);
|
|
2348
|
+
});
|
|
2349
|
+
await t.step("non-2xx response", async () => {
|
|
2350
|
+
esm_default.removeRoutes();
|
|
2351
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", () => neverCancellableResponse({ status: 404 }));
|
|
2352
|
+
(0, node_assert_strict.deepStrictEqual)(await settleWithin(lookupDirect("acct:johndoe@example.com")), null);
|
|
2353
|
+
});
|
|
2354
|
+
} finally {
|
|
2355
|
+
esm_default.hardReset();
|
|
2356
|
+
}
|
|
2357
|
+
});
|
|
2358
|
+
(0, node_test.test)("lookupWebFinger() records network_error for rejected hop addresses", async () => {
|
|
2359
|
+
for (const lookup of [
|
|
2360
|
+
() => Promise.reject(/* @__PURE__ */ new Error("DNS unavailable")),
|
|
2361
|
+
() => Promise.resolve([]),
|
|
2362
|
+
() => Promise.resolve([{
|
|
2363
|
+
address: "127.0.0.1",
|
|
2364
|
+
family: 4
|
|
2365
|
+
}])
|
|
2366
|
+
]) {
|
|
2367
|
+
const [meterProvider, recorder] = (0, _fedify_fixture.createTestMeterProvider)();
|
|
2368
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupWebFingerWithDependencies("acct:alice@dns-failure.invalid", { meterProvider }, { openHop: (url) => (0, _fedify_vocab_runtime_internal.openPublicFetch)(url, { lookup }) }), null);
|
|
2369
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
2370
|
+
(0, node_assert_strict.ok)(counter != null);
|
|
2371
|
+
(0, node_assert_strict.deepStrictEqual)(counter.attributes["webfinger.lookup.result"], "network_error");
|
|
2372
|
+
(0, node_assert_strict.deepStrictEqual)(counter.attributes["activitypub.remote.host"], "dns-failure.invalid");
|
|
2373
|
+
(0, node_assert_strict.deepStrictEqual)("http.response.status_code" in counter.attributes, false);
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2036
2376
|
//#endregion
|