withdrawal replacements patch

This commit is contained in:
nelle 2024-06-17 13:02:29 -06:00
parent 50eb66e375
commit 98f3f55371
8 changed files with 110 additions and 62 deletions

View file

@ -14,6 +14,7 @@ import { StatusError } from "@/misc/fetch.js";
import { shouldSkipInstance } from "@/misc/skipped-instances.js";
import type { DeliverJobData } from "@/queue/types.js";
import type Bull from "bull";
import { patchText, shouldPatchText } from "@/remote/activitypub/renderer/note.js";
const logger = new Logger("deliver");
@ -30,10 +31,33 @@ export default async (job: Bull.Job<DeliverJobData>) => {
logger.debug(`delivering ${latest}`);
}
let i = undefined;
if (
["Create", "Update"].includes(job.data.content.type)
&& job.data.content.object.type === "Note"
) {
const obj = job.data.content.object;
const patchSrcContent = shouldPatchText(obj.source.content);
if (patchSrcContent) {
i = await registerOrFetchInstanceDoc(host);
if (shouldPatchText(obj.content))
obj.content = patchText(obj.content, i);
if (shouldPatchText(obj._misskey_content))
obj._misskey_content = patchText(obj._misskey_content, i);
if (patchSrcContent)
obj.source.content = patchText(obj.source.content, i);
}
}
await request(job.data.user, job.data.to, job.data.content);
(async () => {
if (i === undefined) {
i = await registerOrFetchInstanceDoc(host);
}
// Update stats
registerOrFetchInstanceDoc(host).then((i) => {
Instances.update(i.id, {
latestRequestSentAt: new Date(),
latestStatus: 200,
@ -46,7 +70,7 @@ export default async (job: Bull.Job<DeliverJobData>) => {
instanceChart.requestSent(i.host, true);
apRequestChart.deliverSucc();
federationChart.deliverd(i.host, true);
});
})();
return "Success";
} catch (res) {

View file

@ -28,24 +28,24 @@ export async function hasSignature(req: IncomingMessage): Promise<string> {
return required ? "supplied" : "unneeded";
}
export async function checkFetch(req: IncomingMessage): Promise<number> {
export async function checkFetch(req: IncomingMessage): Promise<{ status: number; host?: string }> {
const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) {
if (req.headers.host !== config.host) return 400;
if (req.headers.host !== config.host) return { status: 400 };
let signature;
try {
signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"] });
} catch (e) {
return 401;
return { status: 401 };
}
const keyId = new URL(signature.keyId);
const host = toPuny(keyId.hostname);
if (await shouldBlockInstance(host, meta)) {
return 403;
return { status: 403 };
}
if (
@ -54,13 +54,13 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
host !== config.domain &&
!meta.allowedHosts.includes(host)
) {
return 403;
return { status: 403 };
}
const keyIdLower = signature.keyId.toLowerCase();
if (keyIdLower.startsWith("acct:")) {
// Old keyId is no longer supported.
return 401;
return { status: 401 };
}
const dbResolver = new DbResolver();
@ -77,23 +77,23 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
);
} catch (e) {
// できなければ駄目
return 403;
return { status: 403 };
}
}
// publicKey がなくても終了
if (authUser?.key == null) {
return 403;
return { status: 403 };
}
// Cannot authenticate against local user
if (authUser.user.uri === null || authUser.user.host === null) {
return 400;
return { status: 400 };
}
// Check if keyId hostname matches actor hostname
if (toPuny(new URL(authUser.user.uri).hostname) !== host) {
return 403;
return { status: 403 };
}
// HTTP-Signatureの検証
@ -107,7 +107,7 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
if (authUser.key == null) {
return 403;
return { status: 403 };
}
httpSignatureValidated = httpSignature.verifySignature(
@ -117,12 +117,12 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
}
if (!httpSignatureValidated) {
return 403;
return { status: 403 };
}
return verifySignature(signature, authUser.key) ? 200 : 401;
}
return 200;
return { status: 200 };
}
export async function getSignatureUser(req: IncomingMessage): Promise<{

View file

@ -10,11 +10,14 @@ import renderEmoji from "./emoji.js";
import renderMention from "./mention.js";
import renderHashtag from "./hashtag.js";
import renderDocument from "./document.js";
import { Instances } from "@/models/index.js";
import { Instance } from "@/models/entities/instance.js";
export default async function renderNote(
note: Note,
dive = true,
isTalk = false,
clientHost: string | undefined = undefined,
): Promise<Record<string, unknown>> {
const getPromisedFiles = async (ids: string[]) => {
if (!ids || ids.length === 0) return [];
@ -93,13 +96,17 @@ export default async function renderNote(
const files = await getPromisedFiles(note.fileIds);
const text = note.text ?? "";
let text = note.text ?? "";
let poll: Poll | null = null;
if (note.hasPoll) {
poll = await Polls.findOneBy({ noteId: note.id });
}
if (clientHost && shouldPatchText(text)) {
text = patchText(text, await Instances.findOneBy({ host: clientHost }));
}
let apText = text;
if (quote) {
@ -186,3 +193,18 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> {
return emojis.filter((emoji) => emoji != null) as Emoji[];
}
export function shouldPatchText(text: string): boolean {
return text.match(/\$INSTANCE\$(?:host|softwareName|softwareVersion|name|description)\$/) !== null;
}
export function patchText(
text: string,
target: Instance
): string {
text = text.replaceAll("$INSTANCE$host$", target.host);
text = text.replaceAll("$INSTANCE$softwareName$", target.softwareName || "softwareName");
text = text.replaceAll("$INSTANCE$softwareVersion$", target.softwareVersion || "softwareVersion");
text = text.replaceAll("$INSTANCE$name$", target.name || "name");
text = text.replaceAll("$INSTANCE$description$", target.description || "description");
return text;
}

View file

@ -110,9 +110,9 @@ router.post("/users/:user/inbox", parseJsonBodyOrFail, inbox);
router.get("/notes/:note", async (ctx, next) => {
if (!isActivityPubReq(ctx)) return await next();
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status, host } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -168,7 +168,7 @@ router.get("/notes/:note", async (ctx, next) => {
serverLogger.debug("Accepting: access criteria met");
}
ctx.body = renderActivity(await renderNote(note, false));
ctx.body = renderActivity(await renderNote(note, false, false, host));
const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) {
@ -181,9 +181,9 @@ router.get("/notes/:note", async (ctx, next) => {
// note activity
router.get("/notes/:note/activity", async (ctx) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status, host } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -199,7 +199,7 @@ router.get("/notes/:note/activity", async (ctx) => {
return;
}
ctx.body = renderActivity(await packActivity(note));
ctx.body = renderActivity(await packActivity(note, host));
const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
@ -233,9 +233,9 @@ router.get("/users/:user/publickey", async (ctx) => {
return;
}
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -293,10 +293,9 @@ router.get("/users/:user", async (ctx, next) => {
return;
}
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
return;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
}
const userId = ctx.params.user;
@ -319,9 +318,9 @@ router.get("/@:user", async (ctx, next) => {
return;
}
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -342,9 +341,9 @@ router.get("/actor", async (ctx, next) => {
// emoji
router.get("/emojis/:emoji", async (ctx) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -370,9 +369,9 @@ router.get("/emojis/:emoji", async (ctx) => {
// like
router.get("/likes/:like", async (ctx) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -404,9 +403,9 @@ router.get("/likes/:like", async (ctx) => {
router.get(
"/follows/:follower/:followee",
async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
// This may be used before the follow is completed, so we do not
@ -441,9 +440,9 @@ router.get(
// follow request
router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status
return;
}

View file

@ -10,9 +10,9 @@ import { setResponseType } from "../activitypub.js";
import type Router from "@koa/router";
export default async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}

View file

@ -14,9 +14,9 @@ import type { FindOptionsWhere } from "typeorm";
import type Router from "@koa/router";
export default async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}

View file

@ -14,9 +14,9 @@ import type { FindOptionsWhere } from "typeorm";
import type Router from "@koa/router";
export default async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}

View file

@ -17,9 +17,9 @@ import { setResponseType } from "../activitypub.js";
import type Router from "@koa/router";
export default async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
const { status, host } = await checkFetch(ctx.req);
if (status !== 200) {
ctx.status = status;
return;
}
@ -78,7 +78,7 @@ export default async (ctx: Router.RouterContext) => {
if (sinceId) notes.reverse();
const activities = await Promise.all(
notes.map((note) => packActivity(note)),
notes.map((note) => packActivity(note, host)),
);
const rendered = renderOrderedCollectionPage(
`${partOf}?${url.query({
@ -129,7 +129,10 @@ export default async (ctx: Router.RouterContext) => {
* Pack Create<Note> or Announce Activity
* @param note Note
*/
export async function packActivity(note: Note): Promise<any> {
export async function packActivity(
note: Note,
clientHost: string | undefined = undefined
): Promise<any> {
if (
note.renoteId &&
note.text == null &&
@ -144,5 +147,5 @@ export async function packActivity(note: Note): Promise<any> {
);
}
return renderCreate(await renderNote(note, false), note);
return renderCreate(await renderNote(note, false, false, clientHost), note);
}