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