add webfinger and routes boilerplate
This commit is contained in:
parent
cad4c59d51
commit
9f74db9857
8 changed files with 589 additions and 17 deletions
|
@ -9,3 +9,10 @@ export const DATABASE_PORT = Number(process.env.DATABASE_PORT) || 5432;
|
|||
export const DATABASE_USER = process.env.DATABASE_USER || "postgres";
|
||||
export const DATABASE_PASS = process.env.DATABASE_PASS || "postgres";
|
||||
export const DATABASE_NAME = process.env.DATABASE_NAME || "postgres";
|
||||
|
||||
export const HTTPS = process.env.HTTPS === "true";
|
||||
export const DOMAIN = process.env.DOMAIN;
|
||||
|
||||
if (!DOMAIN) throw "$DOMAIN is empty";
|
||||
|
||||
export const BASE_URL = `${HTTPS ? "https" : "http"}://${DOMAIN}`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as crypto from "node:crypto";
|
||||
import * as util from "node:util";
|
||||
import { promisify }from "node:util";
|
||||
|
||||
const generate = util.promisify(crypto.generateKeyPair);
|
||||
const generate = promisify(crypto.generateKeyPair);
|
||||
|
||||
export async function generateKeyPair() {
|
||||
return await generate("rsa", {
|
||||
|
|
31
src/routes.ts
Normal file
31
src/routes.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { glob } from "glob";
|
||||
import type { FastifyInstance, RouteOptions } from "fastify";
|
||||
|
||||
import log from "./log.js";
|
||||
|
||||
export default async function getRoutes() {
|
||||
const rootDir = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const routes: RouteOptions[] = [];
|
||||
const matches = await glob(join(rootDir, "/routes/**/*.{js,ts}"));
|
||||
|
||||
for (const filename of matches) {
|
||||
try {
|
||||
const mod = await import(filename);
|
||||
routes.push(mod.default as RouteOptions);
|
||||
} catch (e) {
|
||||
log.error("Importing route %s", filename, e);
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
export function mountRoutes(app: FastifyInstance, routes: RouteOptions[]) {
|
||||
for (const route of routes) {
|
||||
log.trace("Mounting route %s %s", route.method, route.url);
|
||||
app.route(route);
|
||||
}
|
||||
}
|
80
src/routes/well-known/webfinger.ts
Normal file
80
src/routes/well-known/webfinger.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import type { FastifyReply, FastifyRequest, RouteOptions } from "fastify";
|
||||
import { IsNull } from "typeorm";
|
||||
|
||||
import log from "../../log.js";
|
||||
import MercuryDataSource from "../../db/index.js";
|
||||
import { Blog } from "../../db/entities/blog.js";
|
||||
import { BASE_URL } from "../../config.js";
|
||||
|
||||
const route: RouteOptions = {
|
||||
method: "GET",
|
||||
url: "/.well-known/webfinger",
|
||||
handler: async (req, res) => {
|
||||
// TypeScript complains if we just use plain `req.query` :(
|
||||
const encodedResource = (req.query as { resource: string }).resource;
|
||||
|
||||
if (!encodedResource || typeof encodedResource !== "string") {
|
||||
res.status(400).send({
|
||||
error: "resource query parameter is missing or invalid",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const resource = decodeURIComponent(encodedResource);
|
||||
|
||||
log.debug("Handling WebFinger request for %s", resource);
|
||||
|
||||
if (resource.startsWith("acct:")) {
|
||||
await handleAcctWebfinger(req, res, resource);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).send({ error: "Unhandled WebFinger resource type" });
|
||||
},
|
||||
};
|
||||
|
||||
export default route;
|
||||
|
||||
async function handleAcctWebfinger(
|
||||
req: FastifyRequest,
|
||||
res: FastifyReply,
|
||||
resource: string
|
||||
): Promise<void> {
|
||||
const [username, domain] = resource.slice("acct:".length).split("@");
|
||||
if (domain !== process.env.DOMAIN) {
|
||||
res.status(404).send({
|
||||
error: "Account not found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const blog = await MercuryDataSource.getRepository(Blog).findOneBy({
|
||||
username: username,
|
||||
host: IsNull(),
|
||||
});
|
||||
if (!blog) {
|
||||
res.status(404).send({
|
||||
error: "Account not found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({
|
||||
subject: resource,
|
||||
aliases: [
|
||||
`${BASE_URL}/@${blog.username}`,
|
||||
`${BASE_URL}/blogs/${blog.username}`,
|
||||
],
|
||||
links: [
|
||||
{
|
||||
rel: "http://webfinger.net/rel/profile-page",
|
||||
type: "text/html",
|
||||
href: `${BASE_URL}/@${blog.username}`,
|
||||
},
|
||||
{
|
||||
rel: "self",
|
||||
type: "application/activity+json",
|
||||
href: `${BASE_URL}/blogs/${blog.username}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
22
src/start.ts
22
src/start.ts
|
@ -1,9 +1,10 @@
|
|||
import "reflect-metadata"; // Required for TypeORM
|
||||
import express from "express";
|
||||
import Fastify from "fastify";
|
||||
|
||||
import MercuryDataSource from "./db/index.js";
|
||||
import log from "./log.js";
|
||||
import { Blog } from "./db/entities/blog.js";
|
||||
import { PORT } from "./config.js";
|
||||
import getRoutes, { mountRoutes } from "./routes.js";
|
||||
|
||||
export default async function start() {
|
||||
log.info("Initializing database");
|
||||
|
@ -17,17 +18,12 @@ export default async function start() {
|
|||
return;
|
||||
}
|
||||
|
||||
log.debug("Setting up routes")
|
||||
const app = express();
|
||||
log.debug("Setting up routes");
|
||||
const app = Fastify();
|
||||
|
||||
app.get("/", async (_, res) => {
|
||||
const blogRepository = MercuryDataSource.getRepository(Blog);
|
||||
const routes = await getRoutes();
|
||||
mountRoutes(app, routes);
|
||||
|
||||
const resp = await blogRepository.find();
|
||||
|
||||
return res.json(resp)
|
||||
})
|
||||
|
||||
log.info("Listening on port %d", PORT)
|
||||
app.listen(PORT);
|
||||
log.info("Listening on port %d", PORT);
|
||||
await app.listen({ port: PORT });
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue