refactor(federation): 🔥 Remove old code and simplify federation requests

This commit is contained in:
Jesse Wierzbinski 2024-07-26 18:51:39 +02:00
parent ad9ed2598c
commit 2f823317c2
No known key found for this signature in database
18 changed files with 182 additions and 302 deletions

View file

@ -1,27 +0,0 @@
import { emojiValidatorWithColons } from "@/api";
import { type InferSelectModel, inArray } from "drizzle-orm";
import { Emojis, type Instances } from "~/drizzle/schema";
import { Emoji } from "~/packages/database-interface/emoji";
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
instance: InferSelectModel<typeof Instances> | null;
};
/**
* Used for parsing emojis from local text
* @param text The text to parse
* @returns An array of emojis
*/
export const parseEmojis = async (text: string): Promise<Emoji[]> => {
const matches = text.match(emojiValidatorWithColons);
if (!matches || matches.length === 0) {
return [];
}
return Emoji.manyFromSql(
inArray(
Emojis.shortcode,
matches.map((match) => match.replace(/:/g, "")),
),
);
};

View file

@ -1,65 +1,10 @@
import { debugRequest } from "@/api";
import { getLogger } from "@logtape/logtape";
import { SignatureConstructor } from "@lysand-org/federation";
import type { Entity, Undo } from "@lysand-org/federation/types";
import type { Undo } from "@lysand-org/federation/types";
import { config } from "config-manager";
import type { User } from "~/packages/database-interface/user";
export const localObjectUri = (id: string) =>
new URL(`/objects/${id}`, config.http.base_url).toString();
export const objectToInboxRequest = async (
object: Entity,
author: User,
userToSendTo: User,
): Promise<Request> => {
if (userToSendTo.isLocal() || !userToSendTo.data.endpoints?.inbox) {
throw new Error("UserToSendTo has no inbox or is a local user");
}
if (author.isRemote()) {
throw new Error("Author is a remote user");
}
const privateKey = await crypto.subtle.importKey(
"pkcs8",
Buffer.from(author.data.privateKey ?? "", "base64"),
"Ed25519",
false,
["sign"],
);
const ctor = new SignatureConstructor(privateKey, author.getUri());
const userInbox = new URL(userToSendTo.data.endpoints?.inbox ?? "");
const request = new Request(userInbox, {
method: "POST",
headers: {
"Content-Type": "application/json",
Origin: new URL(config.http.base_url).host,
},
body: JSON.stringify(object),
});
const { request: signed, signedString } = await ctor.sign(request);
if (config.debug.federation) {
// Debug request
await debugRequest(signed);
const logger = getLogger("federation");
// Log public key
logger.debug`Sender public key: ${author.data.publicKey}`;
// Log signed string
logger.debug`Signed string:\n${signedString}`;
}
return signed;
};
export const undoFederationRequest = (undoer: User, uri: string): Undo => {
const id = crypto.randomUUID();
return {

View file

@ -1,11 +1,6 @@
import { mentionValidator } from "@/api";
import { sanitizeHtml, sanitizeHtmlInline } from "@/sanitization";
import markdownItTaskLists from "@hackmd/markdown-it-task-lists";
import { getLogger } from "@logtape/logtape";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import type { ContentFormat } from "@lysand-org/federation/types";
import { config } from "config-manager";
import {
@ -37,11 +32,9 @@ import {
type Notes,
Users,
} from "~/drizzle/schema";
import type { Note } from "~/packages/database-interface/note";
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
import { User } from "~/packages/database-interface/user";
import type { Application } from "./application";
import type { EmojiWithInstance } from "./emoji";
import { objectToInboxRequest } from "./federation";
import {
type UserWithInstance,
type UserWithRelations,
@ -316,11 +309,7 @@ export const parseTextMentions = async (
// Attempt to resolve mentions that were not found
for (const person of notFoundRemoteUsers) {
const signatureConstructor = await SignatureConstructor.fromStringKey(
author.data.privateKey ?? "",
author.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const manager = await author.getFederationRequester();
const uri = await User.webFinger(
manager,
@ -451,26 +440,3 @@ export const getMarkdownRenderer = () => {
return renderer;
};
export const federateNote = async (note: Note) => {
for (const user of await note.getUsersToFederateTo()) {
// TODO: Add queue system
const request = await objectToInboxRequest(
note.toLysand(),
note.author,
user,
);
// Send request
const response = await fetch(request, {
proxy: config.http.proxy.address,
});
if (!response.ok) {
const logger = getLogger("federation");
logger.debug`${await response.text()}`;
logger.error`Failed to federate status ${note.data.id} to ${user.getUri()}`;
}
}
};

View file

@ -1,7 +1,3 @@
/**
* Old code that needs to be rewritten
*/
import { getLogger } from "@logtape/logtape";
import type {
Follow,
FollowAccept,
@ -19,10 +15,9 @@ import {
Tokens,
type Users,
} from "~/drizzle/schema";
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
import { User } from "~/packages/database-interface/user";
import type { Application } from "./application";
import type { EmojiWithInstance } from "./emoji";
import { objectToInboxRequest } from "./federation";
import {
type Relationship,
checkForBidirectionalRelationships,
@ -168,25 +163,12 @@ export const followRequestUser = async (
}
if (isRemote) {
// Federate
// TODO: Make database job
const request = await objectToInboxRequest(
const { ok } = await follower.federateToUser(
followRequestToLysand(follower, followee),
follower,
followee,
);
// Send request
const response = await fetch(request, {
proxy: config.http.proxy.address,
});
if (!response.ok) {
const logger = getLogger("federation");
logger.debug`${await response.text()}`;
logger.error`Failed to federate follow request from ${follower.id} to ${followee.getUri()}`;
if (!ok) {
await db
.update(Relationships)
.set({
@ -217,45 +199,17 @@ export const followRequestUser = async (
};
export const sendFollowAccept = async (follower: User, followee: User) => {
// TODO: Make database job
const request = await objectToInboxRequest(
await follower.federateToUser(
followAcceptToLysand(follower, followee),
followee,
follower,
);
// Send request
const response = await fetch(request, {
proxy: config.http.proxy.address,
});
if (!response.ok) {
const logger = getLogger("federation");
logger.debug`${await response.text()}`;
logger.error`Failed to federate follow accept from ${followee.id} to ${follower.getUri()}`;
}
};
export const sendFollowReject = async (follower: User, followee: User) => {
// TODO: Make database job
const request = await objectToInboxRequest(
await follower.federateToUser(
followRejectToLysand(follower, followee),
followee,
follower,
);
// Send request
const response = await fetch(request, {
proxy: config.http.proxy.address,
});
if (!response.ok) {
const logger = getLogger("federation");
logger.debug`${await response.text()}`;
logger.error`Failed to federate follow reject from ${followee.id} to ${follower.getUri()}`;
}
};
export const transformOutputToUserWithRelations = (

View file

@ -1,8 +1,4 @@
import { parseUserAddress, userAddressValidator } from "@/api";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { Args } from "@oclif/core";
import chalk from "chalk";
import ora from "ora";
@ -47,11 +43,7 @@ export default class FederationUserFetch extends BaseCommand<
const requester = await User.getServerActor();
const signatureConstructor = await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const manager = await requester.getFederationRequester();
const uri = await User.webFinger(manager, username, host);

View file

@ -1,8 +1,4 @@
import { parseUserAddress, userAddressValidator } from "@/api";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { Args } from "@oclif/core";
import chalk from "chalk";
import ora from "ora";
@ -47,11 +43,7 @@ export default class FederationUserFinger extends BaseCommand<
const requester = await User.getServerActor();
const signatureConstructor = await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const manager = await requester.getFederationRequester();
const uri = await User.webFinger(manager, username, host);

View file

@ -1,20 +1,25 @@
import { emojiValidatorWithColons } from "@/api";
import { proxyUrl } from "@/response";
import type { Emoji as ApiEmoji } from "@lysand-org/client/types";
import type { CustomEmojiExtension } from "@lysand-org/federation/types";
import {
type InferInsertModel,
type InferSelectModel,
type SQL,
and,
desc,
eq,
inArray,
} from "drizzle-orm";
import type { EmojiWithInstance } from "~/classes/functions/emoji";
import { db } from "~/drizzle/db";
import { Emojis, Instances } from "~/drizzle/schema";
import { BaseInterface } from "./base";
import { Instance } from "./instance";
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
instance: InferSelectModel<typeof Instances> | null;
};
export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
async reload(): Promise<void> {
const reloaded = await Emoji.fromId(this.data.id);
@ -152,6 +157,26 @@ export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
return this.data.id;
}
/**
* Parse emojis from text
*
* @param text The text to parse
* @returns An array of emojis
*/
public static async parseFromText(text: string): Promise<Emoji[]> {
const matches = text.match(emojiValidatorWithColons);
if (!matches || matches.length === 0) {
return [];
}
return Emoji.manyFromSql(
inArray(
Emojis.shortcode,
matches.map((match) => match.replace(/:/g, "")),
),
);
}
public toApi(): ApiEmoji {
return {
// @ts-expect-error ID is not in regular Mastodon API

View file

@ -1,7 +1,6 @@
import { getLogger } from "@logtape/logtape";
import {
EntityValidator,
FederationRequester,
type ResponseError,
type ValidationError,
} from "@lysand-org/federation";
@ -20,6 +19,7 @@ import {
import { db } from "~/drizzle/db";
import { Instances } from "~/drizzle/schema";
import { BaseInterface } from "./base";
import { User } from "./user";
export type AttachmentType = InferSelectModel<typeof Instances>;
@ -139,8 +139,10 @@ export class Instance extends BaseInterface<typeof Instances> {
const wellKnownUrl = new URL("/.well-known/lysand", origin);
const logger = getLogger("federation");
const requester = await User.getServerActor().getFederationRequester();
try {
const { ok, raw, data } = await new FederationRequester()
const { ok, raw, data } = await requester
.get(wellKnownUrl, {
// @ts-expect-error Bun extension
proxy: config.http.proxy.address,
@ -193,13 +195,14 @@ export class Instance extends BaseInterface<typeof Instances> {
// Go to endpoint, then follow the links to the actual metadata
const logger = getLogger("federation");
const requester = await User.getServerActor().getFederationRequester();
try {
const {
raw: response,
ok,
data: wellKnown,
} = await new FederationRequester()
} = await requester
.get<{
links: { rel: string; href: string }[];
}>(wellKnownUrl, {
@ -245,7 +248,7 @@ export class Instance extends BaseInterface<typeof Instances> {
raw: metadataResponse,
ok: ok2,
data: metadata,
} = await new FederationRequester()
} = await requester
.get<{
metadata: {
nodeName?: string;

View file

@ -7,7 +7,7 @@ import type {
Attachment as ApiAttachment,
Status as ApiStatus,
} from "@lysand-org/client/types";
import { EntityValidator, FederationRequester } from "@lysand-org/federation";
import { EntityValidator } from "@lysand-org/federation";
import type {
ContentFormat,
Note as LysandNote,
@ -29,7 +29,6 @@ import {
type Application,
applicationToApi,
} from "~/classes/functions/application";
import { parseEmojis } from "~/classes/functions/emoji";
import { localObjectUri } from "~/classes/functions/federation";
import {
type StatusWithRelations,
@ -190,6 +189,14 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
return this.data.id;
}
async federateToUsers(): Promise<void> {
const users = await this.getUsersToFederateTo();
for (const user of users) {
await this.author.federateToUser(this.toLysand(), user);
}
}
/**
* Fetch the users that should be federated to for this note
*
@ -336,7 +343,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
const parsedEmojis = [
...(data.emojis ?? []),
...(await parseEmojis(plaintextContent)),
...(await Emoji.parseFromText(plaintextContent)),
// Deduplicate by .id
].filter(
(emoji, index, self) =>
@ -429,7 +436,9 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
const parsedEmojis = [
...(data.emojis ?? []),
...(plaintextContent ? await parseEmojis(plaintextContent) : []),
...(plaintextContent
? await Emoji.parseFromText(plaintextContent)
: []),
// Deduplicate by .id
].filter(
(emoji, index, self) =>
@ -592,7 +601,10 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
throw new Error(`Invalid URI to parse ${uri}`);
}
const { data } = await new FederationRequester().get(uri, {
const requester =
await User.getServerActor().getFederationRequester();
const { data } = await requester.get(uri, {
// @ts-expect-error Bun extension
proxy: config.http.proxy.address,
});

View file

@ -2,12 +2,20 @@ import { idValidator } from "@/api";
import { getBestContentType, urlToContentFormat } from "@/content_types";
import { randomString } from "@/math";
import { proxyUrl } from "@/response";
import { sentry } from "@/sentry";
import { getLogger } from "@logtape/logtape";
import type {
Account as ApiAccount,
Mention as ApiMention,
} from "@lysand-org/client/types";
import { EntityValidator, FederationRequester } from "@lysand-org/federation";
import {
EntityValidator,
FederationRequester,
type HttpVerb,
SignatureConstructor,
} from "@lysand-org/federation";
import type { Entity, User as LysandUser } from "@lysand-org/federation/types";
import chalk from "chalk";
import {
type InferInsertModel,
type SQL,
@ -23,7 +31,6 @@ import {
sql,
} from "drizzle-orm";
import { htmlToText } from "html-to-text";
import { objectToInboxRequest } from "~/classes/functions/federation";
import {
type UserWithRelations,
findManyUsers,
@ -341,9 +348,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
uri: string,
instance: Instance,
): Promise<User> {
const { data: json } = await new FederationRequester().get<
Partial<LysandUser>
>(uri, {
const requester = await User.getServerActor().getFederationRequester();
const { data: json } = await requester.get<Partial<LysandUser>>(uri, {
// @ts-expect-error Bun extension
proxy: config.http.proxy.address,
});
@ -617,7 +623,66 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return updated.data;
}
async federateToFollowers(object: Entity) {
/**
* Signs a Lysand entity with that user's private key
*
* @param entity Entity to sign
* @param signatureUrl URL to embed in signature (must be the same URI of queries made with this signature)
* @param signatureMethod HTTP method to embed in signature (default: POST)
* @returns The signed string and headers to send with the request
*/
async sign(
entity: Entity,
signatureUrl: string | URL,
signatureMethod: HttpVerb = "POST",
): Promise<{
headers: Headers;
signedString: string;
}> {
const signatureConstructor = await SignatureConstructor.fromStringKey(
this.data.privateKey ?? "",
this.getUri(),
);
const output = await signatureConstructor.sign(
signatureMethod,
new URL(signatureUrl),
JSON.stringify(entity),
);
if (config.debug.federation) {
const logger = getLogger("federation");
// Log public key
logger.debug`Sender public key: ${this.data.publicKey}`;
// Log signed string
logger.debug`Signed string:\n${output.signedString}`;
}
return output;
}
/**
* Helper to get the appropriate Lysand SDK requester with this user's private key
*
* @returns The requester
*/
async getFederationRequester(): Promise<FederationRequester> {
const signatureConstructor = await SignatureConstructor.fromStringKey(
this.data.privateKey ?? "",
this.getUri(),
);
return new FederationRequester(signatureConstructor);
}
/**
* Federates an entity to all followers of the user
*
* @param entity Entity to federate
*/
async federateToFollowers(entity: Entity): Promise<void> {
// Get followers
const followers = await User.manyFromSql(
and(
@ -627,19 +692,45 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
);
for (const follower of followers) {
const federationRequest = await objectToInboxRequest(
object,
this,
follower,
);
// FIXME: Add to new queue system when it's implemented
fetch(federationRequest, {
proxy: config.http.proxy.address,
});
await this.federateToUser(entity, follower);
}
}
/**
* Federates an entity to any user.
*
* @param entity Entity to federate
* @param user User to federate to
* @returns Whether the federation was successful
*/
async federateToUser(entity: Entity, user: User): Promise<{ ok: boolean }> {
const { headers } = await this.sign(
entity,
user.data.endpoints?.inbox ?? "",
);
try {
await new FederationRequester().post(
user.data.endpoints?.inbox ?? "",
entity,
{
// @ts-expect-error Bun extension
proxy: config.http.proxy.address,
headers,
},
);
} catch (e) {
getLogger("federation")
.error`Federating ${chalk.gray(entity.type)} to ${user.getUri()} ${chalk.bold.red("failed")}`;
getLogger("federation").error`${e}`;
sentry?.captureException(e);
return { ok: false };
}
return { ok: true };
}
toApi(isOwnAccount = false): ApiAccount {
const user = this.data;
return {

View file

@ -2,10 +2,6 @@ import { applyConfig, auth, handleZodError } from "@/api";
import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { eq } from "drizzle-orm";
import {
anyOf,
@ -84,12 +80,7 @@ export default (app: Hono) =>
const requester = user ?? User.getServerActor();
const signatureConstructor =
await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const manager = await requester.getFederationRequester();
const uri = await User.webFinger(manager, username, domain);

View file

@ -2,10 +2,6 @@ import { applyConfig, auth, handleZodError } from "@/api";
import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { eq, ilike, not, or, sql } from "drizzle-orm";
import {
anyOf,
@ -95,12 +91,7 @@ export default (app: Hono) =>
if (resolve && username && host) {
const requester = self ?? User.getServerActor();
const signatureConstructor =
await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const manager = await requester.getFederationRequester();
const uri = await User.webFinger(manager, username, host);

View file

@ -7,13 +7,12 @@ import { config } from "config-manager";
import { and, eq, isNull } from "drizzle-orm";
import ISO6391 from "iso-639-1";
import { z } from "zod";
import { parseEmojis } from "~/classes/functions/emoji";
import { contentToHtml } from "~/classes/functions/status";
import { MediaManager } from "~/classes/media/media-manager";
import { db } from "~/drizzle/db";
import { EmojiToUser, RolePermissions, Users } from "~/drizzle/schema";
import { Attachment } from "~/packages/database-interface/attachment";
import type { Emoji } from "~/packages/database-interface/emoji";
import { Emoji } from "~/packages/database-interface/emoji";
import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({
@ -252,8 +251,8 @@ export default (app: Hono) =>
);
// Parse emojis
const nameEmojis = await parseEmojis(parsedName);
const valueEmojis = await parseEmojis(parsedValue);
const nameEmojis = await Emoji.parseFromText(parsedName);
const valueEmojis = await Emoji.parseFromText(parsedValue);
fieldEmojis.push(...nameEmojis, ...valueEmojis);
@ -279,8 +278,9 @@ export default (app: Hono) =>
}
// Parse emojis
const displaynameEmojis = await parseEmojis(sanitizedDisplayName);
const noteEmojis = await parseEmojis(self.note);
const displaynameEmojis =
await Emoji.parseFromText(sanitizedDisplayName);
const noteEmojis = await Emoji.parseFromText(self.note);
self.emojis = [
...displaynameEmojis,

View file

@ -5,7 +5,6 @@ import { zValidator } from "@hono/zod-validator";
import { config } from "config-manager";
import ISO6391 from "iso-639-1";
import { z } from "zod";
import { federateNote } from "~/classes/functions/status";
import { RolePermissions } from "~/drizzle/schema";
import { Attachment } from "~/packages/database-interface/attachment";
import { Note } from "~/packages/database-interface/note";
@ -164,7 +163,7 @@ export default (app: Hono) =>
});
if (!local_only) {
await federateNote(newNote);
await newNote.federateToUsers();
}
return jsonResponse(await newNote.toApi(user));

View file

@ -8,10 +8,6 @@ import {
import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { and, eq, inArray, sql } from "drizzle-orm";
import { z } from "zod";
import { searchManager } from "~/classes/search/search-manager";
@ -133,14 +129,8 @@ export default (app: Hono) =>
if (resolve) {
const requester = self ?? User.getServerActor();
const signatureConstructor =
await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(
signatureConstructor,
);
const manager =
await requester.getFederationRequester();
const uri = await User.webFinger(
manager,

View file

@ -2,8 +2,6 @@ import { applyConfig, handleZodError } from "@/api";
import { errorResponse, response } from "@/response";
import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator";
import { getLogger } from "@logtape/logtape";
import { SignatureConstructor } from "@lysand-org/federation";
import type { Entity } from "@lysand-org/federation/types";
import { and, eq, inArray, sql } from "drizzle-orm";
import { z } from "zod";
@ -100,22 +98,7 @@ export default (app: Hono) =>
const author = foundAuthor ?? User.getServerActor();
const { headers, signedString } = await (
await SignatureConstructor.fromStringKey(
author.data.privateKey ?? "",
author.getUri(),
)
).sign("POST", reqUrl, objectString);
if (config.debug.federation) {
const logger = getLogger("federation");
// Log public key
logger.debug`Sender public key: ${author.data.publicKey}`;
// Log signed string
logger.debug`Signed string:\n${signedString}`;
}
const { headers } = await author.sign(apiObject, reqUrl, "GET");
return response(objectString, 200, {
"Content-Type": "application/json",

View file

@ -2,8 +2,6 @@ import { applyConfig, handleZodError } from "@/api";
import { errorResponse, redirect, response } from "@/response";
import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator";
import { getLogger } from "@logtape/logtape";
import { SignatureConstructor } from "@lysand-org/federation";
import { z } from "zod";
import { config } from "~/packages/config-manager";
import { User } from "~/packages/database-interface/user";
@ -84,22 +82,7 @@ export default (app: Hono) =>
reqUrl.protocol = "https:";
}
const { headers, signedString } = await (
await SignatureConstructor.fromStringKey(
user.data.privateKey ?? "",
user.getUri(),
)
).sign("POST", reqUrl, userString);
if (config.debug.federation) {
const logger = getLogger("federation");
// Log public key
logger.debug`Sender public key: ${user.data.publicKey}`;
// Log signed string
logger.debug`Signed string:\n${signedString}`;
}
const { headers } = await user.sign(user.toLysand(), reqUrl, "GET");
return response(userString, 200, {
"Content-Type": "application/json",

View file

@ -8,11 +8,7 @@ import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator";
import { getLogger } from "@logtape/logtape";
import {
FederationRequester,
type ResponseError,
SignatureConstructor,
} from "@lysand-org/federation";
import type { ResponseError } from "@lysand-org/federation";
import { and, eq, isNull } from "drizzle-orm";
import { lookup } from "mime-types";
import { z } from "zod";
@ -84,13 +80,7 @@ export default (app: Hono) =>
if (config.federation.bridge.enabled) {
const requester = await User.getServerActor();
const signatureConstructor =
await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const manager = await requester.getFederationRequester();
try {
activityPubUrl = await manager.webFinger(