From cfbf2c9c8eb374791d163962b761248b11dc557e Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 01:32:45 -0400 Subject: pack users with "me" context in AbuseUserReportEntityService --- packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 0dbfaae054..ad0bbb5647 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -134,7 +134,7 @@ export default class extends Endpoint { // eslint- const reports = await query.limit(ps.limit).getMany(); - return await this.abuseUserReportEntityService.packMany(reports); + return await this.abuseUserReportEntityService.packMany(reports, me); }); } } -- cgit v1.2.3-freya From 71f60d519b6f8e655737136484658ea0f1d00507 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 01:33:55 -0400 Subject: add targetInstance to abuse report schema --- .../core/entities/AbuseUserReportEntityService.ts | 27 +++++++++++++++++++--- packages/backend/src/models/AbuseUserReport.ts | 21 +++++++++++++++++ .../api/endpoints/admin/abuse-user-reports.ts | 5 ++++ packages/misskey-js/src/autogen/types.ts | 1 + 4 files changed, 51 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index a6c00d89b9..c1d877aa12 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -5,13 +5,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AbuseUserReportsRepository, MiUser } from '@/models/_.js'; +import type { AbuseUserReportsRepository, InstancesRepository, MiInstance, MiUser } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { Packed } from '@/misc/json-schema.js'; import { UserEntityService } from './UserEntityService.js'; +import { InstanceEntityService } from './InstanceEntityService.js'; @Injectable() export class AbuseUserReportEntityService { @@ -19,6 +20,10 @@ export class AbuseUserReportEntityService { @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + private readonly instanceEntityService: InstanceEntityService, private userEntityService: UserEntityService, private idService: IdService, ) { @@ -30,6 +35,7 @@ export class AbuseUserReportEntityService { hint?: { packedReporter?: Packed<'UserDetailedNotMe'>, packedTargetUser?: Packed<'UserDetailedNotMe'>, + packedTargetInstance?: Packed<'FederationInstance'>, packedAssignee?: Packed<'UserDetailedNotMe'>, }, me?: MiUser | null, @@ -51,7 +57,16 @@ export class AbuseUserReportEntityService { targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, me, { schema: 'UserDetailedNotMe', }), - assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, { + // return hint, or pack by relation, or fetch and pack by id, or null + targetInstance: hint?.packedTargetInstance ?? ( + report.targetUserInstance + ? this.instanceEntityService.pack(report.targetUserInstance, me) + : report.targetUserHost + ? this.instancesRepository.findOneBy({ host: report.targetUserHost }).then(instance => instance + ? this.instanceEntityService.pack(instance, me) + : null) + : null), + assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, me, { schema: 'UserDetailedNotMe', }) : null, forwarded: report.forwarded, @@ -73,12 +88,18 @@ export class AbuseUserReportEntityService { me, { schema: 'UserDetailedNotMe' }, ).then(users => new Map(users.map(u => [u.id, u]))); + const _targetInstances = reports + .map(({ targetUserInstance, targetUserHost }) => targetUserInstance ?? targetUserHost) + .filter((i): i is MiInstance | string => i != null); + const _instanceMap = await this.instanceEntityService.packMany(await this.instanceEntityService.fetchInstancesByHost(_targetInstances), me) + .then(instances => new Map(instances.map(i => [i.host, i]))); return Promise.all( reports.map(report => { const packedReporter = _userMap.get(report.reporterId); const packedTargetUser = _userMap.get(report.targetUserId); + const packedTargetInstance = report.targetUserHost ? _instanceMap.get(report.targetUserHost) : undefined; const packedAssignee = report.assigneeId != null ? _userMap.get(report.assigneeId) : undefined; - return this.pack(report, { packedReporter, packedTargetUser, packedAssignee }, me); + return this.pack(report, { packedReporter, packedTargetUser, packedAssignee, packedTargetInstance }, me); }), ); } diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index d43ebf9342..8f8d759004 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -4,6 +4,7 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { MiInstance } from '@/models/Instance.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; @@ -88,11 +89,31 @@ export class MiAbuseUserReport { }) public targetUserHost: string | null; + @ManyToOne(() => MiInstance, { + // TODO create a foreign key constraint after hazelnoot/labs/persisted-instance-blocks is merged + createForeignKeyConstraints: false, + }) + @JoinColumn({ + name: 'targetUserHost', + referencedColumnName: 'host', + }) + public targetUserInstance: MiInstance | null; + @Index() @Column('varchar', { length: 128, nullable: true, comment: '[Denormalized]', }) public reporterHost: string | null; + + @ManyToOne(() => MiInstance, { + // TODO create a foreign key constraint after hazelnoot/labs/persisted-instance-blocks is merged + createForeignKeyConstraints: false, + }) + @JoinColumn({ + name: 'reporterHost', + referencedColumnName: 'host', + }) + public reporterInstance: MiInstance | null; //#endregion } diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index ad0bbb5647..f052d3dd14 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -69,6 +69,11 @@ export const meta = { nullable: false, optional: false, ref: 'UserDetailedNotMe', }, + targetInstance: { + type: 'object', + nullable: true, optional: false, + ref: 'FederationInstance', + }, assignee: { type: 'object', nullable: true, optional: false, diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 5e91fb14ac..b1d5f63a8b 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -6198,6 +6198,7 @@ export type operations = { assigneeId: string | null; reporter: components['schemas']['UserDetailedNotMe']; targetUser: components['schemas']['UserDetailedNotMe']; + targetInstance: components['schemas']['FederationInstance'] | null; assignee: components['schemas']['UserDetailedNotMe'] | null; forwarded: boolean; /** @enum {string|null} */ -- cgit v1.2.3-freya From b05ccbc3aca8e008555736f25760f9f2b1b2a5e7 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:03:05 -0400 Subject: add relations from abuse_user_report->user_profile to speed up admin/abuse-user-reports endpoint --- packages/backend/src/models/AbuseUserReport.ts | 22 ++++++++++++++++++++++ .../api/endpoints/admin/abuse-user-reports.ts | 10 +++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index 8f8d759004..c1a44c3d40 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -5,6 +5,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; import { MiInstance } from '@/models/Instance.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; @@ -25,6 +26,13 @@ export class MiAbuseUserReport { @JoinColumn() public targetUser: MiUser | null; + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + createForeignKeyConstraints: false, + }) + @JoinColumn({ name: 'targetUserId', referencedColumnName: 'userId' }) + public targetUserProfile: MiUserProfile | null; + @Index() @Column(id()) public reporterId: MiUser['id']; @@ -35,6 +43,13 @@ export class MiAbuseUserReport { @JoinColumn() public reporter: MiUser | null; + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + createForeignKeyConstraints: false, + }) + @JoinColumn({ name: 'reporterId', referencedColumnName: 'userId' }) + public reporterProfile: MiUserProfile | null; + @Column({ ...id(), nullable: true, @@ -47,6 +62,13 @@ export class MiAbuseUserReport { @JoinColumn() public assignee: MiUser | null; + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + createForeignKeyConstraints: false, + }) + @JoinColumn({ name: 'assigneeId', referencedColumnName: 'userId' }) + public assigneeProfile: MiUserProfile | null; + @Index() @Column('boolean', { default: false, diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index f052d3dd14..f9aa3b7ae5 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -120,7 +120,15 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId) + .leftJoinAndSelect('report.targetUser', 'targetUser') + .leftJoinAndSelect('report.targetUserProfile', 'targetUserProfile') + .leftJoinAndSelect('report.targetUserInstance', 'targetUserInstance') + .leftJoinAndSelect('report.reporter', 'reporter') + .leftJoinAndSelect('report.reporterUserProfile', 'reporterUserProfile') + .leftJoinAndSelect('report.assignee', 'assignee') + .leftJoinAndSelect('report.assigneeUserProfile', 'assigneeUserProfile') + ; switch (ps.state) { case 'resolved': query.andWhere('report.resolved = TRUE'); break; -- cgit v1.2.3-freya From 16891f82246ebb4ea84a9b2011041a837f4aeb28 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:34:14 -0400 Subject: fix relation typo in abuse-user-reports --- packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index f9aa3b7ae5..bf71cb82c4 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -125,9 +125,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('report.targetUserProfile', 'targetUserProfile') .leftJoinAndSelect('report.targetUserInstance', 'targetUserInstance') .leftJoinAndSelect('report.reporter', 'reporter') - .leftJoinAndSelect('report.reporterUserProfile', 'reporterUserProfile') + .leftJoinAndSelect('report.reporterProfile', 'reporterProfile') .leftJoinAndSelect('report.assignee', 'assignee') - .leftJoinAndSelect('report.assigneeUserProfile', 'assigneeUserProfile') + .leftJoinAndSelect('report.assigneeProfile', 'assigneeProfile') ; switch (ps.state) { -- cgit v1.2.3-freya From b1876bf06e92acda34270cf2582559b2f4cc1001 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 03:08:34 -0400 Subject: remove report -> profile relations to avoid TypeORM bug https://github.com/typeorm/typeorm/issues/10469 --- packages/backend/src/models/AbuseUserReport.ts | 22 ---------------------- .../api/endpoints/admin/abuse-user-reports.ts | 6 +++--- 2 files changed, 3 insertions(+), 25 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index c1a44c3d40..8f8d759004 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -5,7 +5,6 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; import { MiInstance } from '@/models/Instance.js'; -import { MiUserProfile } from '@/models/UserProfile.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; @@ -26,13 +25,6 @@ export class MiAbuseUserReport { @JoinColumn() public targetUser: MiUser | null; - @ManyToOne(() => MiUserProfile, { - onDelete: 'CASCADE', - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'targetUserId', referencedColumnName: 'userId' }) - public targetUserProfile: MiUserProfile | null; - @Index() @Column(id()) public reporterId: MiUser['id']; @@ -43,13 +35,6 @@ export class MiAbuseUserReport { @JoinColumn() public reporter: MiUser | null; - @ManyToOne(() => MiUserProfile, { - onDelete: 'CASCADE', - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'reporterId', referencedColumnName: 'userId' }) - public reporterProfile: MiUserProfile | null; - @Column({ ...id(), nullable: true, @@ -62,13 +47,6 @@ export class MiAbuseUserReport { @JoinColumn() public assignee: MiUser | null; - @ManyToOne(() => MiUserProfile, { - onDelete: 'CASCADE', - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'assigneeId', referencedColumnName: 'userId' }) - public assigneeProfile: MiUserProfile | null; - @Index() @Column('boolean', { default: false, diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index bf71cb82c4..b8200c09aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -122,12 +122,12 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId) .leftJoinAndSelect('report.targetUser', 'targetUser') - .leftJoinAndSelect('report.targetUserProfile', 'targetUserProfile') + .leftJoinAndSelect('targetUser.userProfile', 'targetUserProfile') .leftJoinAndSelect('report.targetUserInstance', 'targetUserInstance') .leftJoinAndSelect('report.reporter', 'reporter') - .leftJoinAndSelect('report.reporterProfile', 'reporterProfile') + .leftJoinAndSelect('reporter.userProfile', 'reporterProfile') .leftJoinAndSelect('report.assignee', 'assignee') - .leftJoinAndSelect('report.assigneeProfile', 'assigneeProfile') + .leftJoinAndSelect('assignee.userProfile', 'assigneeProfile') ; switch (ps.state) { -- cgit v1.2.3-freya From f4107b1c2b0632504b9fefb6c8c5608282313cc2 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:31:24 -0400 Subject: check if previews are disabled before anything else --- packages/backend/src/server/web/UrlPreviewService.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 2a300782c6..160cf37c00 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -123,6 +123,16 @@ export class UrlPreviewService { request: FastifyRequest, reply: FastifyReply, ): Promise { + if (!this.meta.urlPreviewEnabled) { + return reply.code(403).send({ + error: { + message: 'URL preview is disabled', + code: 'URL_PREVIEW_DISABLED', + id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', + }, + }); + } + const url = request.query.url; if (typeof url !== 'string' || !URL.canParse(url)) { reply.code(400); @@ -135,16 +145,6 @@ export class UrlPreviewService { return; } - if (!this.meta.urlPreviewEnabled) { - return reply.code(403).send({ - error: { - message: 'URL preview is disabled', - code: 'URL_PREVIEW_DISABLED', - id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', - }, - }); - } - // Check rate limit const auth = await this.authenticate(request); if (!await this.checkRateLimit(auth, reply)) { -- cgit v1.2.3-freya From f601cff5c5222d6f3a7c06ecbafb3d07ad63997f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:31:40 -0400 Subject: check input URL scheme before continuing --- packages/backend/src/server/web/UrlPreviewService.ts | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 160cf37c00..da2660ab0f 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -139,6 +139,13 @@ export class UrlPreviewService { return; } + // Enforce HTTP(S) for input URLs + const urlScheme = this.utilityService.getUrlScheme(url); + if (urlScheme !== 'http:' && urlScheme !== 'https:') { + reply.code(400); + return; + } + const lang = request.query.lang; if (Array.isArray(lang)) { reply.code(400); -- cgit v1.2.3-freya From 865b198ab31688de957d82ea447e11be78f718dc Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:32:04 -0400 Subject: redirect to exclude hash from preview URL --- packages/backend/src/server/web/UrlPreviewService.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index da2660ab0f..3e0133d50e 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -152,13 +152,25 @@ export class UrlPreviewService { return; } + // Strip out hash (anchor) + const urlObj = new URL(url); + if (urlObj.hash) { + urlObj.hash = ''; + const params = new URLSearchParams({ url: urlObj.href }); + if (lang) params.set('lang', lang); + const newUrl = `/url?${params.toString()}`; + + reply.redirect(newUrl, 301); + return; + } + // Check rate limit const auth = await this.authenticate(request); if (!await this.checkRateLimit(auth, reply)) { return; } - if (this.utilityService.isBlockedHost(this.meta.blockedHosts, new URL(url).host)) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, urlObj.host)) { return reply.code(403).send({ error: { message: 'URL is blocked', -- cgit v1.2.3-freya From a91c0de9b5b337fdb65fbd922969132d610bd8c4 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:32:21 -0400 Subject: cache alternate URLs in UrlPreviewService --- packages/backend/src/server/web/UrlPreviewService.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 3e0133d50e..78b2204fbb 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -185,7 +185,7 @@ export class UrlPreviewService { return; } - const cacheKey = `${url}@${lang}@${cacheFormatVersion}`; + const cacheKey = getCacheKey(url, lang); if (await this.sendCachedPreview(cacheKey, reply, fetch)) { return; } @@ -236,6 +236,18 @@ export class UrlPreviewService { // Await this to avoid hammering redis when a bunch of URLs are fetched at once await this.previewCache.set(cacheKey, summary); + // Also cache the response URL in case of redirects + if (summary.url !== url) { + const responseCacheKey = getCacheKey(summary.url, lang); + await this.previewCache.set(responseCacheKey, summary); + } + + // Also cache the ActivityPub URL, if different from the others + if (summary.activityPub && summary.activityPub !== summary.url) { + const apCacheKey = getCacheKey(summary.activityPub, lang); + await this.previewCache.set(apCacheKey, summary); + } + // Cache 1 day (matching redis), but only once we finalize the result if (!summary.activityPub || summary.haveNoteLocally) { reply.header('Cache-Control', 'public, max-age=86400'); @@ -552,3 +564,7 @@ export class UrlPreviewService { return true; } } + +function getCacheKey(url: string, lang = 'none') { + return `${url}@${lang}@${cacheFormatVersion}`; +} -- cgit v1.2.3-freya