mirror of
https://github.com/versia-pub/server.git
synced 2026-04-27 12:49:16 +02:00
feat(federation): Finalize Versia 0.6 port
This commit is contained in:
parent
fca30b4dad
commit
b7e77097ba
20 changed files with 2788 additions and 439 deletions
|
|
@ -28,10 +28,7 @@ export const refetchUserCommand = defineCommand(
|
||||||
const spinner = ora("Refetching user").start();
|
const spinner = ora("Refetching user").start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await User.fromVersia(
|
await User.fromVersia(user.reference);
|
||||||
user.reference,
|
|
||||||
user.reference.domain as string,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail(
|
spinner.fail(
|
||||||
`Failed to refetch user ${chalk.gray(user.data.username)}`,
|
`Failed to refetch user ${chalk.gray(user.data.username)}`,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
default = pkgs.mkShell rec {
|
default = pkgs.mkShell rec {
|
||||||
libPath = with pkgs;
|
libPath = with pkgs;
|
||||||
lib.makeLibraryPath [
|
lib.makeLibraryPath [
|
||||||
|
vips
|
||||||
stdenv.cc.cc.lib
|
stdenv.cc.cc.lib
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -55,7 +56,6 @@
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
bun
|
bun
|
||||||
vips
|
|
||||||
nodePackages.typescript
|
nodePackages.typescript
|
||||||
nodePackages.typescript-language-server
|
nodePackages.typescript-language-server
|
||||||
nix-ld
|
nix-ld
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
"hono-openapi": "~1.1.2",
|
"hono-openapi": "~1.1.2",
|
||||||
"hono-rate-limiter": "~0.5.1",
|
"hono-rate-limiter": "~0.5.1",
|
||||||
"html-to-text": "~9.0.5",
|
"html-to-text": "~9.0.5",
|
||||||
"ioredis": "~5.8.2",
|
"ioredis": "5.9.2",
|
||||||
"ip-matching": "~2.1.2",
|
"ip-matching": "~2.1.2",
|
||||||
"iso-639-1": "~3.1.5",
|
"iso-639-1": "~3.1.5",
|
||||||
"linkify-html": "~4.3.2",
|
"linkify-html": "~4.3.2",
|
||||||
|
|
@ -158,6 +158,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "catalog:",
|
"@bull-board/api": "catalog:",
|
||||||
"@bull-board/hono": "catalog:",
|
"@bull-board/hono": "catalog:",
|
||||||
|
"@clerc/core": "catalog:",
|
||||||
"@clerc/plugin-completions": "catalog:",
|
"@clerc/plugin-completions": "catalog:",
|
||||||
"@clerc/plugin-friendly-error": "catalog:",
|
"@clerc/plugin-friendly-error": "catalog:",
|
||||||
"@clerc/plugin-help": "catalog:",
|
"@clerc/plugin-help": "catalog:",
|
||||||
|
|
@ -180,7 +181,6 @@
|
||||||
"blurhash": "catalog:",
|
"blurhash": "catalog:",
|
||||||
"bullmq": "catalog:",
|
"bullmq": "catalog:",
|
||||||
"chalk": "catalog:",
|
"chalk": "catalog:",
|
||||||
"@clerc/core": "catalog:",
|
|
||||||
"confbox": "catalog:",
|
"confbox": "catalog:",
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"feed": "catalog:",
|
"feed": "catalog:",
|
||||||
|
|
@ -216,5 +216,8 @@
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
"zod-openapi": "catalog:",
|
"zod-openapi": "catalog:",
|
||||||
"zod-validation-error": "catalog:"
|
"zod-validation-error": "catalog:"
|
||||||
|
},
|
||||||
|
"patchedDependencies": {
|
||||||
|
"bun-bagel@1.2.0": "patches/bun-bagel@1.2.0.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,7 @@ export default apiRoute((app) =>
|
||||||
throw new ApiError(400, "Cannot refetch a local user");
|
throw new ApiError(400, "Cannot refetch a local user");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUser = await User.fromVersia(
|
const newUser = await User.fromVersia(otherUser.reference);
|
||||||
otherUser.reference,
|
|
||||||
otherUser.reference.domain as string,
|
|
||||||
);
|
|
||||||
|
|
||||||
return context.json(newUser.toApi(false), 200);
|
return context.json(newUser.toApi(false), 200);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,7 @@ export default apiRoute((app) =>
|
||||||
VersiaEntities.User,
|
VersiaEntities.User,
|
||||||
);
|
);
|
||||||
|
|
||||||
const foundAccount = await User.fromVersia(
|
const foundAccount = await User.fromVersia(accountData, instance);
|
||||||
accountData,
|
|
||||||
instance.data.baseUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
return context.json(foundAccount.toApi(), 200);
|
return context.json(foundAccount.toApi(), 200);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
const foundAccount = await User.fromVersia(
|
const foundAccount = await User.fromVersia(
|
||||||
accountData,
|
accountData,
|
||||||
instance.data.baseUrl,
|
instance,
|
||||||
);
|
);
|
||||||
|
|
||||||
accounts.push(foundAccount);
|
accounts.push(foundAccount);
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
const newUser = await User.fromVersia(
|
const newUser = await User.fromVersia(
|
||||||
accountData,
|
accountData,
|
||||||
instance.data.baseUrl,
|
instance,
|
||||||
);
|
);
|
||||||
|
|
||||||
return context.json(
|
return context.json(
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ export default apiRoute((app) => {
|
||||||
|
|
||||||
const jwtPayload = (await verify(state, config.authentication.key, {
|
const jwtPayload = (await verify(state, config.authentication.key, {
|
||||||
iss: config.http.base_url.toString(),
|
iss: config.http.base_url.toString(),
|
||||||
|
alg: "HS256",
|
||||||
})) as {
|
})) as {
|
||||||
flow: string;
|
flow: string;
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
|
|
|
||||||
25
packages/api/routes/versia/v0.6/inbox.test.ts
vendored
25
packages/api/routes/versia/v0.6/inbox.test.ts
vendored
|
|
@ -40,6 +40,17 @@ mock(new URL("/.well-known/versia", instanceUrl).href, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
|
data: {
|
||||||
|
versions: ["0.6.0"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
mock(new URL("/.versia/v0.6/instance", instanceUrl).href, {
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/vnd.versia+json; charset=utf-8",
|
||||||
|
},
|
||||||
data: new VersiaEntities.InstanceMetadata({
|
data: new VersiaEntities.InstanceMetadata({
|
||||||
type: "InstanceMetadata",
|
type: "InstanceMetadata",
|
||||||
name: "Versia",
|
name: "Versia",
|
||||||
|
|
@ -70,7 +81,7 @@ mock(new URL("/.well-known/versia", instanceUrl).href, {
|
||||||
mock(new URL(`/.versia/v0.6/entities/User/${userId}`, instanceUrl).href, {
|
mock(new URL(`/.versia/v0.6/entities/User/${userId}`, instanceUrl).href, {
|
||||||
response: {
|
response: {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/vnd.versia+json; charset=utf-8",
|
||||||
},
|
},
|
||||||
data: new VersiaEntities.User({
|
data: new VersiaEntities.User({
|
||||||
id: userId,
|
id: userId,
|
||||||
|
|
@ -132,7 +143,7 @@ describe("Inbox Tests", () => {
|
||||||
|
|
||||||
const signedRequest = await sign(
|
const signedRequest = await sign(
|
||||||
instanceKeys.privateKey,
|
instanceKeys.privateKey,
|
||||||
new URL(exampleNote.data.author),
|
instanceUrl,
|
||||||
new Request(inboxUrl, {
|
new Request(inboxUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -151,6 +162,8 @@ describe("Inbox Tests", () => {
|
||||||
body: signedRequest.body,
|
body: signedRequest.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(await response.text());
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
|
|
@ -174,7 +187,7 @@ describe("Inbox Tests", () => {
|
||||||
|
|
||||||
const signedRequest = await sign(
|
const signedRequest = await sign(
|
||||||
instanceKeys.privateKey,
|
instanceKeys.privateKey,
|
||||||
new URL(exampleRequest.data.author),
|
instanceUrl,
|
||||||
new Request(inboxUrl, {
|
new Request(inboxUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -227,7 +240,7 @@ describe("Inbox Tests", () => {
|
||||||
|
|
||||||
const signedRequest = await sign(
|
const signedRequest = await sign(
|
||||||
instanceKeys.privateKey,
|
instanceKeys.privateKey,
|
||||||
new URL(exampleRequest.data.author),
|
instanceUrl,
|
||||||
new Request(inboxUrl, {
|
new Request(inboxUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -322,7 +335,7 @@ describe("Inbox Tests", () => {
|
||||||
|
|
||||||
const signedRequest = await sign(
|
const signedRequest = await sign(
|
||||||
instanceKeys.privateKey,
|
instanceKeys.privateKey,
|
||||||
new URL(exampleRequest.data.author),
|
instanceUrl,
|
||||||
new Request(inboxUrl, {
|
new Request(inboxUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -402,7 +415,7 @@ describe("Inbox Tests", () => {
|
||||||
|
|
||||||
const signedRequest = await sign(
|
const signedRequest = await sign(
|
||||||
instanceKeys.privateKey,
|
instanceKeys.privateKey,
|
||||||
new URL(exampleRequest.data.author),
|
instanceUrl,
|
||||||
new Request(inboxUrl, {
|
new Request(inboxUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
2
packages/api/routes/versia/v0.6/inbox.ts
vendored
2
packages/api/routes/versia/v0.6/inbox.ts
vendored
|
|
@ -4,7 +4,7 @@ import z from "zod";
|
||||||
import { InboxJobType, inboxQueue } from "~/packages/kit/queues/inbox/queue";
|
import { InboxJobType, inboxQueue } from "~/packages/kit/queues/inbox/queue";
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) =>
|
||||||
app.get(
|
app.post(
|
||||||
"/.versia/v0.6/inbox",
|
"/.versia/v0.6/inbox",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
summary: "Instance inbox endpoint",
|
summary: "Instance inbox endpoint",
|
||||||
|
|
|
||||||
|
|
@ -941,20 +941,19 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
*/
|
*/
|
||||||
public static async resolve(
|
public static async resolve(
|
||||||
reference: VersiaEntities.Reference,
|
reference: VersiaEntities.Reference,
|
||||||
|
defaultInstance?: Instance,
|
||||||
): Promise<Note | null> {
|
): Promise<Note | null> {
|
||||||
// Check if note not already in database
|
// Check if note not already in database
|
||||||
if (
|
if (
|
||||||
!reference.domain ||
|
!(reference.domain || defaultInstance) ||
|
||||||
reference.domain === config.http.base_url.hostname
|
reference.domain === config.http.base_url.hostname
|
||||||
) {
|
) {
|
||||||
return await Note.fromId(reference.id);
|
return await Note.fromId(reference.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = await Instance.resolve(reference.domain);
|
const instance = reference.domain
|
||||||
|
? await Instance.resolve(reference.domain)
|
||||||
if (!instance) {
|
: (defaultInstance as Instance);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundNote = await Note.fromSql(
|
const foundNote = await Note.fromSql(
|
||||||
and(
|
and(
|
||||||
|
|
@ -963,7 +962,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
Notes.authorId,
|
Notes.authorId,
|
||||||
sql`(
|
sql`(
|
||||||
SELECT "Users".id FROM "Users"
|
SELECT "Users".id FROM "Users"
|
||||||
WHERE "Users".instanceId = ${instance.id}
|
WHERE "Users"."instanceId" = ${instance.id}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)`,
|
)`,
|
||||||
),
|
),
|
||||||
|
|
@ -974,7 +973,14 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
return foundNote;
|
return foundNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Note.fromVersia(reference);
|
return Note.fromVersia(
|
||||||
|
reference.domain
|
||||||
|
? reference
|
||||||
|
: new VersiaEntities.Reference(
|
||||||
|
reference.id,
|
||||||
|
instance.data.baseUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -983,28 +989,45 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
* If the note already exists, it will update it.
|
* If the note already exists, it will update it.
|
||||||
* @param versiaNote - Reference or Versia Note representation
|
* @param versiaNote - Reference or Versia Note representation
|
||||||
*/
|
*/
|
||||||
|
public static async fromVersia(
|
||||||
|
versiaNote: VersiaEntities.Note,
|
||||||
|
instance: Instance,
|
||||||
|
): Promise<Note>;
|
||||||
|
|
||||||
|
public static async fromVersia(
|
||||||
|
reference: VersiaEntities.Reference,
|
||||||
|
): Promise<Note>;
|
||||||
|
|
||||||
public static async fromVersia(
|
public static async fromVersia(
|
||||||
versiaNote: VersiaEntities.Note | VersiaEntities.Reference,
|
versiaNote: VersiaEntities.Note | VersiaEntities.Reference,
|
||||||
|
instance?: Instance,
|
||||||
): Promise<Note> {
|
): Promise<Note> {
|
||||||
if (versiaNote instanceof VersiaEntities.Reference) {
|
if (versiaNote instanceof VersiaEntities.Reference) {
|
||||||
|
if (!versiaNote.domain) {
|
||||||
|
throw new Error(
|
||||||
|
"Cannot fetch Versia note from reference without domain",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// No bridge support for notes yet
|
// No bridge support for notes yet
|
||||||
const note = await Instance.federationRequester.fetchEntity(
|
const note = await Instance.federationRequester.fetchEntity(
|
||||||
versiaNote,
|
versiaNote,
|
||||||
VersiaEntities.Note,
|
VersiaEntities.Note,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Note.fromVersia(note);
|
const instance = await Instance.resolve(versiaNote.domain);
|
||||||
|
|
||||||
|
return Note.fromVersia(note, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
throw new Error("Instance must be provided when fetching note");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { created_at, extensions, group, id, is_sensitive, subject } =
|
const { created_at, extensions, group, id, is_sensitive, subject } =
|
||||||
versiaNote.data;
|
versiaNote.data;
|
||||||
|
|
||||||
if (!versiaNote.author.domain) {
|
const author = await User.resolve(versiaNote.author, instance);
|
||||||
throw new Error("Entity author domain is missing");
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = await Instance.resolve(versiaNote.author.domain);
|
|
||||||
const author = await User.resolve(versiaNote.author);
|
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new Error("Entity author could not be resolved");
|
throw new Error("Entity author could not be resolved");
|
||||||
|
|
@ -1041,7 +1064,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
|
|
||||||
const mentions = (
|
const mentions = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
versiaNote.mentions.map((m) => User.resolve(m)) ?? [],
|
versiaNote.mentions.map((m) => User.resolve(m, instance)) ?? [],
|
||||||
)
|
)
|
||||||
).filter((m) => m !== null);
|
).filter((m) => m !== null);
|
||||||
|
|
||||||
|
|
@ -1052,10 +1075,10 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
: (group as "public" | "followers" | "unlisted");
|
: (group as "public" | "followers" | "unlisted");
|
||||||
|
|
||||||
const reply = versiaNote.repliesTo
|
const reply = versiaNote.repliesTo
|
||||||
? await Note.resolve(versiaNote.repliesTo)
|
? await Note.resolve(versiaNote.repliesTo, instance)
|
||||||
: null;
|
: null;
|
||||||
const quote = versiaNote.quotes
|
const quote = versiaNote.quotes
|
||||||
? await Note.resolve(versiaNote.quotes)
|
? await Note.resolve(versiaNote.quotes, instance)
|
||||||
: null;
|
: null;
|
||||||
const spoiler = subject ? await sanitizedHtmlStrip(subject) : undefined;
|
const spoiler = subject ? await sanitizedHtmlStrip(subject) : undefined;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -672,9 +672,18 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
* If the user already exists, it will update it.
|
* If the user already exists, it will update it.
|
||||||
* @param versiaUser Reference or Versia User representation
|
* @param versiaUser Reference or Versia User representation
|
||||||
*/
|
*/
|
||||||
|
public static async fromVersia(
|
||||||
|
versiaUser: VersiaEntities.User,
|
||||||
|
instance: Instance,
|
||||||
|
): Promise<User>;
|
||||||
|
|
||||||
|
public static async fromVersia(
|
||||||
|
versiaUser: VersiaEntities.Reference,
|
||||||
|
): Promise<User>;
|
||||||
|
|
||||||
public static async fromVersia(
|
public static async fromVersia(
|
||||||
versiaUser: VersiaEntities.User | VersiaEntities.Reference,
|
versiaUser: VersiaEntities.User | VersiaEntities.Reference,
|
||||||
domain: string,
|
instance?: Instance,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
if (versiaUser instanceof VersiaEntities.Reference) {
|
if (versiaUser instanceof VersiaEntities.Reference) {
|
||||||
if (!versiaUser.domain) {
|
if (!versiaUser.domain) {
|
||||||
|
|
@ -688,7 +697,13 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
VersiaEntities.User,
|
VersiaEntities.User,
|
||||||
);
|
);
|
||||||
|
|
||||||
return User.fromVersia(user, versiaUser.domain);
|
const instance = await Instance.resolve(versiaUser.domain);
|
||||||
|
|
||||||
|
return User.fromVersia(user, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
throw new Error("Instance must be provided when fetching user");
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -702,7 +717,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
extensions,
|
extensions,
|
||||||
} = versiaUser.data;
|
} = versiaUser.data;
|
||||||
|
|
||||||
const instance = await Instance.resolve(domain);
|
|
||||||
const existingUser = await User.fromSql(
|
const existingUser = await User.fromSql(
|
||||||
and(eq(Users.instanceId, instance.id), eq(Users.remoteId, id)),
|
and(eq(Users.instanceId, instance.id), eq(Users.remoteId, id)),
|
||||||
);
|
);
|
||||||
|
|
@ -788,10 +802,11 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
|
|
||||||
public static async resolve(
|
public static async resolve(
|
||||||
reference: VersiaEntities.Reference,
|
reference: VersiaEntities.Reference,
|
||||||
|
defaultInstance?: Instance,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
// Check if user not already in database
|
// Check if user not already in database
|
||||||
if (
|
if (
|
||||||
!reference.domain ||
|
!(reference.domain || defaultInstance) ||
|
||||||
reference.domain === config.http.base_url.hostname
|
reference.domain === config.http.base_url.hostname
|
||||||
) {
|
) {
|
||||||
const user = await User.fromId(reference.id);
|
const user = await User.fromId(reference.id);
|
||||||
|
|
@ -805,7 +820,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = await Instance.resolve(reference.domain);
|
const instance = reference.domain
|
||||||
|
? await Instance.resolve(reference.domain)
|
||||||
|
: (defaultInstance as Instance);
|
||||||
|
|
||||||
const foundUser = await User.fromSql(
|
const foundUser = await User.fromSql(
|
||||||
and(
|
and(
|
||||||
|
|
@ -818,7 +835,14 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
return foundUser;
|
return foundUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
return User.fromVersia(reference, reference.domain);
|
return User.fromVersia(
|
||||||
|
reference.domain
|
||||||
|
? reference
|
||||||
|
: new VersiaEntities.Reference(
|
||||||
|
reference.id,
|
||||||
|
instance.data.baseUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -182,32 +182,36 @@ export class InboxProcessor {
|
||||||
shouldCheckSignature && federationInboxLogger.debug`Signature is valid`;
|
shouldCheckSignature && federationInboxLogger.debug`Signature is valid`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// TODO: Rip out bridge code so this is never null
|
||||||
|
const instance = this.sender?.instance as Instance;
|
||||||
|
|
||||||
await new EntitySorter(this.body)
|
await new EntitySorter(this.body)
|
||||||
.on(VersiaEntities.Note, (n) => InboxProcessor.processNote(n))
|
.on(VersiaEntities.Note, (n) =>
|
||||||
|
InboxProcessor.processNote(n, instance),
|
||||||
|
)
|
||||||
.on(VersiaEntities.Follow, (f) =>
|
.on(VersiaEntities.Follow, (f) =>
|
||||||
InboxProcessor.processFollowRequest(f),
|
InboxProcessor.processFollowRequest(f, instance),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.FollowAccept, (f) =>
|
.on(VersiaEntities.FollowAccept, (f) =>
|
||||||
InboxProcessor.processFollowAccept(f),
|
InboxProcessor.processFollowAccept(f, instance),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.FollowReject, (f) =>
|
.on(VersiaEntities.FollowReject, (f) =>
|
||||||
InboxProcessor.processFollowReject(f),
|
InboxProcessor.processFollowReject(f, instance),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.Like, (l) =>
|
.on(VersiaEntities.Like, (l) =>
|
||||||
InboxProcessor.processLikeRequest(l),
|
InboxProcessor.processLikeRequest(l, instance),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.Delete, (d) =>
|
.on(VersiaEntities.Delete, (d) =>
|
||||||
InboxProcessor.processDelete(d),
|
InboxProcessor.processDelete(d, instance),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.User, (u) =>
|
.on(VersiaEntities.User, (u) =>
|
||||||
InboxProcessor.processUser(
|
InboxProcessor.processUser(u, instance),
|
||||||
u,
|
)
|
||||||
this.sender?.instance.data.baseUrl ?? "",
|
.on(VersiaEntities.Share, (s) =>
|
||||||
),
|
InboxProcessor.processShare(s, instance),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.Share, (s) => InboxProcessor.processShare(s))
|
|
||||||
.on(VersiaEntities.Reaction, (r) =>
|
.on(VersiaEntities.Reaction, (r) =>
|
||||||
InboxProcessor.processReaction(r),
|
InboxProcessor.processReaction(r, instance),
|
||||||
)
|
)
|
||||||
.sort(() => {
|
.sort(() => {
|
||||||
throw new ApiError(400, "Unknown entity type");
|
throw new ApiError(400, "Unknown entity type");
|
||||||
|
|
@ -225,9 +229,10 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processReaction(
|
private static async processReaction(
|
||||||
reaction: VersiaEntities.Reaction,
|
reaction: VersiaEntities.Reaction,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const author = await User.resolve(reaction.author);
|
const author = await User.resolve(reaction.author, sender);
|
||||||
const note = await Note.resolve(reaction.object);
|
const note = await Note.resolve(reaction.object, sender);
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new ApiError(404, "Author not found");
|
throw new ApiError(404, "Author not found");
|
||||||
|
|
@ -246,7 +251,10 @@ export class InboxProcessor {
|
||||||
* @param {VersiaNote} note - The Note entity to process.
|
* @param {VersiaNote} note - The Note entity to process.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private static async processNote(note: VersiaEntities.Note): Promise<void> {
|
private static async processNote(
|
||||||
|
note: VersiaEntities.Note,
|
||||||
|
sender: Instance,
|
||||||
|
): Promise<void> {
|
||||||
// If note has a blocked word
|
// If note has a blocked word
|
||||||
if (
|
if (
|
||||||
Object.values(note.content?.data ?? {})
|
Object.values(note.content?.data ?? {})
|
||||||
|
|
@ -262,7 +270,7 @@ export class InboxProcessor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Note.fromVersia(note);
|
await Note.fromVersia(note, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -274,7 +282,7 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processUser(
|
private static async processUser(
|
||||||
user: VersiaEntities.User,
|
user: VersiaEntities.User,
|
||||||
domain: string,
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (
|
if (
|
||||||
config.validation.filters.username.some((filter) =>
|
config.validation.filters.username.some((filter) =>
|
||||||
|
|
@ -303,7 +311,7 @@ export class InboxProcessor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await User.fromVersia(user, domain);
|
await User.fromVersia(user, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -314,9 +322,10 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processFollowRequest(
|
private static async processFollowRequest(
|
||||||
follow: VersiaEntities.Follow,
|
follow: VersiaEntities.Follow,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const author = await User.resolve(follow.author);
|
const author = await User.resolve(follow.author, sender);
|
||||||
const followee = await User.resolve(follow.followee);
|
const followee = await User.resolve(follow.followee, sender);
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new ApiError(404, "Author not found");
|
throw new ApiError(404, "Author not found");
|
||||||
|
|
@ -362,9 +371,10 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processFollowAccept(
|
private static async processFollowAccept(
|
||||||
followAccept: VersiaEntities.FollowAccept,
|
followAccept: VersiaEntities.FollowAccept,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const author = await User.resolve(followAccept.author);
|
const author = await User.resolve(followAccept.author, sender);
|
||||||
const follower = await User.resolve(followAccept.follower);
|
const follower = await User.resolve(followAccept.follower, sender);
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new ApiError(404, "Author not found");
|
throw new ApiError(404, "Author not found");
|
||||||
|
|
@ -397,9 +407,10 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processFollowReject(
|
private static async processFollowReject(
|
||||||
followReject: VersiaEntities.FollowReject,
|
followReject: VersiaEntities.FollowReject,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const author = await User.resolve(followReject.author);
|
const author = await User.resolve(followReject.author, sender);
|
||||||
const follower = await User.resolve(followReject.follower);
|
const follower = await User.resolve(followReject.follower, sender);
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new ApiError(404, "Author not found");
|
throw new ApiError(404, "Author not found");
|
||||||
|
|
@ -432,9 +443,10 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processShare(
|
private static async processShare(
|
||||||
share: VersiaEntities.Share,
|
share: VersiaEntities.Share,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const author = await User.resolve(share.author);
|
const author = await User.resolve(share.author, sender);
|
||||||
const sharedNote = await Note.resolve(share.shared);
|
const sharedNote = await Note.resolve(share.shared, sender);
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new ApiError(404, "Author not found");
|
throw new ApiError(404, "Author not found");
|
||||||
|
|
@ -455,10 +467,11 @@ export class InboxProcessor {
|
||||||
*/ // JS doesn't allow the use of `delete` as a variable name
|
*/ // JS doesn't allow the use of `delete` as a variable name
|
||||||
public static async processDelete(
|
public static async processDelete(
|
||||||
delete_: VersiaEntities.Delete,
|
delete_: VersiaEntities.Delete,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const toDelete = delete_.deleted;
|
const toDelete = delete_.deleted;
|
||||||
|
|
||||||
const author = await User.resolve(delete_.author);
|
const author = await User.resolve(delete_.author, sender);
|
||||||
|
|
||||||
switch (delete_.data.deleted_type) {
|
switch (delete_.data.deleted_type) {
|
||||||
case "Note": {
|
case "Note": {
|
||||||
|
|
@ -478,7 +491,7 @@ export class InboxProcessor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case "User": {
|
case "User": {
|
||||||
const userToDelete = await User.resolve(toDelete);
|
const userToDelete = await User.resolve(toDelete, sender);
|
||||||
|
|
||||||
if (!userToDelete) {
|
if (!userToDelete) {
|
||||||
throw new ApiError(404, "User to delete not found");
|
throw new ApiError(404, "User to delete not found");
|
||||||
|
|
@ -573,9 +586,10 @@ export class InboxProcessor {
|
||||||
*/
|
*/
|
||||||
private static async processLikeRequest(
|
private static async processLikeRequest(
|
||||||
like: VersiaEntities.Like,
|
like: VersiaEntities.Like,
|
||||||
|
sender: Instance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const author = await User.resolve(like.author);
|
const author = await User.resolve(like.author, sender);
|
||||||
const likedNote = await Note.resolve(like.liked);
|
const likedNote = await Note.resolve(like.liked, sender);
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new ApiError(404, "Author not found");
|
throw new ApiError(404, "Author not found");
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,9 @@ export const parseMentionsFromText = async (text: string): Promise<User[]> => {
|
||||||
VersiaEntities.User,
|
VersiaEntities.User,
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = await User.fromVersia(userEntity, url.hostname);
|
const instance = await Instance.resolve(url.hostname);
|
||||||
|
|
||||||
|
const user = await User.fromVersia(userEntity, instance);
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
finalList.push(user);
|
finalList.push(user);
|
||||||
|
|
|
||||||
14
packages/kit/tables/migrations/0054_good_madelyne_pryor.sql
Normal file
14
packages/kit/tables/migrations/0054_good_madelyne_pryor.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
ALTER TABLE "Likes" DROP CONSTRAINT "Likes_uri_unique";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Notes" DROP CONSTRAINT "Notes_uri_unique";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP CONSTRAINT "Users_uri_unique";--> statement-breakpoint
|
||||||
|
DROP INDEX "Users_uri_index";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Likes" ADD COLUMN "remote_id" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Notes" ADD COLUMN "remote_id" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Reaction" ADD COLUMN "remote_id" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" ADD COLUMN "remote_id" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Likes" DROP COLUMN "uri";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Notes" DROP COLUMN "uri";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP COLUMN "uri";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP COLUMN "endpoints";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP COLUMN "public_key";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP COLUMN "private_key";
|
||||||
2394
packages/kit/tables/migrations/meta/0054_snapshot.json
Normal file
2394
packages/kit/tables/migrations/meta/0054_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -379,6 +379,13 @@
|
||||||
"when": 1765422160004,
|
"when": 1765422160004,
|
||||||
"tag": "0053_lively_hellfire_club",
|
"tag": "0053_lively_hellfire_club",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 54,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1771983340896,
|
||||||
|
"tag": "0054_good_madelyne_pryor",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
patches/bun-bagel@1.2.0.patch
Normal file
11
patches/bun-bagel@1.2.0.patch
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
diff --git a/dist/index.js b/dist/index.js
|
||||||
|
index 801380e3811704fc199b80f884ec3ff87d5116f1..381de429cae24d254609c097dad8b52d8865c2cb 100644
|
||||||
|
--- a/dist/index.js
|
||||||
|
+++ b/dist/index.js
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
// @bun
|
||||||
|
-var $={method:"GET",data:null,headers:new Headers,response:{data:null,headers:new Headers,status:200}};function Q(z){let X=`${z.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,"[\\s\\S]*")}$`;return new RegExp(X)}var f=(z)=>(W)=>{let[J,X]=z,[V,Y]=W;if(!(J.toString()===V.toString()||J.match(V)))return!1;let x=X?.method||"GET",H=Y?.method||"GET";if(x.toLowerCase()!==H.toLowerCase())return!1;let G=new Headers(X?.headers),b=new Headers(Y?.headers),K=[...G.keys(),...b.keys()];for(let y of K){let N=G.get(y),g=b.get(y);if(N!==g)return!1}return!0},v=(z,W=$)=>{let{headers:J,data:X,response:V}=W,Y=V?.data??X,w=V?.headers??J,x=Y instanceof Blob||Y instanceof FormData?Y:new Blob([JSON.stringify(Y)]);return new Response(x,{headers:w,status:z})};var Z,P=!1,j=new Map,m=(z,W=$)=>{let J=z instanceof Request?z.url:z,X=J instanceof RegExp?J:new RegExp(Q(J.toString())),V=[...j.entries()].find(f([X.toString(),W]));if(process.env.VERBOSE){if(!V)console.debug("\x1B[1mRegistered mocked request\x1B[0m");else console.debug("\x1B[1mRequest already mocked\x1B[0m \x1B[2mupdated\x1B[0m");console.debug("\x1B[2mURL\x1B[0m",J),console.debug("\x1B[2mPath Pattern\x1B[0m",X),console.debug("\x1B[2mStatus\x1B[0m",W.response?.status||200),console.debug("\x1B[2mMethod\x1B[0m",W.method||"GET"),console.debug(`
|
||||||
|
+var $={method:"GET",data:null,headers:new Headers,response:{data:null,headers:new Headers,status:200}};function Q(z){let X=`${z.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,"[\\s\\S]*")}$`;return new RegExp(X)}var f=(z)=>(W)=>{let[J,X]=z,[V,Y]=W;if(!(J.toString()===V.toString()||J.match(V)))return!1;let x=X?.method||"GET",H=Y?.method||"GET";if(x.toLowerCase()!==H.toLowerCase())return!1;let G=new Headers(X?.headers),b=new Headers(Y?.headers),K=[...G.keys(),...b.keys()];for(let y of K){let N=G.get(y),g=b.get(y);if(g&&N!==g)return!1}return!0},v=(z,W=$)=>{let{headers:J,data:X,response:V}=W,Y=V?.data??X,w=V?.headers??J,x=Y instanceof Blob||Y instanceof FormData?Y:new Blob([JSON.stringify(Y)]);return new Response(x,{headers:w,status:z})};var Z,P=!1,j=new Map,m=(z,W=$)=>{let J=z instanceof Request?z.url:z,X=J instanceof RegExp?J:new RegExp(Q(J.toString())),V=[...j.entries()].find(f([X.toString(),W]));if(process.env.VERBOSE){if(!V)console.debug("\x1B[1mRegistered mocked request\x1B[0m");else console.debug("\x1B[1mRequest already mocked\x1B[0m \x1B[2mupdated\x1B[0m");console.debug("\x1B[2mURL\x1B[0m",J),console.debug("\x1B[2mPath Pattern\x1B[0m",X),console.debug("\x1B[2mStatus\x1B[0m",W.response?.status||200),console.debug("\x1B[2mMethod\x1B[0m",W.method||"GET"),console.debug(`
|
||||||
|
`)}if(!V)j.set(X,W);else return;if(!Z)Z=globalThis.fetch,globalThis.fetch=F;return!0},h=()=>{if(j.clear(),Z)globalThis.fetch=Z.bind({}),Z=void 0},F=async(z,W)=>{let J=z instanceof Request?z.url:z.toString(),X=z instanceof Request?z:new Request(J),V=[...j.entries()].find(f([J,W]));if(!V){if(P)return Promise.reject(v(404));return await Z(X,W)}if(process.env.VERBOSE)console.debug("\x1B[2mMocked fetch called\x1B[0m",J);if(V[1].throw)throw V[1].throw;let Y=V[1].response?.status||200;return v(Y,V[1])},I=()=>{P=!1},O=()=>{P=!0};export{m as mock,I as enableRealRequests,O as disableRealRequests,h as clearMocks};
|
||||||
|
|
||||||
|
//# debugId=6C205C9DF1DC0DBE64756E2164756E21
|
||||||
|
|
@ -57,7 +57,12 @@ export const applyToHono = (app: Hono<HonoEnv>): void => {
|
||||||
throw new ApiError(401, "Missing JWT cookie");
|
throw new ApiError(401, "Missing JWT cookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await verify(jwtCookie, config.authentication.key);
|
const result = await verify(jwtCookie, config.authentication.key, {
|
||||||
|
alg: "HS256",
|
||||||
|
iss: config.http.base_url.toString(),
|
||||||
|
}).catch(() => {
|
||||||
|
throw new ApiError(401, "Invalid JWT");
|
||||||
|
});
|
||||||
|
|
||||||
const { sub } = result;
|
const { sub } = result;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue