Files
2026-07-26 18:25:37 +09:00

150 lines
4.9 KiB
JavaScript

import Router from "@koa/router";
import config from "../config/index.js";
import * as Acct from "../misc/acct.js";
import { links } from "./nodeinfo.js";
import { escapeAttribute, escapeValue } from "../prelude/xml.js";
import { Users } from "../models/index.js";
import { IsNull } from "typeorm";
// Init router
const router = new Router();
const XRD = (...x)=>`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">${x.map(({ element, value, attributes })=>`<${Object.entries(typeof attributes === "object" && attributes || {}).reduce((a, [k, v])=>`${a} ${k}="${escapeAttribute(v)}"`, element)}${typeof value === "string" ? `>${escapeValue(value)}</${element}` : "/"}>`).reduce((a, c)=>a + c, "")}</XRD>`;
const allPath = "/.well-known/{*splat}";
const webFingerPath = "/.well-known/webfinger";
const jrd = "application/jrd+json";
const xrd = "application/xrd+xml";
router.use(allPath, async (ctx, next)=>{
ctx.set({
"Access-Control-Allow-Headers": "Accept",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Origin": "*",
"Access-Control-Expose-Headers": "Vary"
});
await next();
});
router.options(allPath, async (ctx)=>{
ctx.status = 204;
});
router.get("/.well-known/host-meta", async (ctx)=>{
ctx.set("Content-Type", xrd);
ctx.body = XRD({
element: "Link",
attributes: {
rel: "lrdd",
type: xrd,
template: `${config.url}${webFingerPath}?resource={uri}`
}
});
});
router.get("/.well-known/host-meta.json", async (ctx)=>{
ctx.set("Content-Type", jrd);
ctx.body = {
links: [
{
rel: "lrdd",
type: jrd,
template: `${config.url}${webFingerPath}?resource={uri}`
}
]
};
});
if (config.twa != null) {
router.get("/.well-known/assetlinks.json", async (ctx)=>{
ctx.set("Content-Type", "application/json");
ctx.body = [
{
relation: [
"delegate_permission/common.handle_all_urls"
],
target: {
namespace: config.twa.nameSpace,
package_name: config.twa.packageName,
sha256_cert_fingerprints: config.twa.sha256CertFingerprints
}
}
];
});
}
router.get("/.well-known/nodeinfo", async (ctx)=>{
ctx.body = {
links
};
});
/* TODO
router.get('/.well-known/change-password', async ctx => {
});
*/ router.get(webFingerPath, async (ctx)=>{
const fromId = (id)=>({
id,
host: IsNull(),
isSuspended: false
});
const generateQuery = (resource)=>resource.startsWith(`${config.url.toLowerCase()}/users/`) ? fromId(resource.split("/").pop()) : fromAcct(Acct.parse(resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split("/").pop() : resource.startsWith("acct:") ? resource.slice("acct:".length) : resource));
const fromAcct = (acct)=>!acct.host || acct.host === config.host.toLowerCase() || acct.host === config.domain.toLowerCase() ? {
usernameLower: acct.username,
host: IsNull(),
isSuspended: false
} : 422;
if (typeof ctx.query.resource !== "string") {
ctx.status = 400;
return;
}
const query = generateQuery(ctx.query.resource.toLowerCase());
if (typeof query === "number") {
ctx.status = query;
return;
}
const user = await Users.findOneBy(query);
if (user == null) {
ctx.status = 404;
return;
}
const subject = `acct:${user.username}@${config.domain}`;
const self = {
rel: "self",
type: "application/activity+json",
href: `${config.url}/users/${user.id}`
};
const profilePage = {
rel: "http://webfinger.net/rel/profile-page",
type: "text/html",
href: `${config.url}/@${user.username}`
};
const subscribe = {
rel: "http://ostatus.org/schema/1.0/subscribe",
template: `${config.url}/authorize-follow?acct={uri}`
};
if (ctx.accepts(jrd, xrd) === xrd) {
ctx.body = XRD({
element: "Subject",
value: subject
}, {
element: "Link",
attributes: self
}, {
element: "Link",
attributes: profilePage
}, {
element: "Link",
attributes: subscribe
});
ctx.type = xrd;
} else {
ctx.body = {
subject,
links: [
self,
profilePage,
subscribe
]
};
ctx.type = jrd;
}
ctx.vary("Accept");
ctx.set("Cache-Control", "public, max-age=180");
});
// Return 404 for other .well-known
router.all(allPath, async (ctx)=>{
ctx.status = 404;
});
export default router;