mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-25 11:27:31 -07:00
withdrawal replacements patch
This commit is contained in:
parent
50eb66e375
commit
98f3f55371
8 changed files with 110 additions and 62 deletions
|
@ -14,6 +14,7 @@ import { StatusError } from "@/misc/fetch.js";
|
||||||
import { shouldSkipInstance } from "@/misc/skipped-instances.js";
|
import { shouldSkipInstance } from "@/misc/skipped-instances.js";
|
||||||
import type { DeliverJobData } from "@/queue/types.js";
|
import type { DeliverJobData } from "@/queue/types.js";
|
||||||
import type Bull from "bull";
|
import type Bull from "bull";
|
||||||
|
import { patchText, shouldPatchText } from "@/remote/activitypub/renderer/note.js";
|
||||||
|
|
||||||
const logger = new Logger("deliver");
|
const logger = new Logger("deliver");
|
||||||
|
|
||||||
|
@ -30,10 +31,33 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
logger.debug(`delivering ${latest}`);
|
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);
|
await request(job.data.user, job.data.to, job.data.content);
|
||||||
|
|
||||||
// Update stats
|
(async () => {
|
||||||
registerOrFetchInstanceDoc(host).then((i) => {
|
if (i === undefined) {
|
||||||
|
i = await registerOrFetchInstanceDoc(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update stats
|
||||||
Instances.update(i.id, {
|
Instances.update(i.id, {
|
||||||
latestRequestSentAt: new Date(),
|
latestRequestSentAt: new Date(),
|
||||||
latestStatus: 200,
|
latestStatus: 200,
|
||||||
|
@ -46,7 +70,7 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
instanceChart.requestSent(i.host, true);
|
instanceChart.requestSent(i.host, true);
|
||||||
apRequestChart.deliverSucc();
|
apRequestChart.deliverSucc();
|
||||||
federationChart.deliverd(i.host, true);
|
federationChart.deliverd(i.host, true);
|
||||||
});
|
})();
|
||||||
|
|
||||||
return "Success";
|
return "Success";
|
||||||
} catch (res) {
|
} catch (res) {
|
||||||
|
|
|
@ -28,24 +28,24 @@ export async function hasSignature(req: IncomingMessage): Promise<string> {
|
||||||
return required ? "supplied" : "unneeded";
|
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();
|
const meta = await fetchMeta();
|
||||||
if (meta.secureMode || meta.privateMode) {
|
if (meta.secureMode || meta.privateMode) {
|
||||||
if (req.headers.host !== config.host) return 400;
|
if (req.headers.host !== config.host) return { status: 400 };
|
||||||
|
|
||||||
let signature;
|
let signature;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"] });
|
signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"] });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 401;
|
return { status: 401 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyId = new URL(signature.keyId);
|
const keyId = new URL(signature.keyId);
|
||||||
const host = toPuny(keyId.hostname);
|
const host = toPuny(keyId.hostname);
|
||||||
|
|
||||||
if (await shouldBlockInstance(host, meta)) {
|
if (await shouldBlockInstance(host, meta)) {
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -54,13 +54,13 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
host !== config.domain &&
|
host !== config.domain &&
|
||||||
!meta.allowedHosts.includes(host)
|
!meta.allowedHosts.includes(host)
|
||||||
) {
|
) {
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyIdLower = signature.keyId.toLowerCase();
|
const keyIdLower = signature.keyId.toLowerCase();
|
||||||
if (keyIdLower.startsWith("acct:")) {
|
if (keyIdLower.startsWith("acct:")) {
|
||||||
// Old keyId is no longer supported.
|
// Old keyId is no longer supported.
|
||||||
return 401;
|
return { status: 401 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
|
@ -77,23 +77,23 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// できなければ駄目
|
// できなければ駄目
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// publicKey がなくても終了
|
// publicKey がなくても終了
|
||||||
if (authUser?.key == null) {
|
if (authUser?.key == null) {
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot authenticate against local user
|
// Cannot authenticate against local user
|
||||||
if (authUser.user.uri === null || authUser.user.host === null) {
|
if (authUser.user.uri === null || authUser.user.host === null) {
|
||||||
return 400;
|
return { status: 400 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if keyId hostname matches actor hostname
|
// Check if keyId hostname matches actor hostname
|
||||||
if (toPuny(new URL(authUser.user.uri).hostname) !== host) {
|
if (toPuny(new URL(authUser.user.uri).hostname) !== host) {
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
|
@ -107,7 +107,7 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
|
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
|
||||||
|
|
||||||
if (authUser.key == null) {
|
if (authUser.key == null) {
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSignatureValidated = httpSignature.verifySignature(
|
httpSignatureValidated = httpSignature.verifySignature(
|
||||||
|
@ -117,12 +117,12 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!httpSignatureValidated) {
|
if (!httpSignatureValidated) {
|
||||||
return 403;
|
return { status: 403 };
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifySignature(signature, authUser.key) ? 200 : 401;
|
return verifySignature(signature, authUser.key) ? 200 : 401;
|
||||||
}
|
}
|
||||||
return 200;
|
return { status: 200 };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSignatureUser(req: IncomingMessage): Promise<{
|
export async function getSignatureUser(req: IncomingMessage): Promise<{
|
||||||
|
|
|
@ -10,11 +10,14 @@ import renderEmoji from "./emoji.js";
|
||||||
import renderMention from "./mention.js";
|
import renderMention from "./mention.js";
|
||||||
import renderHashtag from "./hashtag.js";
|
import renderHashtag from "./hashtag.js";
|
||||||
import renderDocument from "./document.js";
|
import renderDocument from "./document.js";
|
||||||
|
import { Instances } from "@/models/index.js";
|
||||||
|
import { Instance } from "@/models/entities/instance.js";
|
||||||
|
|
||||||
export default async function renderNote(
|
export default async function renderNote(
|
||||||
note: Note,
|
note: Note,
|
||||||
dive = true,
|
dive = true,
|
||||||
isTalk = false,
|
isTalk = false,
|
||||||
|
clientHost: string | undefined = undefined,
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
const getPromisedFiles = async (ids: string[]) => {
|
const getPromisedFiles = async (ids: string[]) => {
|
||||||
if (!ids || ids.length === 0) return [];
|
if (!ids || ids.length === 0) return [];
|
||||||
|
@ -93,13 +96,17 @@ export default async function renderNote(
|
||||||
|
|
||||||
const files = await getPromisedFiles(note.fileIds);
|
const files = await getPromisedFiles(note.fileIds);
|
||||||
|
|
||||||
const text = note.text ?? "";
|
let text = note.text ?? "";
|
||||||
let poll: Poll | null = null;
|
let poll: Poll | null = null;
|
||||||
|
|
||||||
if (note.hasPoll) {
|
if (note.hasPoll) {
|
||||||
poll = await Polls.findOneBy({ noteId: note.id });
|
poll = await Polls.findOneBy({ noteId: note.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientHost && shouldPatchText(text)) {
|
||||||
|
text = patchText(text, await Instances.findOneBy({ host: clientHost }));
|
||||||
|
}
|
||||||
|
|
||||||
let apText = text;
|
let apText = text;
|
||||||
|
|
||||||
if (quote) {
|
if (quote) {
|
||||||
|
@ -186,3 +193,18 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> {
|
||||||
|
|
||||||
return emojis.filter((emoji) => emoji != null) as 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;
|
||||||
|
}
|
||||||
|
|
|
@ -110,9 +110,9 @@ router.post("/users/:user/inbox", parseJsonBodyOrFail, inbox);
|
||||||
router.get("/notes/:note", async (ctx, next) => {
|
router.get("/notes/:note", async (ctx, next) => {
|
||||||
if (!isActivityPubReq(ctx)) return await next();
|
if (!isActivityPubReq(ctx)) return await next();
|
||||||
|
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status, host } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ router.get("/notes/:note", async (ctx, next) => {
|
||||||
serverLogger.debug("Accepting: access criteria met");
|
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();
|
const meta = await fetchMeta();
|
||||||
if (meta.secureMode || meta.privateMode) {
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
@ -181,9 +181,9 @@ router.get("/notes/:note", async (ctx, next) => {
|
||||||
|
|
||||||
// note activity
|
// note activity
|
||||||
router.get("/notes/:note/activity", async (ctx) => {
|
router.get("/notes/:note/activity", async (ctx) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status, host } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ router.get("/notes/:note/activity", async (ctx) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(await packActivity(note));
|
ctx.body = renderActivity(await packActivity(note, host));
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
if (meta.secureMode || meta.privateMode) {
|
if (meta.secureMode || meta.privateMode) {
|
||||||
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
|
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
|
||||||
|
@ -233,9 +233,9 @@ router.get("/users/:user/publickey", async (ctx) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,10 +293,9 @@ router.get("/users/:user", async (ctx, next) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
@ -319,9 +318,9 @@ router.get("/@:user", async (ctx, next) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,9 +341,9 @@ router.get("/actor", async (ctx, next) => {
|
||||||
|
|
||||||
// emoji
|
// emoji
|
||||||
router.get("/emojis/:emoji", async (ctx) => {
|
router.get("/emojis/:emoji", async (ctx) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,9 +369,9 @@ router.get("/emojis/:emoji", async (ctx) => {
|
||||||
|
|
||||||
// like
|
// like
|
||||||
router.get("/likes/:like", async (ctx) => {
|
router.get("/likes/:like", async (ctx) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,9 +403,9 @@ router.get("/likes/:like", async (ctx) => {
|
||||||
router.get(
|
router.get(
|
||||||
"/follows/:follower/:followee",
|
"/follows/:follower/:followee",
|
||||||
async (ctx: Router.RouterContext) => {
|
async (ctx: Router.RouterContext) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// This may be used before the follow is completed, so we do not
|
// This may be used before the follow is completed, so we do not
|
||||||
|
@ -441,9 +440,9 @@ router.get(
|
||||||
|
|
||||||
// follow request
|
// follow request
|
||||||
router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => {
|
router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import { setResponseType } from "../activitypub.js";
|
||||||
import type Router from "@koa/router";
|
import type Router from "@koa/router";
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@ import type { FindOptionsWhere } from "typeorm";
|
||||||
import type Router from "@koa/router";
|
import type Router from "@koa/router";
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@ import type { FindOptionsWhere } from "typeorm";
|
||||||
import type Router from "@koa/router";
|
import type Router from "@koa/router";
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@ import { setResponseType } from "../activitypub.js";
|
||||||
import type Router from "@koa/router";
|
import type Router from "@koa/router";
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const verify = await checkFetch(ctx.req);
|
const { status, host } = await checkFetch(ctx.req);
|
||||||
if (verify !== 200) {
|
if (status !== 200) {
|
||||||
ctx.status = verify;
|
ctx.status = status;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ export default async (ctx: Router.RouterContext) => {
|
||||||
if (sinceId) notes.reverse();
|
if (sinceId) notes.reverse();
|
||||||
|
|
||||||
const activities = await Promise.all(
|
const activities = await Promise.all(
|
||||||
notes.map((note) => packActivity(note)),
|
notes.map((note) => packActivity(note, host)),
|
||||||
);
|
);
|
||||||
const rendered = renderOrderedCollectionPage(
|
const rendered = renderOrderedCollectionPage(
|
||||||
`${partOf}?${url.query({
|
`${partOf}?${url.query({
|
||||||
|
@ -129,7 +129,10 @@ export default async (ctx: Router.RouterContext) => {
|
||||||
* Pack Create<Note> or Announce Activity
|
* Pack Create<Note> or Announce Activity
|
||||||
* @param note Note
|
* @param note Note
|
||||||
*/
|
*/
|
||||||
export async function packActivity(note: Note): Promise<any> {
|
export async function packActivity(
|
||||||
|
note: Note,
|
||||||
|
clientHost: string | undefined = undefined
|
||||||
|
): Promise<any> {
|
||||||
if (
|
if (
|
||||||
note.renoteId &&
|
note.renoteId &&
|
||||||
note.text == null &&
|
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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue