UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

519 lines (515 loc) • 15.7 kB
import { Temporal } from "@js-temporal/polyfill"; import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; import { assertEquals } from "../assert_equals-CTYbeopb.js"; import "../assert-DmFG7ppO.js"; import "../assert_instance_of-CF09JHYM.js"; import { MemoryKvStore } from "../kv-BMY6Qf_A.js"; import { createFederation, handleWebFinger } from "../middleware-DlU2_Djj.js"; import "../docloader-C8mmLN-Y.js"; import "../url-kTAI6_KP.js"; import "../semver-DWClQt_5.js"; import "../client-rv6n0nKR.js"; import "../router-D_aVZZUc.js"; import "../types-C7C_l-jz.js"; import "../multibase-DeCHcK8L.js"; import { Image, Link, Person } from "../vocab-dDpPQ0fF.js"; import "../langstr-DbWheeIS.js"; import "../lookup-Ozuw-rxN.js"; import "../type-D2s5lmbZ.js"; import "../actor-BP-NTJ2m.js"; import "../key-CPSlDkCj.js"; import "../http-BmfI6U39.js"; import "../authdocloader-Banw__l6.js"; import "../ld-D9kk1Uy2.js"; import "../owner-DF6Fg4-s.js"; import "../proof-13QPysOU.js"; import "../lookup-DNXR408t.js"; import "../inbox-AwdJVoMZ.js"; import "../builder-EdzpUb9N.js"; import "../collection-Dfb0TPno.js"; import "../keycache-BPwY_Up0.js"; import "../retry-BiIhZWgD.js"; import "../send-CaL5Xv1F.js"; import { test } from "../testing-BZ0dJ4qn.js"; import "../std__assert-vp0TKMS1.js"; import "../assert_rejects-C-sxEMM5.js"; import "../assert_is_error-nrwA1GeT.js"; import "../assert_not_equals-Dc7y-V5Q.js"; import "../assert_throws-Cn9C6Jur.js"; import "../docloader-09nVWLAZ.js"; import { createRequestContext } from "../context-Buzj1YKD.js"; //#region webfinger/handler.test.ts test("handleWebFinger()", async (t) => { const url = new URL("https://example.com/.well-known/webfinger"); function createContext(url$1) { const federation = createFederation({ kv: new MemoryKvStore() }); const context = createRequestContext({ federation, url: url$1, data: void 0, getActorUri(identifier) { return new URL(`${url$1.origin}/users/${identifier}`); }, async getActor(handle) { return await actorDispatcher(context, handle); }, parseUri(uri) { if (uri == null) return null; if (uri.protocol === "acct:") return null; if (!uri.pathname.startsWith("/users/")) return null; const paths = uri.pathname.split("/"); const identifier = paths[paths.length - 1]; return { type: "actor", identifier, get handle() { throw new Error("ParseUriResult.handle is deprecated!"); } }; } }); return context; } const actorDispatcher = (ctx, identifier) => { if (identifier !== "someone" && identifier !== "someone2") return null; const actorUri = ctx.getActorUri(identifier); return new Person({ id: actorUri, name: identifier === "someone" ? "Someone" : "Someone 2", preferredUsername: identifier === "someone" ? null : identifier === "someone2" ? "bar" : null, icon: new Image({ url: new URL(`${actorUri.origin}/icon.jpg`), mediaType: "image/jpeg" }), urls: [new URL(`${actorUri.origin}/@${identifier}`), new Link({ href: new URL(`${actorUri.origin}/@${identifier}`), rel: "alternate", mediaType: "text/html" })] }); }; let onNotFoundCalled = null; const onNotFound = (request) => { onNotFoundCalled = request; return new Response("Not found", { status: 404 }); }; await t.step("no actor dispatcher", async () => { const context = createContext(url); const request = context.request; const response = await handleWebFinger(request, { context, onNotFound }); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, request); }); onNotFoundCalled = null; await t.step("no resource", async () => { const context = createContext(url); const request = context.request; const response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 400); assertEquals(await response.text(), "Missing resource parameter."); assertEquals(onNotFoundCalled, null); }); await t.step("invalid resource", async () => { const u = new URL(url); u.searchParams.set("resource", " invalid "); const context = createContext(u); const request = new Request(u); const response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 400); assertEquals(await response.text(), "Invalid resource URL."); assertEquals(onNotFoundCalled, null); }); const expected = { subject: "acct:someone@example.com", aliases: ["https://example.com/users/someone"], links: [ { href: "https://example.com/users/someone", rel: "self", type: "application/activity+json" }, { href: "https://example.com/@someone", rel: "http://webfinger.net/rel/profile-page" }, { href: "https://example.com/@someone", rel: "alternate", type: "text/html" }, { href: "https://example.com/icon.jpg", rel: "http://webfinger.net/rel/avatar", type: "image/jpeg" } ] }; await t.step("ok: resource=acct:...", async () => { const u = new URL(url); u.searchParams.set("resource", "acct:someone@example.com"); const context = createContext(u); const request = context.request; const response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(response.headers.get("Content-Type"), "application/jrd+json"); assertEquals(response.headers.get("Access-Control-Allow-Origin"), "*"); assertEquals(await response.json(), expected); }); const expected2 = { subject: "https://example.com/users/someone2", aliases: ["acct:bar@example.com"], links: [ { href: "https://example.com/users/someone2", rel: "self", type: "application/activity+json" }, { href: "https://example.com/@someone2", rel: "http://webfinger.net/rel/profile-page" }, { href: "https://example.com/@someone2", rel: "alternate", type: "text/html" }, { href: "https://example.com/icon.jpg", rel: "http://webfinger.net/rel/avatar", type: "image/jpeg" } ] }; await t.step("ok: resource=https:...", async () => { const u = new URL(url); u.searchParams.set("resource", "https://example.com/users/someone"); let context = createContext(u); let request = context.request; let response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected, aliases: [], subject: "https://example.com/users/someone" }); u.searchParams.set("resource", "https://example.com/users/someone2"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), expected2); }); await t.step("not found: resource=acct:...", async () => { const u = new URL(url); u.searchParams.set("resource", "acct:no-one@example.com"); const context = createContext(u); const request = context.request; const response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, request); }); onNotFoundCalled = null; await t.step("not found: resource=http:...", async () => { const u = new URL(url); u.searchParams.set("resource", "https://example.com/users/no-one"); let context = createContext(u); let request = context.request; let response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, request); onNotFoundCalled = null; u.searchParams.set("resource", "https://google.com/"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, request); }); onNotFoundCalled = null; const actorHandleMapper = (_ctx, username) => { return username === "foo" ? "someone" : username === "bar" ? "someone2" : null; }; await t.step("handle mapper", async () => { const u = new URL(url); u.searchParams.set("resource", "acct:foo@example.com"); let context = createContext(u); let request = context.request; let response = await handleWebFinger(request, { context, actorDispatcher, actorHandleMapper, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected, aliases: ["https://example.com/users/someone"], subject: "acct:foo@example.com" }); u.searchParams.set("resource", "acct:bar@example.com"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, actorHandleMapper, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected2, aliases: ["https://example.com/users/someone2"], subject: "acct:bar@example.com" }); u.searchParams.set("resource", "https://example.com/users/someone"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, actorHandleMapper, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected, aliases: [], subject: "https://example.com/users/someone" }); u.searchParams.set("resource", "acct:baz@example.com"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, actorHandleMapper, onNotFound }); assertEquals(response.status, 404); }); const actorAliasMapper = (_ctx, resource) => { if (resource.protocol !== "https:") return null; if (resource.host !== "example.com") return null; const m = /^\/@(\w+)$/.exec(resource.pathname); if (m == null) return null; return { username: m[1] }; }; await t.step("alias mapper", async () => { const u = new URL(url); u.searchParams.set("resource", "https://example.com/@someone"); let context = createContext(u); let request = context.request; let response = await handleWebFinger(request, { context, actorDispatcher, actorAliasMapper, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected, aliases: ["https://example.com/users/someone"], subject: "https://example.com/@someone" }); u.searchParams.set("resource", "https://example.com/@bar"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected2, aliases: ["acct:bar@example.com", "https://example.com/users/someone2"], subject: "https://example.com/@bar" }); u.searchParams.set("resource", "https://example.com/@no-one"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, actorAliasMapper, onNotFound }); assertEquals(response.status, 404); u.searchParams.set("resource", "https://example.com/@no-one"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound }); assertEquals(response.status, 404); }); await t.step("handleHost", async () => { const u = new URL(url); u.searchParams.set("resource", "acct:someone@example.com"); let context = createContext(u); let request = context.request; let response = await handleWebFinger(request, { context, host: "handle.example.com", actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected, aliases: [...expected.aliases, "acct:someone@handle.example.com"] }); u.searchParams.set("resource", "acct:someone@handle.example.com"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, host: "handle.example.com", actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected, subject: "acct:someone@handle.example.com" }); u.searchParams.set("resource", "https://example.com/users/someone2"); context = createContext(u); request = context.request; response = await handleWebFinger(request, { context, host: "handle.example.com", actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(await response.json(), { ...expected2, aliases: ["acct:bar@handle.example.com", "acct:bar@example.com"], subject: "https://example.com/users/someone2" }); }); const expectedForLocalhostWithPort = { subject: "acct:someone@localhost:8000", aliases: ["https://localhost:8000/users/someone"], links: [ { href: "https://localhost:8000/users/someone", rel: "self", type: "application/activity+json" }, { href: "https://localhost:8000/@someone", rel: "http://webfinger.net/rel/profile-page" }, { href: "https://localhost:8000/@someone", rel: "alternate", type: "text/html" }, { href: "https://localhost:8000/icon.jpg", rel: "http://webfinger.net/rel/avatar", type: "image/jpeg" } ] }; await t.step("on localhost with port, ok: resource=acct:...", async () => { const u = new URL("https://localhost:8000/.well-known/webfinger"); u.searchParams.set("resource", "acct:someone@localhost:8000"); const context = createContext(u); const request = context.request; const response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(response.headers.get("Content-Type"), "application/jrd+json"); assertEquals(response.headers.get("Access-Control-Allow-Origin"), "*"); assertEquals(await response.json(), expectedForLocalhostWithPort); }); const expectedForHostnameWithPort = { subject: "acct:someone@example.com:8000", aliases: ["http://example.com:8000/users/someone"], links: [ { href: "http://example.com:8000/users/someone", rel: "self", type: "application/activity+json" }, { href: "http://example.com:8000/@someone", rel: "http://webfinger.net/rel/profile-page" }, { href: "http://example.com:8000/@someone", rel: "alternate", type: "text/html" }, { href: "http://example.com:8000/icon.jpg", rel: "http://webfinger.net/rel/avatar", type: "image/jpeg" } ] }; await t.step("on hostname with port, ok: resource=acct:...", async () => { const u = new URL("http://example.com:8000/.well-known/webfinger"); u.searchParams.set("resource", "acct:someone@example.com:8000"); const context = createContext(u); const request = context.request; const response = await handleWebFinger(request, { context, actorDispatcher, onNotFound }); assertEquals(response.status, 200); assertEquals(response.headers.get("Content-Type"), "application/jrd+json"); assertEquals(response.headers.get("Access-Control-Allow-Origin"), "*"); assertEquals(await response.json(), expectedForHostnameWithPort); }); }); //#endregion