Unverified Commit 2a4aee50 authored by Hong Minhee's avatar Hong Minhee
Browse files

WebFinger client

parent 074619ed
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -23,7 +23,15 @@ To be released.
     -  Added `NodeInfoDispatcher` type.
     -  Added `nodeInfoToJson()` function.

 -  Implemented [WebFinger] client.

     -  Added `lookupWebFinger()` function.

 -  `Federation.handle()` now responds with `Access-Control-Allow-Origin: *`
    header for WebFinger requests.

[NodeInfo]: https://nodeinfo.diaspora.software/
[WebFinger]: https://datatracker.ietf.org/doc/html/rfc7033
[#1]: https://github.com/dahlia/fedify/issues/1


+5 −0
Original line number Diff line number Diff line
@@ -46,9 +46,14 @@ In the above example, the `~Federation.setActorDispatcher()` method registers
an actor dispatcher for the `/users/{handle}` path.  This pattern syntax
follows the [URI Template] specification.

> [!TIP]
> By regisrtering the actor dispatcher, `Federation.handle()` automatically
> deals with [WebFinger] requests for the actor.

[actors]: https://www.w3.org/TR/activitystreams-core/#actors
[activities]: https://www.w3.org/TR/activitystreams-core/#activities
[URI Template]: https://datatracker.ietf.org/doc/html/rfc6570
[WebFinger]: https://datatracker.ietf.org/doc/html/rfc7033


Key properties of an `Actor`
+2 −0
Original line number Diff line number Diff line
@@ -83,6 +83,8 @@ Deno.test("handleWebFinger()", async () => {
    onNotFound,
  });
  assertEquals(response.status, 200);
  assertEquals(response.headers.get("Content-Type"), "application/jrd+json");
  assertEquals(response.headers.get("Access-Control-Allow-Origin"), "*");
  const expected = {
    subject: "acct:someone@example.com",
    aliases: [
+1 −0
Original line number Diff line number Diff line
@@ -100,6 +100,7 @@ export async function handleWebFinger<TContextData>(
  return new Response(JSON.stringify(jrd), {
    headers: {
      "Content-Type": "application/jrd+json",
      "Access-Control-Allow-Origin": "*",
    },
  });
}
+67 −0
Original line number Diff line number Diff line
import * as mf from "https://deno.land/x/mock_fetch@0.3.0/mod.ts";
import { assertEquals } from "jsr:@std/assert@^0.218.2";
import { ResourceDescriptor } from "./jrd.ts";
import { lookupWebFinger } from "./lookup.ts";

Deno.test("lookupWebFinger()", async (t) => {
  mf.install();

  await t.step("invalid resource", async () => {
    assertEquals(await lookupWebFinger("acct:johndoe"), null);
    assertEquals(await lookupWebFinger(new URL("acct:johndoe")), null);
    assertEquals(await lookupWebFinger("acct:johndoe@"), null);
    assertEquals(await lookupWebFinger(new URL("acct:johndoe@")), null);
  });

  mf.mock("GET@/.well-known/webfinger", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    return new Response("", { status: 404 });
  });

  await t.step("not found", async () => {
    assertEquals(await lookupWebFinger("acct:johndoe@example.com"), null);
    assertEquals(await lookupWebFinger("https://example.com/foo"), null);
  });

  const expected: ResourceDescriptor = {
    subject: "acct:johndoe@example.com",
    links: [],
  };
  mf.mock("GET@/.well-known/webfinger", (req) => {
    assertEquals(
      req.url,
      "https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com",
    );
    return new Response(JSON.stringify(expected));
  });

  await t.step("acct", async () => {
    assertEquals(await lookupWebFinger("acct:johndoe@example.com"), expected);
  });

  const expected2: ResourceDescriptor = {
    subject: "https://example.com/foo",
    links: [],
  };
  mf.mock("GET@/.well-known/webfinger", (req) => {
    assertEquals(
      req.url,
      "https://example.com/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%2Ffoo",
    );
    return new Response(JSON.stringify(expected2));
  });

  await t.step("https", async () => {
    assertEquals(await lookupWebFinger("https://example.com/foo"), expected2);
  });

  mf.mock("GET@/.well-known/webfinger", (_req) => {
    return new Response("not json");
  });

  await t.step("invalid response", async () => {
    assertEquals(await lookupWebFinger("acct:johndoe@example.com"), null);
  });

  mf.uninstall();
});
Loading