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 type { Undo } from "@lysand-org/federation/types";
import { getLogger } from "@logtape/logtape";
import { SignatureConstructor } from "@lysand-org/federation";
import type { Entity, Undo } from "@lysand-org/federation/types";
import { config } from "config-manager"; import { config } from "config-manager";
import type { User } from "~/packages/database-interface/user"; import type { User } from "~/packages/database-interface/user";
export const localObjectUri = (id: string) => export const localObjectUri = (id: string) =>
new URL(`/objects/${id}`, config.http.base_url).toString(); 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 => { export const undoFederationRequest = (undoer: User, uri: string): Undo => {
const id = crypto.randomUUID(); const id = crypto.randomUUID();
return { return {

View file

@ -1,11 +1,6 @@
import { mentionValidator } from "@/api"; import { mentionValidator } from "@/api";
import { sanitizeHtml, sanitizeHtmlInline } from "@/sanitization"; import { sanitizeHtml, sanitizeHtmlInline } from "@/sanitization";
import markdownItTaskLists from "@hackmd/markdown-it-task-lists"; 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 type { ContentFormat } from "@lysand-org/federation/types";
import { config } from "config-manager"; import { config } from "config-manager";
import { import {
@ -37,11 +32,9 @@ import {
type Notes, type Notes,
Users, Users,
} from "~/drizzle/schema"; } 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 { User } from "~/packages/database-interface/user";
import type { Application } from "./application"; import type { Application } from "./application";
import type { EmojiWithInstance } from "./emoji";
import { objectToInboxRequest } from "./federation";
import { import {
type UserWithInstance, type UserWithInstance,
type UserWithRelations, type UserWithRelations,
@ -316,11 +309,7 @@ export const parseTextMentions = async (
// Attempt to resolve mentions that were not found // Attempt to resolve mentions that were not found
for (const person of notFoundRemoteUsers) { for (const person of notFoundRemoteUsers) {
const signatureConstructor = await SignatureConstructor.fromStringKey( const manager = await author.getFederationRequester();
author.data.privateKey ?? "",
author.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const uri = await User.webFinger( const uri = await User.webFinger(
manager, manager,
@ -451,26 +440,3 @@ export const getMarkdownRenderer = () => {
return renderer; 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 { import type {
Follow, Follow,
FollowAccept, FollowAccept,
@ -19,10 +15,9 @@ import {
Tokens, Tokens,
type Users, type Users,
} from "~/drizzle/schema"; } from "~/drizzle/schema";
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
import type { Application } from "./application"; import type { Application } from "./application";
import type { EmojiWithInstance } from "./emoji";
import { objectToInboxRequest } from "./federation";
import { import {
type Relationship, type Relationship,
checkForBidirectionalRelationships, checkForBidirectionalRelationships,
@ -168,25 +163,12 @@ export const followRequestUser = async (
} }
if (isRemote) { if (isRemote) {
// Federate const { ok } = await follower.federateToUser(
// TODO: Make database job
const request = await objectToInboxRequest(
followRequestToLysand(follower, followee), followRequestToLysand(follower, followee),
follower,
followee, followee,
); );
// Send request if (!ok) {
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()}`;
await db await db
.update(Relationships) .update(Relationships)
.set({ .set({
@ -217,45 +199,17 @@ export const followRequestUser = async (
}; };
export const sendFollowAccept = async (follower: User, followee: User) => { export const sendFollowAccept = async (follower: User, followee: User) => {
// TODO: Make database job await follower.federateToUser(
const request = await objectToInboxRequest(
followAcceptToLysand(follower, followee), followAcceptToLysand(follower, followee),
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) => { export const sendFollowReject = async (follower: User, followee: User) => {
// TODO: Make database job await follower.federateToUser(
const request = await objectToInboxRequest(
followRejectToLysand(follower, followee), followRejectToLysand(follower, followee),
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 = ( export const transformOutputToUserWithRelations = (

View file

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

View file

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

View file

@ -1,20 +1,25 @@
import { emojiValidatorWithColons } from "@/api";
import { proxyUrl } from "@/response"; import { proxyUrl } from "@/response";
import type { Emoji as ApiEmoji } from "@lysand-org/client/types"; import type { Emoji as ApiEmoji } from "@lysand-org/client/types";
import type { CustomEmojiExtension } from "@lysand-org/federation/types"; import type { CustomEmojiExtension } from "@lysand-org/federation/types";
import { import {
type InferInsertModel, type InferInsertModel,
type InferSelectModel,
type SQL, type SQL,
and, and,
desc, desc,
eq, eq,
inArray, inArray,
} from "drizzle-orm"; } from "drizzle-orm";
import type { EmojiWithInstance } from "~/classes/functions/emoji";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Emojis, Instances } from "~/drizzle/schema"; import { Emojis, Instances } from "~/drizzle/schema";
import { BaseInterface } from "./base"; import { BaseInterface } from "./base";
import { Instance } from "./instance"; import { Instance } from "./instance";
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
instance: InferSelectModel<typeof Instances> | null;
};
export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> { export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
async reload(): Promise<void> { async reload(): Promise<void> {
const reloaded = await Emoji.fromId(this.data.id); const reloaded = await Emoji.fromId(this.data.id);
@ -152,6 +157,26 @@ export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
return this.data.id; 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 { public toApi(): ApiEmoji {
return { return {
// @ts-expect-error ID is not in regular Mastodon API // @ts-expect-error ID is not in regular Mastodon API

View file

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

View file

@ -7,7 +7,7 @@ import type {
Attachment as ApiAttachment, Attachment as ApiAttachment,
Status as ApiStatus, Status as ApiStatus,
} from "@lysand-org/client/types"; } from "@lysand-org/client/types";
import { EntityValidator, FederationRequester } from "@lysand-org/federation"; import { EntityValidator } from "@lysand-org/federation";
import type { import type {
ContentFormat, ContentFormat,
Note as LysandNote, Note as LysandNote,
@ -29,7 +29,6 @@ import {
type Application, type Application,
applicationToApi, applicationToApi,
} from "~/classes/functions/application"; } from "~/classes/functions/application";
import { parseEmojis } from "~/classes/functions/emoji";
import { localObjectUri } from "~/classes/functions/federation"; import { localObjectUri } from "~/classes/functions/federation";
import { import {
type StatusWithRelations, type StatusWithRelations,
@ -190,6 +189,14 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
return this.data.id; 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 * 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 = [ const parsedEmojis = [
...(data.emojis ?? []), ...(data.emojis ?? []),
...(await parseEmojis(plaintextContent)), ...(await Emoji.parseFromText(plaintextContent)),
// Deduplicate by .id // Deduplicate by .id
].filter( ].filter(
(emoji, index, self) => (emoji, index, self) =>
@ -429,7 +436,9 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
const parsedEmojis = [ const parsedEmojis = [
...(data.emojis ?? []), ...(data.emojis ?? []),
...(plaintextContent ? await parseEmojis(plaintextContent) : []), ...(plaintextContent
? await Emoji.parseFromText(plaintextContent)
: []),
// Deduplicate by .id // Deduplicate by .id
].filter( ].filter(
(emoji, index, self) => (emoji, index, self) =>
@ -592,7 +601,10 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
throw new Error(`Invalid URI to parse ${uri}`); 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 // @ts-expect-error Bun extension
proxy: config.http.proxy.address, proxy: config.http.proxy.address,
}); });

View file

@ -2,12 +2,20 @@ import { idValidator } from "@/api";
import { getBestContentType, urlToContentFormat } from "@/content_types"; import { getBestContentType, urlToContentFormat } from "@/content_types";
import { randomString } from "@/math"; import { randomString } from "@/math";
import { proxyUrl } from "@/response"; import { proxyUrl } from "@/response";
import { sentry } from "@/sentry";
import { getLogger } from "@logtape/logtape";
import type { import type {
Account as ApiAccount, Account as ApiAccount,
Mention as ApiMention, Mention as ApiMention,
} from "@lysand-org/client/types"; } 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 type { Entity, User as LysandUser } from "@lysand-org/federation/types";
import chalk from "chalk";
import { import {
type InferInsertModel, type InferInsertModel,
type SQL, type SQL,
@ -23,7 +31,6 @@ import {
sql, sql,
} from "drizzle-orm"; } from "drizzle-orm";
import { htmlToText } from "html-to-text"; import { htmlToText } from "html-to-text";
import { objectToInboxRequest } from "~/classes/functions/federation";
import { import {
type UserWithRelations, type UserWithRelations,
findManyUsers, findManyUsers,
@ -341,9 +348,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
uri: string, uri: string,
instance: Instance, instance: Instance,
): Promise<User> { ): Promise<User> {
const { data: json } = await new FederationRequester().get< const requester = await User.getServerActor().getFederationRequester();
Partial<LysandUser> const { data: json } = await requester.get<Partial<LysandUser>>(uri, {
>(uri, {
// @ts-expect-error Bun extension // @ts-expect-error Bun extension
proxy: config.http.proxy.address, proxy: config.http.proxy.address,
}); });
@ -617,7 +623,66 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return updated.data; 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 // Get followers
const followers = await User.manyFromSql( const followers = await User.manyFromSql(
and( and(
@ -627,19 +692,45 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
); );
for (const follower of followers) { for (const follower of followers) {
const federationRequest = await objectToInboxRequest( await this.federateToUser(entity, follower);
object,
this,
follower,
);
// FIXME: Add to new queue system when it's implemented
fetch(federationRequest, {
proxy: config.http.proxy.address,
});
} }
} }
/**
* 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 { toApi(isOwnAccount = false): ApiAccount {
const user = this.data; const user = this.data;
return { return {

View file

@ -2,10 +2,6 @@ import { applyConfig, auth, handleZodError } from "@/api";
import { errorResponse, jsonResponse } from "@/response"; import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "@hono/hono"; import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { import {
anyOf, anyOf,
@ -84,12 +80,7 @@ export default (app: Hono) =>
const requester = user ?? User.getServerActor(); const requester = user ?? User.getServerActor();
const signatureConstructor = const manager = await requester.getFederationRequester();
await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const uri = await User.webFinger(manager, username, domain); 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 { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "@hono/hono"; import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import {
FederationRequester,
SignatureConstructor,
} from "@lysand-org/federation";
import { eq, ilike, not, or, sql } from "drizzle-orm"; import { eq, ilike, not, or, sql } from "drizzle-orm";
import { import {
anyOf, anyOf,
@ -95,12 +91,7 @@ export default (app: Hono) =>
if (resolve && username && host) { if (resolve && username && host) {
const requester = self ?? User.getServerActor(); const requester = self ?? User.getServerActor();
const signatureConstructor = const manager = await requester.getFederationRequester();
await SignatureConstructor.fromStringKey(
requester.data.privateKey ?? "",
requester.getUri(),
);
const manager = new FederationRequester(signatureConstructor);
const uri = await User.webFinger(manager, username, host); 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 { and, eq, isNull } from "drizzle-orm";
import ISO6391 from "iso-639-1"; import ISO6391 from "iso-639-1";
import { z } from "zod"; import { z } from "zod";
import { parseEmojis } from "~/classes/functions/emoji";
import { contentToHtml } from "~/classes/functions/status"; import { contentToHtml } from "~/classes/functions/status";
import { MediaManager } from "~/classes/media/media-manager"; import { MediaManager } from "~/classes/media/media-manager";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { EmojiToUser, RolePermissions, Users } from "~/drizzle/schema"; import { EmojiToUser, RolePermissions, Users } from "~/drizzle/schema";
import { Attachment } from "~/packages/database-interface/attachment"; 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"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -252,8 +251,8 @@ export default (app: Hono) =>
); );
// Parse emojis // Parse emojis
const nameEmojis = await parseEmojis(parsedName); const nameEmojis = await Emoji.parseFromText(parsedName);
const valueEmojis = await parseEmojis(parsedValue); const valueEmojis = await Emoji.parseFromText(parsedValue);
fieldEmojis.push(...nameEmojis, ...valueEmojis); fieldEmojis.push(...nameEmojis, ...valueEmojis);
@ -279,8 +278,9 @@ export default (app: Hono) =>
} }
// Parse emojis // Parse emojis
const displaynameEmojis = await parseEmojis(sanitizedDisplayName); const displaynameEmojis =
const noteEmojis = await parseEmojis(self.note); await Emoji.parseFromText(sanitizedDisplayName);
const noteEmojis = await Emoji.parseFromText(self.note);
self.emojis = [ self.emojis = [
...displaynameEmojis, ...displaynameEmojis,

View file

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

View file

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

View file

@ -2,8 +2,6 @@ import { applyConfig, handleZodError } from "@/api";
import { errorResponse, response } from "@/response"; import { errorResponse, response } from "@/response";
import type { Hono } from "@hono/hono"; import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator"; 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 type { Entity } from "@lysand-org/federation/types";
import { and, eq, inArray, sql } from "drizzle-orm"; import { and, eq, inArray, sql } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
@ -100,22 +98,7 @@ export default (app: Hono) =>
const author = foundAuthor ?? User.getServerActor(); const author = foundAuthor ?? User.getServerActor();
const { headers, signedString } = await ( const { headers } = await author.sign(apiObject, reqUrl, "GET");
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}`;
}
return response(objectString, 200, { return response(objectString, 200, {
"Content-Type": "application/json", "Content-Type": "application/json",

View file

@ -2,8 +2,6 @@ import { applyConfig, handleZodError } from "@/api";
import { errorResponse, redirect, response } from "@/response"; import { errorResponse, redirect, response } from "@/response";
import type { Hono } from "@hono/hono"; import type { Hono } from "@hono/hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { getLogger } from "@logtape/logtape";
import { SignatureConstructor } from "@lysand-org/federation";
import { z } from "zod"; import { z } from "zod";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
@ -84,22 +82,7 @@ export default (app: Hono) =>
reqUrl.protocol = "https:"; reqUrl.protocol = "https:";
} }
const { headers, signedString } = await ( const { headers } = await user.sign(user.toLysand(), reqUrl, "GET");
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}`;
}
return response(userString, 200, { return response(userString, 200, {
"Content-Type": "application/json", "Content-Type": "application/json",

View file

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