From c560d58f68278aa07fc5847517431cc47100b8e4 Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:49:20 +1000 Subject: [PATCH] random bullshit, go!! --- assets/schemas.json | Bin 18227560 -> 18458145 bytes package.json | 5 +- src/activitypub/Server.ts | 64 +++++++++++++++ src/activitypub/index.ts | 1 + .../routes/channel/#channel_id/index.ts | 30 +++++++ .../#channel_id/messages/#message_id/index.ts | 50 ++++++++++++ .../routes/channel/#channel_id/outbox.ts | 76 ++++++++++++++++++ src/activitypub/routes/user.ts | 36 +++++++++ src/activitypub/start.ts | 7 ++ src/activitypub/util/actor.ts | 0 src/activitypub/webfinger/index.ts | 63 +++++++++++++++ src/bundle/Server.ts | 18 +++-- src/util/config/Config.ts | 2 + .../config/types/FederationConfiguration.ts | 5 ++ .../schemas/responses/WebfingerResponse.ts | 12 +++ src/util/schemas/responses/index.ts | 3 +- tsconfig.json | 3 +- 17 files changed, 366 insertions(+), 9 deletions(-) create mode 100644 src/activitypub/Server.ts create mode 100644 src/activitypub/index.ts create mode 100644 src/activitypub/routes/channel/#channel_id/index.ts create mode 100644 src/activitypub/routes/channel/#channel_id/messages/#message_id/index.ts create mode 100644 src/activitypub/routes/channel/#channel_id/outbox.ts create mode 100644 src/activitypub/routes/user.ts create mode 100644 src/activitypub/start.ts create mode 100644 src/activitypub/util/actor.ts create mode 100644 src/activitypub/webfinger/index.ts create mode 100644 src/util/config/types/FederationConfiguration.ts create mode 100644 src/util/schemas/responses/WebfingerResponse.ts diff --git a/assets/schemas.json b/assets/schemas.json index 3891cee7668f40df7180c76ce32c0799cd05621a..ec0b51e9dec3dd70351ce5b75330f0326578e6ef 100644 GIT binary patch delta 13479 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 P%7@%Ddul~9^+*2$+UF?q delta 2705 zcmZveeO#1P7RNu=7vvdKpph2=nE_OCWJUtPDP;#ML2wi(!CZqS!(erRC>G;Q1|v>^ z1VS>nf@0!JEH>&UXYqyc1p&pENE9qFHy5d4TUY`i*I7F=^E`a&^VvVX=a1h#_uPBW zbMCp%{;LnIzrWC9ttnt%Oj51Dw`<%lbI=$yiI|LyQB$lvcr74r3Iya$#eh7k=LD^{ z!3ugCreSY`b-I-IYrkU+?RPkAzat8o%WF9z8^{q&#~jg&$^2X`X`({qj6&tiMy2gi zVfKjxTR4$mhbI#3Qw6PRr#)2ddeR2v_tEb&E0kb#Y*m?ncZ(n{;=a59CKk$X{wJ$1}S=MnQUB-A^@i$A~rR&Vg%`Yify zT@dS`w{sG+8=SWG=0YnFA6!7lP<(KqohLs6G(z}b?M?Z(6g9DaUvl8c6vQHQWWEn2 zyp|>Rg)I4Vm?ignO%CKo_|w?pZv#ypwCV2J#Y6yh*9JP9BaxPPEs$7(iPw}r3#sdV ztc32z&tv!F7vzGL?Y$JTy_aFOcaVpmiPr{!ckn;`0^Q0P@p1$v7lhN zzu-&*pg8^_DvrM-5ww=xm!PHhWo+qPQ6qfI9-9#8u?fW*m=n$bQHKXHGU{^#m5rexT*0u8iw59>A?7*BE(3icAcn$lq*Uz43 z32z>S%%kgx-(%*{4eNyD&@^j^=-oiPjuE}F7k(WNc5rdTA8-d3|0Z8QmQb6O@vvF> z25wev{Llw#6E_lXVr^o=o&~6Bd5ek|YZ8b}h&6Ay&IQtlL>fzq^x-|!#w1W1|A=bi z=1Liq$+i%G!ZKO%kp+BGbn(fci%&sad}@_cAAe>h>*S!dB$ap@TT9Y9B!XynDh=#T zy@PhA-gUQlUgh>>i>;VQdondVL91Wz>U8kzf0sx{&;CE3w9KSxix6iJTQNfX-eRi2 zuTEd(z^HnU$V8(m%OJFfxU?*YOUuT%w4Bp|U&Y=WsMwo}6??boC57j=6`s#)Jg>18 z>Y;E<1BGMxSU9#_?ExCs_lW}3xPBm(lYO5qjeYb7L?PNo!x;%3eG8pWoe#u^sLuar zz7(W&9}z_;tt(EFo6Q`TL(rO?`^aJWIEq z_!MX94oqm}cMm}M-8wA4doa=)$d5Qo{ztrg`0|jcj(|+{8Ol`kaxbAbb*0opSIS?p zE2Y6sXn7lI8el`sQQT1Tc^6;baOm>qgboi~J{B(cC5|71#PQ>pIDVpnzd4X5`y25E zrpZpm2zO&>J5JRu{LaBa>#N(pL|dk73e~Q8#LdwTrZ~E4@25vNR@mCFt4Qf~+Beuf zlTFhe3!B7&{E3oy9_3G7=yvCiK#^c*``>YBd(&lMXTG{7@YS6{U)||Xr9c%)oT17* zJ4eOWr;~d#IJxW5$^D!y^;&by)`p#f+OYFj8+JkUtVyG^#%;SmT*SC-EiVdwF7++o zQhy0u>RTfO?ZK^9cyOx?AKYqRBb?FPXd2jMXkb^cfpzRK2c-*hEU<&PipK)4d6@%T z;VLwo2%x78cBF>V&+euQ0FaW~88n_1%Q9zAg;w zyA>j6oc(R!?7NY(?^!8m9m{&4W7!?-SoZaVR+98Jlq7wFB}sRqB~Vs$m*~Z^qI*9r z;?rYny$8nDZ_(I#e`1z?_&(@||AG49?_L(7=ej?DT=#!su6y6aE`q}QASnDH28H*( zJjj&9!0O}N91RDI}B})z|b~^ zhPH8u@Mh8H1I_5qs2P1+#=j?)&Ry*JUnur`g2kT3RSHW0%Rqhc)8hJKrYG3c-#j-_ zu||lB{U1ifKE3C`cid7%6Jum1F}Yet4+ zdf#9XbCfim-$rWl77-(3!}QHtrZakk&3^^jX$P&GELThMhOKLbW~y9o+cuN+v;T9E zN=?rK8Cym>vtu7Gw4AAQ(}*1-W6$)PMxJ3tNvY|thRiHRox*|b;@_B3x77}ej3d*# ht#)Gc{Ly1ZJB!J5Vq~0|J|=TETW?O`1#Xfk_ZNWr^4$Oc diff --git a/package.json b/package.json index 70e4ae849..d2c68a77c 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,8 @@ "@spacebar/api": "dist/api", "@spacebar/cdn": "dist/cdn", "@spacebar/gateway": "dist/gateway", - "@spacebar/util": "dist/util" + "@spacebar/util": "dist/util", + "@spacebar/ap": "dist/activitypub" }, "optionalDependencies": { "erlpack": "^0.1.4", @@ -122,4 +123,4 @@ "nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport", "sqlite3": "^5.1.6" } -} +} \ No newline at end of file diff --git a/src/activitypub/Server.ts b/src/activitypub/Server.ts new file mode 100644 index 000000000..492d43b65 --- /dev/null +++ b/src/activitypub/Server.ts @@ -0,0 +1,64 @@ +import { BodyParser, CORS, ErrorHandler } from "@spacebar/api"; +import { + Config, + JSONReplacer, + initDatabase, + registerRoutes, +} from "@spacebar/util"; +import { Request, Response, Router } from "express"; +import { Server, ServerOptions } from "lambert-server"; +import path from "path"; +import webfinger from "./webfinger"; + +export class APServer extends Server { + public declare options: ServerOptions; + + constructor(opts?: Partial) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + super({ ...opts, errorHandler: false, jsonBody: false }); + } + + async start() { + await initDatabase(); + await Config.init(); + + this.app.set("json replacer", JSONReplacer); + + this.app.use(CORS); + this.app.use(BodyParser({ inflate: true, limit: "10mb" })); + + const api = Router(); + const app = this.app; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // lambert server is lame + this.app = api; + + this.routes = await registerRoutes( + this, + path.join(__dirname, "routes", "/"), + ); + + api.use("*", (req: Request, res: Response) => { + res.status(404).json({ + message: "404 endpoint not found", + code: 0, + }); + }); + + this.app = app; + + this.app.use("/fed", api); + this.app.get("/fed", (req, res) => { + res.json({ ping: "pong" }); + }); + + this.app.use("/.well-known/webfinger", webfinger); + + this.app.use(ErrorHandler); + + return super.start(); + } +} diff --git a/src/activitypub/index.ts b/src/activitypub/index.ts new file mode 100644 index 000000000..7513bd2fd --- /dev/null +++ b/src/activitypub/index.ts @@ -0,0 +1 @@ +export * from "./Server"; diff --git a/src/activitypub/routes/channel/#channel_id/index.ts b/src/activitypub/routes/channel/#channel_id/index.ts new file mode 100644 index 000000000..95495ffe0 --- /dev/null +++ b/src/activitypub/routes/channel/#channel_id/index.ts @@ -0,0 +1,30 @@ +import { route } from "@spacebar/api"; +import { Channel, Config } from "@spacebar/util"; +import { Request, Response, Router } from "express"; + +const router = Router(); +export default router; + +router.get("/", route({}), async (req: Request, res: Response) => { + const id = req.params.id; + + const channel = await Channel.findOneOrFail({ where: { id } }); + + const { webDomain } = Config.get().federation; + + return res.json({ + "@context": "https://www.w3.org/ns/activitystreams", + type: "Group", + id: `https://${webDomain}/fed/channel/${channel.id}`, + name: channel.name, + preferredUsername: channel.name, + summary: channel.topic, + icon: undefined, + + inbox: `https://${webDomain}/fed/channel/${channel.id}/inbox`, + outbox: `https://${webDomain}/fed/channel/${channel.id}/outbox`, + followers: `https://${webDomain}/fed/channel/${channel.id}/followers`, + following: `https://${webDomain}/fed/channel/${channel.id}/following`, + linked: `https://${webDomain}/fed/channel/${channel.id}/likeds`, + }); +}); diff --git a/src/activitypub/routes/channel/#channel_id/messages/#message_id/index.ts b/src/activitypub/routes/channel/#channel_id/messages/#message_id/index.ts new file mode 100644 index 000000000..6b8060879 --- /dev/null +++ b/src/activitypub/routes/channel/#channel_id/messages/#message_id/index.ts @@ -0,0 +1,50 @@ +import { route } from "@spacebar/api"; +import { Config, Message } from "@spacebar/util"; +import { Request, Response, Router } from "express"; + +const router = Router(); +export default router; + +router.get("/", route({}), async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: { author: true, guild: true }, + }); + const { webDomain } = Config.get().federation; + + return res.json({ + "@context": "https://www.w3.org/ns/activitystreams", + id: "Announce", + actor: `https://${webDomain}/fed/user/${message.author!.id}`, + published: message.timestamp, + to: ["https://www.w3.org/ns/activitystreams#Public"], + cc: [ + message.author?.id + ? `https://${webDomain}/fed/users/${message.author.id}` + : undefined, + `https://${webDomain}/fed/channel/${channel_id}/followers`, + ], + object: { + id: `https://${webDomain}/fed/channel/${channel_id}/mesages/${message.id}`, + type: "Note", + summary: null, + inReplyTo: undefined, // TODO + published: message.timestamp, + url: `https://app.spacebar.chat/channels${ + message.guild?.id ? `/${message.guild.id}` : "" + }/${channel_id}/${message.id}`, + attributedTo: `https://${webDomain}/fed/user/${message.author!.id}`, + to: ["https://www.w3.org/ns/activitystreams#Public"], + cc: [ + message.author?.id + ? `https://${webDomain}/fed/users/${message.author.id}` + : undefined, + `https://${webDomain}/fed/channel/${channel_id}/followers`, + ], + sensitive: false, + content: message.content, + }, + }); +}); diff --git a/src/activitypub/routes/channel/#channel_id/outbox.ts b/src/activitypub/routes/channel/#channel_id/outbox.ts new file mode 100644 index 000000000..03a312538 --- /dev/null +++ b/src/activitypub/routes/channel/#channel_id/outbox.ts @@ -0,0 +1,76 @@ +import { route } from "@spacebar/api"; +import { Config, Message, Snowflake } from "@spacebar/util"; +import { Router } from "express"; +import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm"; + +const router = Router(); +export default router; + +router.get("/", route({}), async (req, res) => { + // TODO: authentication + + const { channel_id } = req.params; + const { page, min_id, max_id } = req.query; + + const { webDomain } = Config.get().federation; + + if (!page) + return res.json({ + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${webDomain}/fed/users/${channel_id}/outbox`, + type: "OrderedCollection", + first: `https://${webDomain}/fed/users/${channel_id}/outbox?page=true`, + last: `https://${webDomain}/fed/users/${channel_id}/outbox?page=true&min_id=0`, + }); + + const after = min_id ? `${min_id}` : undefined; + const before = max_id ? `${max_id}` : undefined; + + const query: FindManyOptions & { + where: { id?: FindOperator | FindOperator[] }; + } = { + order: { timestamp: "DESC" }, + take: 20, + where: { channel_id: channel_id }, + relations: ["author"], + }; + + if (after) { + if (BigInt(after) > BigInt(Snowflake.generate())) + return res.status(422); + query.where.id = MoreThan(after); + } else if (before) { + if (BigInt(before) > BigInt(Snowflake.generate())) + return res.status(422); + query.where.id = LessThan(before); + } + + const messages = await Message.find(query); + + return res.json({ + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true`, + type: "OrderedCollection", + next: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true&max_id=${ + messages[0]?.id || "0" + }`, + prev: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true&max_id=${ + messages[messages.length - 1]?.id || "0" + }`, + partOf: `https://${webDomain}/fed/channel/${channel_id}/outbox`, + orderedItems: messages.map((message) => ({ + id: `https://${webDomain}/fed/channel/${channel_id}/message/${message.id}`, + type: "Announce", // hmm + actor: `https://${webDomain}/fed/channel/${channel_id}`, + published: message.timestamp, + to: ["https://www.w3.org/ns/activitystreams#Public"], + cc: [ + message.author?.id + ? `https://${webDomain}/fed/users/${message.author.id}` + : undefined, + `https://${webDomain}/fed/channel/${channel_id}/followers`, + ], + object: `https://${webDomain}/fed/channel/${channel_id}/messages/${message.id}`, + })), + }); +}); diff --git a/src/activitypub/routes/user.ts b/src/activitypub/routes/user.ts new file mode 100644 index 000000000..838d14b70 --- /dev/null +++ b/src/activitypub/routes/user.ts @@ -0,0 +1,36 @@ +import { route } from "@spacebar/api"; +import { Config, User } from "@spacebar/util"; +import { Request, Response, Router } from "express"; + +const router = Router(); +export default router; + +router.get("/:id", route({}), async (req: Request, res: Response) => { + const id = req.params.name; + + const user = await User.findOneOrFail({ where: { id } }); + + const { webDomain } = Config.get().federation; + + return res.json({ + "@context": "https://www.w3.org/ns/activitystreams", + type: "Person", + id: `https://${webDomain}/fed/user/${user.id}`, + name: user.username, + preferredUsername: user.username, + summary: user.bio, + icon: user.avatar + ? [ + `${Config.get().cdn.endpointPublic}/avatars/${user.id}/${ + user.avatar + }`, + ] + : undefined, + + inbox: `https://${webDomain}/fed/user/${user.id}/inbox`, + outbox: `https://${webDomain}/fed/user/${user.id}/outbox`, + followers: `https://${webDomain}/fed/user/${user.id}/followers`, + following: `https://${webDomain}/fed/user/${user.id}/following`, + linked: `https://${webDomain}/fed/user/${user.id}/likeds`, + }); +}); diff --git a/src/activitypub/start.ts b/src/activitypub/start.ts new file mode 100644 index 000000000..3f28fa42c --- /dev/null +++ b/src/activitypub/start.ts @@ -0,0 +1,7 @@ +require("module-alias/register"); +import "dotenv/config"; +import { APServer } from "./Server"; + +const port = Number(process.env.PORT) || 3005; +const server = new APServer({ port }); +server.start().catch(console.error); diff --git a/src/activitypub/util/actor.ts b/src/activitypub/util/actor.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/activitypub/webfinger/index.ts b/src/activitypub/webfinger/index.ts new file mode 100644 index 000000000..0b82e1039 --- /dev/null +++ b/src/activitypub/webfinger/index.ts @@ -0,0 +1,63 @@ +import { route } from "@spacebar/api"; +import { Channel, Config, User, WebfingerResponse } from "@spacebar/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; + +const router = Router(); +export default router; + +router.get( + "/", + route({ + query: { + resource: { + type: "string", + description: "Resource to locate", + }, + }, + responses: { + 200: { + body: "WebfingerResponse", + }, + }, + }), + async (req: Request, res: Response) => { + let resource = req.query.resource as string | undefined; + if (!resource) throw new HTTPError("Must specify resource"); + + // we know what you mean, bro + resource = resource.replace("acct:", ""); + + const [resourceId, resourceDomain] = resource.split("@"); + + const { webDomain } = Config.get().federation; + if (resourceDomain != webDomain) + throw new HTTPError("Resource could not be found", 404); + + const found = + (await User.findOne({ + where: { id: resourceId }, + select: ["id"], + })) || + (await Channel.findOne({ + where: { id: resourceId }, + select: ["id"], + })); + + if (!found) throw new HTTPError("Resource could not be found", 404); + + const type = found instanceof Channel ? "channel" : "user"; + + return res.json({ + subject: `acct:${resourceId}@${webDomain}`, // mastodon always returns acct so might as well + aliases: [`https://${webDomain}/fed/${type}/${resourceId}`], + links: [ + { + rel: "self", + type: "application/activity+json", + href: `https://${webDomain}/fed/${type}/${resourceId}`, + }, + ], + }); + }, +); diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts index d281120d1..d5e2d6def 100644 --- a/src/bundle/Server.ts +++ b/src/bundle/Server.ts @@ -19,13 +19,14 @@ process.on("unhandledRejection", console.error); process.on("uncaughtException", console.error); -import http from "http"; +import { APServer } from "@spacebar/ap"; import * as Api from "@spacebar/api"; -import * as Gateway from "@spacebar/gateway"; import { CDNServer } from "@spacebar/cdn"; +import * as Gateway from "@spacebar/gateway"; +import { Config, Sentry, initDatabase } from "@spacebar/util"; import express from "express"; -import { green, bold } from "picocolors"; -import { Config, initDatabase, Sentry } from "@spacebar/util"; +import http from "http"; +import { bold, green } from "picocolors"; const app = express(); const server = http.createServer(); @@ -36,12 +37,14 @@ server.on("request", app); const api = new Api.SpacebarServer({ server, port, production, app }); const cdn = new CDNServer({ server, port, production, app }); const gateway = new Gateway.Server({ server, port, production }); +const activitypub = new APServer({ server, port, production, app }); process.on("SIGTERM", async () => { console.log("Shutting down due to SIGTERM"); await gateway.stop(); await cdn.stop(); await api.stop(); + activitypub.stop(); server.close(); Sentry.close(); }); @@ -54,7 +57,12 @@ async function main() { await new Promise((resolve) => server.listen({ port }, () => resolve(undefined)), ); - await Promise.all([api.start(), cdn.start(), gateway.start()]); + await Promise.all([ + api.start(), + cdn.start(), + gateway.start(), + activitypub.start(), + ]); Sentry.errorHandler(app); diff --git a/src/util/config/Config.ts b/src/util/config/Config.ts index 90b98b7a2..0b3a41522 100644 --- a/src/util/config/Config.ts +++ b/src/util/config/Config.ts @@ -38,6 +38,7 @@ import { SentryConfiguration, TemplateConfiguration, } from "../config"; +import { FederationConfiguration } from "./types/FederationConfiguration"; export class ConfigValue { gateway: EndpointConfiguration = new EndpointConfiguration(); @@ -61,4 +62,5 @@ export class ConfigValue { email: EmailConfiguration = new EmailConfiguration(); passwordReset: PasswordResetConfiguration = new PasswordResetConfiguration(); + federation = new FederationConfiguration(); } diff --git a/src/util/config/types/FederationConfiguration.ts b/src/util/config/types/FederationConfiguration.ts new file mode 100644 index 000000000..b04388fd4 --- /dev/null +++ b/src/util/config/types/FederationConfiguration.ts @@ -0,0 +1,5 @@ +export class FederationConfiguration { + enabled: boolean = false; + localDomain: string | null = null; + webDomain: string | null = null; +} diff --git a/src/util/schemas/responses/WebfingerResponse.ts b/src/util/schemas/responses/WebfingerResponse.ts new file mode 100644 index 000000000..a3186a031 --- /dev/null +++ b/src/util/schemas/responses/WebfingerResponse.ts @@ -0,0 +1,12 @@ +interface WebfingerLink { + rel: string; + type: string; + href: string; + template?: string; +} + +export interface WebfingerResponse { + subject: string; + aliases: string[]; + links: WebfingerLink[]; +} diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts index d8b7fd575..66b9986b9 100644 --- a/src/util/schemas/responses/index.ts +++ b/src/util/schemas/responses/index.ts @@ -28,7 +28,8 @@ export * from "./TypedResponses"; export * from "./UpdatesResponse"; export * from "./UserNoteResponse"; export * from "./UserProfileResponse"; -export * from "./UserRelationshipsResponse"; export * from "./UserRelationsResponse"; +export * from "./UserRelationshipsResponse"; export * from "./WebAuthnCreateResponse"; +export * from "./WebfingerResponse"; export * from "./WebhookCreateResponse"; diff --git a/tsconfig.json b/tsconfig.json index 63b5e96cb..1ced0e3c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,7 +37,8 @@ "@spacebar/api*": ["./api"], "@spacebar/gateway*": ["./gateway"], "@spacebar/cdn*": ["./cdn"], - "@spacebar/util*": ["./util"] + "@spacebar/util*": ["./util"], + "@spacebar/ap*": ["./activitypub"] } /* Specify a set of entries that re-map imports to additional lookup locations. */, // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */