Unverified Commit 8d28f99f authored by Hong Minhee's avatar Hong Minhee
Browse files

Provide a full context to key pairs dispatcher

parent a0fdeee4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@ To be released.
     -  Added `Context.hostname` property.
     -  Added `Context.host` property.
     -  Added `Context.origin` property.
     -  The type of `ActorKeyPairsDispatcher<TContextData>`'s first parameter
        became `Context` (was `TContextData`).

[#66]: https://github.com/dahlia/fedify/issues/66
[#85]: https://github.com/dahlia/fedify/issues/85
+2 −2
Original line number Diff line number Diff line
@@ -195,7 +195,7 @@ federation.setActorDispatcher("/users/{handle}", async (ctx, handle) => {
    // Many more properties; see the previous section for details.
  });
})
  .setKeyPairsDispatcher(async (ctxData, handle) => {
  .setKeyPairsDispatcher(async (ctx, handle) => {
    // Work with the database to find the key pair by the handle.
    if (user == null) return [];  // Return null if the key pair is not found.
    // Return the loaded key pair.  See the below example for details.
@@ -256,7 +256,7 @@ federation
  .setActorDispatcher("/users/{handle}", async (ctx, handle) => {
    // Omitted for brevity; see the previous example for details.
  })
  .setKeyPairsDispatcher(async (ctxData, handle) => {
  .setKeyPairsDispatcher(async (ctx, handle) => {
    const kv = await Deno.openKv();
    const entry = await kv.get<{ privateKey: JsonWebKey; publicKey: JsonWebKey }>(
      ["keypair", handle],
+1 −1
Original line number Diff line number Diff line
@@ -80,7 +80,7 @@ federation.setActorDispatcher("/users/{handle}", async (ctx, handle) => {
    assertionMethods: keyPairs.map((keyPair) => keyPair.multikey),
  });
})
  .setKeyPairsDispatcher(async (_ctxData, handle) => {
  .setKeyPairsDispatcher(async (_ctx, handle) => {
    const blog = await getBlog();
    if (blog == null) return [];
    else if (blog.handle !== handle) return [];
+3 −3
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@ import type { Actor } from "../vocab/actor.ts";
import type { Activity, CryptographicKey } from "../vocab/mod.ts";
import type { Object } from "../vocab/vocab.ts";
import type { PageItems } from "./collection.ts";
import type { RequestContext } from "./context.ts";
import type { Context, RequestContext } from "./context.ts";
import type { SenderKeyPair } from "./send.ts";

/**
@@ -35,13 +35,13 @@ export type ActorDispatcher<TContextData> = (
 * A callback that dispatches key pairs for an actor.
 *
 * @typeParam TContextData The context data to pass to the {@link Context}.
 * @param contextData The context data.
 * @param context The context.
 * @param handle The actor's handle.
 * @returns The key pairs.
 * @since 0.10.0
 */
export type ActorKeyPairsDispatcher<TContextData> = (
  contextData: TContextData,
  context: Context<TContextData>,
  handle: string,
) => CryptoKeyPair[] | Promise<CryptoKeyPair[]>;

+41 −25
Original line number Diff line number Diff line
@@ -748,8 +748,8 @@ export class Federation<TContextData> {
            "deprecated.  Use the ActorCallbackSetters.setKeyPairsDispatcher() " +
            "instead.",
        );
        callbacks.keyPairsDispatcher = async (ctxData, handle) => {
          const key = await dispatcher(ctxData, handle);
        callbacks.keyPairsDispatcher = async (ctx, handle) => {
          const key = await dispatcher(ctx.data, handle);
          if (key == null) return [];
          return [key];
        };
@@ -1737,6 +1737,7 @@ interface ContextOptions<TContextData> {
  documentLoader: DocumentLoader;
  contextLoader: DocumentLoader;
  authenticatedDocumentLoaderFactory: AuthenticatedDocumentLoaderFactory;
  invokedFromActorKeyPairsDispatcher?: { handle: string };
}

class ContextImpl<TContextData> implements Context<TContextData> {
@@ -1758,6 +1759,7 @@ class ContextImpl<TContextData> implements Context<TContextData> {
  readonly contextLoader: DocumentLoader;
  readonly #authenticatedDocumentLoaderFactory:
    AuthenticatedDocumentLoaderFactory;
  readonly #invokedFromActorKeyPairsDispatcher?: { handle: string };

  constructor(
    {
@@ -1771,6 +1773,7 @@ class ContextImpl<TContextData> implements Context<TContextData> {
      documentLoader,
      contextLoader,
      authenticatedDocumentLoaderFactory,
      invokedFromActorKeyPairsDispatcher,
    }: ContextOptions<TContextData>,
  ) {
    this.#url = url;
@@ -1784,6 +1787,8 @@ class ContextImpl<TContextData> implements Context<TContextData> {
    this.contextLoader = contextLoader;
    this.#authenticatedDocumentLoaderFactory =
      authenticatedDocumentLoaderFactory;
    this.#invokedFromActorKeyPairsDispatcher =
      invokedFromActorKeyPairsDispatcher;
  }

  get hostname(): string {
@@ -1945,16 +1950,24 @@ class ContextImpl<TContextData> implements Context<TContextData> {
  }

  async getActorKeyPairs(handle: string): Promise<ActorKeyPair[]> {
    const logger = getLogger(["fedify", "federation", "actor"]);
    if (this.#invokedFromActorKeyPairsDispatcher != null) {
      logger.warn(
        "Context.getActorKeyPairs({getActorKeyPairsHandle}) method is " +
          "invoked from the actor key pairs dispatcher " +
          "({actorKeyPairsDispatcherHandle}); this may cause an infinite loop.",
        {
          getActorKeyPairsHandle: handle,
          actorKeyPairsDispatcherHandle:
            this.#invokedFromActorKeyPairsDispatcher.handle,
        },
      );
    }
    let keyPairs: (CryptoKeyPair & { keyId: URL })[];
    try {
      keyPairs = await this.getKeyPairsFromHandle(
        this.#url,
        this.data,
        handle,
      );
      keyPairs = await this.getKeyPairsFromHandle(handle);
    } catch (_) {
      getLogger(["fedify", "federation", "actor"])
        .warn("No actor key pairs dispatcher registered.");
      logger.warn("No actor key pairs dispatcher registered.");
      return [];
    }
    const owner = this.getActorUri(handle);
@@ -1979,8 +1992,6 @@ class ContextImpl<TContextData> implements Context<TContextData> {
  }

  protected async getKeyPairsFromHandle(
    url: URL | string,
    contextData: TContextData,
    handle: string,
  ): Promise<(CryptoKeyPair & { keyId: URL })[]> {
    const logger = getLogger(["fedify", "federation", "actor"]);
@@ -1992,9 +2003,22 @@ class ContextImpl<TContextData> implements Context<TContextData> {
      logger.warn("No actor dispatcher registered.");
      return [];
    }
    const actorUri = new URL(path, url);
    const actorUri = new URL(path, this.#url);
    const keyPairs = await this.actorCallbacks?.keyPairsDispatcher(
      contextData,
      new ContextImpl({
        url: this.#url,
        federation: this.#federation,
        router: this.#router,
        objectTypeIds: this.#objectTypeIds,
        objectCallbacks: this.objectCallbacks,
        actorCallbacks: this.actorCallbacks,
        data: this.data,
        documentLoader: this.documentLoader,
        contextLoader: this.contextLoader,
        authenticatedDocumentLoaderFactory:
          this.#authenticatedDocumentLoaderFactory,
        invokedFromActorKeyPairsDispatcher: { handle },
      }),
      handle,
    );
    if (keyPairs.length < 1) {
@@ -2038,11 +2062,7 @@ class ContextImpl<TContextData> implements Context<TContextData> {
  protected async getRsaKeyPairFromHandle(
    handle: string,
  ): Promise<CryptoKeyPair & { keyId: URL } | null> {
    const keyPairs = await this.getKeyPairsFromHandle(
      this.#url,
      this.data,
      handle,
    );
    const keyPairs = await this.getKeyPairsFromHandle(handle);
    for (const keyPair of keyPairs) {
      const { privateKey } = keyPair;
      if (
@@ -2085,11 +2105,7 @@ class ContextImpl<TContextData> implements Context<TContextData> {
  ): Promise<void> {
    let keys: SenderKeyPair[];
    if ("handle" in sender) {
      keys = await this.getKeyPairsFromHandle(
        this.#url,
        this.data,
        sender.handle,
      );
      keys = await this.getKeyPairsFromHandle(sender.handle);
      if (keys.length < 1) {
        throw new Error(
          `No key pair found for actor ${JSON.stringify(sender.handle)}.`,
@@ -2223,7 +2239,7 @@ class RequestContextImpl<TContextData> extends ContextImpl<TContextData>
      throw new Error("No actor dispatcher registered.");
    }
    if (this.#invokedFromActorDispatcher != null) {
      getLogger(["fedify", "federation"]).warn(
      getLogger(["fedify", "federation", "actor"]).warn(
        "RequestContext.getActor({getActorHandle}) is invoked from " +
          "the actor dispatcher ({actorDispatcherHandle}); " +
          "this may cause an infinite loop.",
@@ -2385,7 +2401,7 @@ export interface ActorCallbackSetters<TContextData> {
   * Use {@link ActorCallbackSetters.setKeyPairsDispatcher} instead.
   * @param dispatcher A callback that returns the key pair for an actor.
   * @returns The setters object so that settings can be chained.
   * @deprecated
   * @deprecated Use {@link ActorCallbackSetters.setKeyPairsDispatcher} instead.
   */
  setKeyPairDispatcher(
    dispatcher: ActorKeyPairDispatcher<TContextData>,