@dwk/webfinger 0.1.0-beta.0
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/LICENSE +15 -0
- package/README.md +106 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -0
- package/dist/handler.d.ts +29 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +130 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/jrd.d.ts +73 -0
- package/dist/jrd.d.ts.map +1 -0
- package/dist/jrd.js +38 -0
- package/dist/jrd.js.map +1 -0
- package/dist/log.d.ts +36 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +34 -0
- package/dist/log.js.map +1 -0
- package/dist/resource.d.ts +35 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +79 -0
- package/dist/resource.js.map +1 -0
- package/package.json +46 -0
- package/src/config.ts +120 -0
- package/src/handler.ts +183 -0
- package/src/index.ts +44 -0
- package/src/jrd.ts +104 -0
- package/src/log.ts +38 -0
- package/src/resource.ts +82 -0
package/src/log.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dwk/webfinger` — structured observability event taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* WebFinger is public discovery data, so the stakes are lower than an auth
|
|
5
|
+
* endpoint — but a server being scraped for `acct:` handles it does not control,
|
|
6
|
+
* or a misconfigured resource map that 404s every lookup, is exactly the kind of
|
|
7
|
+
* thing an operator wants a signal for. Logging and metrics are opt-in via an
|
|
8
|
+
* injected {@link Logger} and {@link Metrics} (see `@dwk/log`) and **share this
|
|
9
|
+
* one vocabulary**: the same dotted event name is passed to the logger and the
|
|
10
|
+
* metrics sink so a log line and its counter line up.
|
|
11
|
+
*
|
|
12
|
+
* Fields follow the redaction policy: a `resource` is reduced to its **host**
|
|
13
|
+
* (the domain of an `acct:`/`mailto:` handle or an `https:` URI) via the
|
|
14
|
+
* handler's `resourceHost` helper — the local part (the user identifier) is
|
|
15
|
+
* never logged, only the domain, a machine-readable `reason`, and the count of
|
|
16
|
+
* `rel` filters. See `spec/observability.md`.
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Stable event names emitted by `@dwk/webfinger`. */
|
|
22
|
+
export const WebfingerLogEvent = {
|
|
23
|
+
/**
|
|
24
|
+
* A `resource` was matched and a JRD returned. Fields: `resourceHost`
|
|
25
|
+
* (sanitized), `relCount` (number of `rel` filters applied).
|
|
26
|
+
*/
|
|
27
|
+
Resolved: "webfinger.resolved",
|
|
28
|
+
/**
|
|
29
|
+
* A request was rejected before a JRD could be returned. Field: `reason`
|
|
30
|
+
* (`missing_resource`, `malformed_resource`, `not_found`, or
|
|
31
|
+
* `method_not_allowed`), plus `resourceHost` when a `resource` was supplied.
|
|
32
|
+
*/
|
|
33
|
+
Rejected: "webfinger.rejected",
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
/** Union of the event-name string literals in {@link WebfingerLogEvent}. */
|
|
37
|
+
export type WebfingerLogEvent =
|
|
38
|
+
(typeof WebfingerLogEvent)[keyof typeof WebfingerLogEvent];
|
package/src/resource.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource-URI normalization for case-insensitive matching (RFC 7033 §4.1;
|
|
3
|
+
* RFC 3986 §3.1 and §6.2.2.1): a URI's **scheme** and **host** are
|
|
4
|
+
* case-insensitive, so a query for `acct:alice@EXAMPLE.COM` must match a
|
|
5
|
+
* configured `acct:alice@example.com`. The local part of an `acct:`/`mailto:`
|
|
6
|
+
* handle is the user identifier and stays **case-sensitive**.
|
|
7
|
+
*
|
|
8
|
+
* Normalization scopes the *lookup key* only: both the queried resource and the
|
|
9
|
+
* configured map keys are normalized before comparison (see `config.ts`). The
|
|
10
|
+
* echoed `subject` keeps the client's literal spelling — the package spec
|
|
11
|
+
* requires the subject to equal the queried resource URI, and fediverse software
|
|
12
|
+
* compares it case-insensitively.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalize a resource URI for comparison: lowercase the scheme and host,
|
|
17
|
+
* preserving the case of an `acct:`/`mailto:` local part. For `http(s)` URIs the
|
|
18
|
+
* WHATWG `URL` parser performs the scheme/host lowercasing (and its standard
|
|
19
|
+
* path normalization); a URI that does not parse is returned with only its
|
|
20
|
+
* scheme lowercased, and a string with no scheme is returned unchanged.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeResource(resource: string): string {
|
|
23
|
+
const colon = resource.indexOf(":");
|
|
24
|
+
if (colon <= 0) return resource;
|
|
25
|
+
|
|
26
|
+
const scheme = resource.slice(0, colon).toLowerCase();
|
|
27
|
+
const rest = resource.slice(colon + 1);
|
|
28
|
+
|
|
29
|
+
if (scheme === "acct" || scheme === "mailto") {
|
|
30
|
+
const at = rest.lastIndexOf("@");
|
|
31
|
+
if (at === -1) return `${scheme}:${rest}`;
|
|
32
|
+
const local = rest.slice(0, at);
|
|
33
|
+
const host = rest.slice(at + 1).toLowerCase();
|
|
34
|
+
return `${scheme}:${local}@${host}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (scheme === "http" || scheme === "https") {
|
|
38
|
+
try {
|
|
39
|
+
return new URL(resource).href;
|
|
40
|
+
} catch {
|
|
41
|
+
return `${scheme}:${rest}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return `${scheme}:${rest}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A valid URI scheme per RFC 3986 §3.1: an ALPHA followed by any number of
|
|
50
|
+
* ALPHA / DIGIT / "+" / "-" / ".".
|
|
51
|
+
*/
|
|
52
|
+
const SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/i;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Decide whether a queried `resource` is a syntactically well-formed URI
|
|
56
|
+
* (RFC 7033 §4.2): it MUST carry a scheme (RFC 3986 §3.1), and an `http(s)`
|
|
57
|
+
* resource MUST additionally parse as an absolute URL. A value with no scheme,
|
|
58
|
+
* an ill-formed scheme, or an unparseable `http(s)` authority is *malformed*,
|
|
59
|
+
* and the handler answers `400` (not `404`) before any lookup — §4.2 requires a
|
|
60
|
+
* "bad request" indication when `resource` is absent **or malformed**.
|
|
61
|
+
*
|
|
62
|
+
* Validation is deliberately minimal: a syntactically valid scheme is enough for
|
|
63
|
+
* non-`http(s)` URIs (`acct:`, `mailto:`, `urn:`), since the resolver — not this
|
|
64
|
+
* gate — owns whether such a resource is controlled.
|
|
65
|
+
*/
|
|
66
|
+
export function isWellFormedResource(resource: string): boolean {
|
|
67
|
+
const colon = resource.indexOf(":");
|
|
68
|
+
if (colon <= 0) return false;
|
|
69
|
+
|
|
70
|
+
const scheme = resource.slice(0, colon).toLowerCase();
|
|
71
|
+
if (!SCHEME_PATTERN.test(scheme)) return false;
|
|
72
|
+
|
|
73
|
+
if (scheme === "http" || scheme === "https") {
|
|
74
|
+
try {
|
|
75
|
+
new URL(resource);
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return true;
|
|
82
|
+
}
|