From 294924fc494e9d72e040f0b4748268c095f766f5 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Thu, 1 May 2025 04:36:08 +0200 Subject: [PATCH] fix(api): :bug: Don't allow replying to reblogs --- api/api/v1/statuses/index.test.ts | 38 +++++++++++++++++++++++++++++++ api/api/v1/statuses/index.ts | 18 +++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/api/api/v1/statuses/index.test.ts b/api/api/v1/statuses/index.test.ts index bb1fa6a9..a1f7008a 100644 --- a/api/api/v1/statuses/index.test.ts +++ b/api/api/v1/statuses/index.test.ts @@ -208,6 +208,44 @@ describe("/api/v1/statuses", () => { }); }); + test("shouldn't allow replying to a reblog", async () => { + await using client = await generateClient(users[0]); + await using client2 = await generateClient(users[1]); + + const { data, ok } = await client.postStatus("Hello, world!"); + + expect(ok).toBe(true); + + const { data: reblog, ok: ok2 } = await client2.reblogStatus(data.id); + + expect(ok2).toBe(true); + + const { ok: ok3 } = await client.postStatus("Hello, world again!", { + in_reply_to_id: reblog.id, + }); + + expect(ok3).toBe(false); + }); + + test("shouldn't allow quoting a reblog", async () => { + await using client = await generateClient(users[0]); + await using client2 = await generateClient(users[1]); + + const { data, ok } = await client.postStatus("Hello, world!"); + + expect(ok).toBe(true); + + const { data: reblog, ok: ok2 } = await client2.reblogStatus(data.id); + + expect(ok2).toBe(true); + + const { ok: ok3 } = await client.postStatus("Hello, world again!", { + quote_id: reblog.id, + }); + + expect(ok3).toBe(false); + }); + describe("mentions testing", () => { test("should correctly parse @mentions", async () => { await using client = await generateClient(users[0]); diff --git a/api/api/v1/statuses/index.ts b/api/api/v1/statuses/index.ts index a2846842..aa00206e 100644 --- a/api/api/v1/statuses/index.ts +++ b/api/api/v1/statuses/index.ts @@ -163,21 +163,35 @@ export default apiRoute((app) => ); } + const reply = in_reply_to_id + ? await Note.fromId(in_reply_to_id) + : null; + // Check that in_reply_to_id and quote_id are real posts if provided - if (in_reply_to_id && !(await Note.fromId(in_reply_to_id))) { + if (in_reply_to_id && !reply) { throw new ApiError( 422, "Note referenced by in_reply_to_id not found", ); } - if (quote_id && !(await Note.fromId(quote_id))) { + if (in_reply_to_id && reply?.data.reblogId) { + throw new ApiError(422, "Cannot reply to a reblog"); + } + + const quote = quote_id ? await Note.fromId(quote_id) : null; + + if (quote_id && !quote) { throw new ApiError( 422, "Note referenced by quote_id not found", ); } + if (quote_id && quote?.data.reblogId) { + throw new ApiError(422, "Cannot quote a reblog"); + } + const sanitizedSpoilerText = spoiler_text ? await sanitizedHtmlStrip(spoiler_text) : undefined;