diff --git a/packages/fedify/src/federation/context.ts b/packages/fedify/src/federation/context.ts index 0f6b14a6..acb533d1 100644 --- a/packages/fedify/src/federation/context.ts +++ b/packages/fedify/src/federation/context.ts @@ -211,6 +211,28 @@ export interface Context { */ getActorKeyPairs(identifier: string): Promise; + /** + * Gets the actor's public keys grouped by usage. + * @param identifier The actor's identifier. + * @returns The actor's public keys and assertion methods. It can be empty. + * @since 2.2.0 + */ + getActorKeysByUsage(identifier: string): Promise<{ + publicKeys: CryptographicKey[]; + assertionMethods: Multikey[]; + }>; + + /** + * Gets the first actor key per usage. + * @param identifier The actor's identifier. + * @returns The first public key and assertion method, if available. + * @since 2.2.0 + */ + getActorFirstKeyByUsage(identifier: string): Promise<{ + publicKey: CryptographicKey | undefined; + assertionMethod: Multikey | undefined; + }>; + /** * Gets an authenticated {@link DocumentLoader} for the given identity. * Note that an authenticated document loader intentionally does not cache diff --git a/packages/fedify/src/federation/middleware.test.ts b/packages/fedify/src/federation/middleware.test.ts index 7f065673..af04525b 100644 --- a/packages/fedify/src/federation/middleware.test.ts +++ b/packages/fedify/src/federation/middleware.test.ts @@ -305,8 +305,9 @@ test({ { type: "actor", identifier: "handle" }, ); assertEquals(ctx.parseUri(null), null); + const keyPairs = await ctx.getActorKeyPairs("handle"); assertEquals( - await ctx.getActorKeyPairs("handle"), + keyPairs, [ { keyId: new URL("https://example.com/users/handle#main-key"), @@ -338,6 +339,14 @@ test({ }, ], ); + assertEquals(await ctx.getActorKeysByUsage("handle"), { + publicKeys: keyPairs.map((key) => key.cryptographicKey), + assertionMethods: keyPairs.map((key) => key.multikey), + }); + assertEquals(await ctx.getActorFirstKeyByUsage("handle"), { + publicKey: keyPairs[0].cryptographicKey, + assertionMethod: keyPairs[0].multikey, + }); const loader = await ctx.getDocumentLoader({ identifier: "handle" }); assertEquals(await loader("https://example.com/auth-check"), { contextUrl: null, diff --git a/packages/fedify/src/federation/middleware.ts b/packages/fedify/src/federation/middleware.ts index 476f53be..9fc463aa 100644 --- a/packages/fedify/src/federation/middleware.ts +++ b/packages/fedify/src/federation/middleware.ts @@ -1969,6 +1969,29 @@ export class ContextImpl implements Context { return result; } + async getActorKeysByUsage(identifier: string): Promise<{ + publicKeys: CryptographicKey[]; + assertionMethods: Multikey[]; + }> { + const keys = await this.getActorKeyPairs(identifier); + return { + publicKeys: keys.map((key) => key.cryptographicKey), + assertionMethods: keys.map((key) => key.multikey), + }; + } + + async getActorFirstKeyByUsage(identifier: string): Promise<{ + publicKey: CryptographicKey | undefined; + assertionMethod: Multikey | undefined; + }> { + const { publicKeys, assertionMethods } = + await this.getActorKeysByUsage(identifier); + return { + publicKey: publicKeys[0], + assertionMethod: assertionMethods[0], + }; + } + protected async getKeyPairsFromIdentifier( identifier: string, ): Promise<(CryptoKeyPair & { keyId: URL })[]> {