Unverified Commit b3233372 authored by Hong Minhee's avatar Hong Minhee
Browse files

RequestContext.getSignedKeyOwner() method

parent 737e0809
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ To be released.
     -  Added `CollectionCallbackSetters.authorize()` method.
     -  Added `AuthorizedPredicate` type.
     -  Added `RequestContext.getSignedKey()` method.
     -  Added `RequestContext.getSignedKeyOwner()` method.
     -  Added `FederationFetchOptions.onUnauthorized` option for handling
        unauthorized fetches.
     -  Added `getKeyOwner()` function.
+15 −0
Original line number Diff line number Diff line
@@ -155,6 +155,21 @@ export interface RequestContext<TContextData> extends Context<TContextData> {
   * @since 0.7.0
   */
  getSignedKey(): Promise<CryptographicKey | null>;

  /**
   * Gets the owner of the signed key, if any exists and it is verified.
   * Otherwise, `null` is returned.
   *
   * This can be used for implementing [authorized fetch] (also known as
   * secure mode) in ActivityPub.
   *
   * [authorized fetch]: https://swicg.github.io/activitypub-http-signature/#authorized-fetch
   *
   * @returns The owner of the signed key, or `null` if the key is not verified
   *          or the owner is not found.
   * @since 0.7.0
   */
  getSignedKeyOwner(): Promise<Actor | null>;
}

/**
+30 −1
Original line number Diff line number Diff line
@@ -13,12 +13,18 @@ import {
  getAuthenticatedDocumentLoader,
} from "../runtime/docloader.ts";
import { mockDocumentLoader } from "../testing/docloader.ts";
import { privateKey2, publicKey2 } from "../testing/keys.ts";
import {
  privateKey2,
  privateKey3,
  publicKey2,
  publicKey3,
} from "../testing/keys.ts";
import { Create, Person } from "../vocab/vocab.ts";
import type { Context } from "./context.ts";
import { MemoryKvStore } from "./kv.ts";
import { Federation } from "./middleware.ts";
import { RouterError } from "./router.ts";
import { lookupObject } from "@fedify/fedify/vocab";

Deno.test("Federation.createContext()", async (t) => {
  const kv = new MemoryKvStore();
@@ -179,8 +185,10 @@ Deno.test("Federation.createContext()", async (t) => {
    assertEquals(ctx.url, new URL("https://example.com/"));
    assertEquals(ctx.data, 123);
    assertEquals(await ctx.getSignedKey(), null);
    assertEquals(await ctx.getSignedKeyOwner(), null);
    // Multiple calls should return the same result:
    assertEquals(await ctx.getSignedKey(), null);
    assertEquals(await ctx.getSignedKeyOwner(), null);

    const signedReq = await sign(
      new Request("https://example.com/"),
@@ -192,8 +200,29 @@ Deno.test("Federation.createContext()", async (t) => {
    assertEquals(signedCtx.url, new URL("https://example.com/"));
    assertEquals(signedCtx.data, 456);
    assertEquals(await signedCtx.getSignedKey(), publicKey2);
    assertEquals(await signedCtx.getSignedKeyOwner(), null);
    // Multiple calls should return the same result:
    assertEquals(await signedCtx.getSignedKey(), publicKey2);
    assertEquals(await signedCtx.getSignedKeyOwner(), null);

    const signedReq2 = await sign(
      new Request("https://example.com/"),
      privateKey3,
      publicKey3.id!,
    );
    const signedCtx2 = federation.createContext(signedReq2, 456);
    assertEquals(signedCtx2.request, signedReq2);
    assertEquals(signedCtx2.url, new URL("https://example.com/"));
    assertEquals(signedCtx2.data, 456);
    assertEquals(await signedCtx2.getSignedKey(), publicKey3);
    const expectedOwner = await lookupObject(
      "https://example.com/person2",
      { documentLoader: mockDocumentLoader },
    );
    assertEquals(await signedCtx2.getSignedKeyOwner(), expectedOwner);
    // Multiple calls should return the same result:
    assertEquals(await signedCtx2.getSignedKey(), publicKey3);
    assertEquals(await signedCtx2.getSignedKeyOwner(), expectedOwner);
  });

  mf.uninstall();
+8 −1
Original line number Diff line number Diff line
import { Temporal } from "@js-temporal/polyfill";
import { exportJwk, importJwk, validateCryptoKey } from "../httpsig/key.ts";
import { verify } from "../httpsig/mod.ts";
import { getKeyOwner, verify } from "../httpsig/mod.ts";
import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.ts";
import {
  type AuthenticatedDocumentLoaderFactory,
@@ -392,6 +392,7 @@ export class Federation<TContextData> {
    };
    if (request == null) return context;
    let signedKey: CryptographicKey | null | undefined = undefined;
    let signedKeyOwner: Actor | null | undefined = undefined;
    const reqCtx: RequestContext<TContextData> = {
      ...context,
      request,
@@ -400,6 +401,12 @@ export class Federation<TContextData> {
        if (signedKey !== undefined) return signedKey;
        return signedKey = await verify(request, context.documentLoader);
      },
      async getSignedKeyOwner() {
        if (signedKeyOwner !== undefined) return signedKeyOwner;
        const key = await this.getSignedKey();
        if (key == null) return signedKeyOwner = null;
        return signedKeyOwner = await getKeyOwner(key, context.documentLoader);
      },
    };
    return reqCtx;
  }
+3 −0
Original line number Diff line number Diff line
@@ -168,6 +168,9 @@ Deno.test("getKeyOwner()", async () => {
    }),
  );

  const owner3 = await getKeyOwner(publicKey1, mockDocumentLoader);
  assertEquals(owner3, owner2);

  const noOwner = await getKeyOwner(
    new URL("https://example.com/key2"),
    mockDocumentLoader,
Loading