@fedify/fedify
Version:
An ActivityPub server framework
519 lines (515 loc) • 15.7 kB
JavaScript
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