diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index a3079d9db9..59f9c32191 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -302,7 +302,7 @@ export class NoteCreateService implements OnApplicationShutdown { case 'followers': // 他人のfollowers noteはreject if (data.renote.userId !== user.id) { - throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); + throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home'); } // Renote対象がfollowersならfollowersにする @@ -310,25 +310,53 @@ export class NoteCreateService implements OnApplicationShutdown { break; case 'specified': // specified / direct noteはreject - throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); + throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home'); + } + + if (data.renote.userId !== user.id) { + // Check local-only + if (data.renote.localOnly && user.host != null) { + throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot renote a local-only note'); + } + + // Check visibility + if (!await this.noteEntityService.isVisibleForMe(data.renote, user.id)) { + throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Cannot renote an invisible note'); + } + + // Check blocking + if (await this.userBlockingService.checkBlocked(data.renote.userId, user.id)) { + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked'); + } + } + } + + if (data.reply) { + if (isPureRenote(data.reply)) { + throw new IdentifiableError('3ac74a84-8fd5-4bb0-870f-01804f82ce15', 'Cannot reply to a pure renote (boost)'); + } + + if (data.reply.userId !== user.id) { + // Check local-only + if (data.reply.localOnly && user.host != null) { + throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot reply to a local-only note'); + } + + // Check visibility + if (!await this.noteEntityService.isVisibleForMe(data.reply, user.id)) { + throw new IdentifiableError('b98980fa-3780-406c-a935-b6d0eeee10d1', 'Cannot reply to an invisible note'); + } + + // Check blocking + if (await this.userBlockingService.checkBlocked(data.reply.userId, user.id)) { + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Reply target is blocked'); + } } } // Check quote permissions await this.checkQuotePermissions(data, user); - // Check blocking - if (this.isRenote(data) && !this.isQuote(data)) { - if (data.renote.userHost === null) { - if (data.renote.userId !== user.id) { - const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id); - if (blocked) { - throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked'); - } - } - } - } - // 返信対象がpublicではないならhomeにする if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') { data.visibility = 'home'; @@ -486,14 +514,6 @@ export class NoteCreateService implements OnApplicationShutdown { mandatoryCW: data.mandatoryCW, }); - // should really not happen, but better safe than sorry - if (data.reply?.id === insert.id) { - throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t reply to itself'); - } - if (data.renote?.id === insert.id) { - throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t renote itself'); - } - if (data.uri != null) insert.uri = data.uri; if (data.url != null) insert.url = data.url; diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index d96a835b8a..7b51ce2022 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -308,18 +308,6 @@ export class NoteEditService implements OnApplicationShutdown { throw new IdentifiableError('fd4cc33e-2a37-48dd-99cc-9b806eb2031a', 'Cannot renote a pure renote (boost)'); } - // Check for recursion - let renoteId: string | null = data.renote.id; - while (renoteId) { - if (renoteId === oldnote.id) { - throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: cannot renote itself`); - } - - // TODO create something like threadId but for quotes, that way we don't need full recursion - const next = await this.notesRepository.findOne({ where: { id: renoteId }, select: { renoteId: true } }); - renoteId = next?.renoteId ?? null; - } - switch (data.renote.visibility) { case 'public': // public noteは無条件にrenote可能 @@ -333,7 +321,7 @@ export class NoteEditService implements OnApplicationShutdown { case 'followers': // 他人のfollowers noteはreject if (data.renote.userId !== user.id) { - throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); + throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home'); } // Renote対象がfollowersならfollowersにする @@ -341,25 +329,44 @@ export class NoteEditService implements OnApplicationShutdown { break; case 'specified': // specified / direct noteはreject - throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); + throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home'); + } + + if (data.renote.userId !== user.id) { + // Check local-only + if (data.renote.localOnly && user.host != null) { + throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot renote a local-only note'); + } + + // Check visibility + if (!await this.noteEntityService.isVisibleForMe(data.renote, user.id)) { + throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Cannot renote an invisible note'); + } + + // Check blocking + if (await this.userBlockingService.checkBlocked(data.renote.userId, user.id)) { + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked'); + } + } + + // Check for recursion + if (data.renote.id === oldnote.id) { + throw new IdentifiableError('33510210-8452-094c-6227-4a6c05d99f02', `edit failed for ${oldnote.id}: note cannot quote itself`); + } + for (let nextRenoteId = data.renote.renoteId; nextRenoteId != null;) { + if (nextRenoteId === oldnote.id) { + throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: note cannot quote a quote of itself`); + } + + // TODO create something like threadId but for quotes, that way we don't need full recursion + const next = await this.notesRepository.findOne({ where: { id: nextRenoteId }, select: { renoteId: true } }); + nextRenoteId = next?.renoteId ?? null; } } // Check quote permissions await this.noteCreateService.checkQuotePermissions(data, user); - // Check blocking - if (this.isRenote(data) && !this.isQuote(data)) { - if (data.renote.userHost === null) { - if (data.renote.userId !== user.id) { - const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id); - if (blocked) { - throw new Error('blocked'); - } - } - } - } - // 返信対象がpublicではないならhomeにする if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') { data.visibility = 'home'; diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index b451cbe1dc..652efd46b2 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -280,13 +280,6 @@ export class ApNoteService { processErrors.push('quoteUnavailable'); } - if (reply && reply.userHost == null && reply.localOnly) { - throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot reply to local-only note'); - } - if (quote && quote.userHost == null && quote.localOnly) { - throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot quote a local-only note'); - } - // vote if (reply && reply.hasPoll) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); @@ -466,10 +459,6 @@ export class ApNoteService { processErrors.push('quoteUnavailable'); } - if (quote && quote.userHost == null && quote.localOnly) { - throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot quote a local-only note'); - } - // vote if (reply && reply.hasPoll) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 98bf827d25..cf61a297b0 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -303,31 +303,6 @@ export default class extends Endpoint { // eslint- if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (isRenote(renote) && !isQuote(renote)) { - throw new ApiError(meta.errors.cannotReRenote); - } else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(renote, me)).accessible) { - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } - - // Check blocking - if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - where: { - blockerId: renote.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - - if (renote.visibility === 'followers' && renote.userId !== me.id) { - // 他人のfollowers noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } else if (renote.visibility === 'specified') { - // specified / direct noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); } if (renote.channelId && renote.channelId !== ps.channelId) { @@ -351,26 +326,9 @@ export default class extends Endpoint { // eslint- if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (isRenote(reply) && !isQuote(reply)) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(reply, me)).accessible) { - throw new ApiError(meta.errors.cannotReplyToInvisibleNote); } else if (reply.visibility === 'specified' && ps.visibility !== 'specified') { throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); } - - // Check blocking - if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - where: { - blockerId: reply.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } } if (ps.poll) { @@ -430,6 +388,14 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.quoteDisabledForUser); } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { throw new ApiError(meta.errors.cannotReRenote); + } else if (e.id === 'b6352a84-e5cd-4b05-a26c-63437a6b98ba') { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } else if (e.id === 'be9529e9-fe72-4de0-ae43-0b363c4938af') { + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + } else if (e.id === '3ac74a84-8fd5-4bb0-870f-01804f82ce15') { + throw new ApiError(meta.errors.cannotReplyToPureRenote); + } else if (e.id === 'b98980fa-3780-406c-a935-b6d0eeee10d1') { + throw new ApiError(meta.errors.cannotReplyToInvisibleNote); } } throw e; diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts index 8a65e016d5..2794d7376a 100644 --- a/packages/backend/src/server/api/endpoints/notes/edit.ts +++ b/packages/backend/src/server/api/endpoints/notes/edit.ts @@ -347,11 +347,6 @@ export default class extends Endpoint { // eslint- } let renote: MiNote | null = null; - - if (ps.renoteId === ps.editId) { - throw new ApiError(meta.errors.cannotQuoteCurrentPost); - } - if (ps.renoteId != null) { // Fetch renote to note renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); @@ -360,27 +355,6 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRenoteTarget); } - // Check blocking - if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - where: { - blockerId: renote.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - - if (renote.visibility === 'followers' && renote.userId !== me.id) { - // 他人のfollowers noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } else if (renote.visibility === 'specified') { - // specified / direct noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } - if (renote.channelId && renote.channelId !== ps.channelId) { // チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック // リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する @@ -402,26 +376,9 @@ export default class extends Endpoint { // eslint- if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (isRenote(reply) && !isQuote(reply)) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(reply, me)).accessible) { - throw new ApiError(meta.errors.cannotReplyToInvisibleNote); } else if (reply.visibility === 'specified' && ps.visibility !== 'specified') { throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); } - - // Check blocking - if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - where: { - blockerId: reply.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } } if (ps.poll) { @@ -477,10 +434,20 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.containsTooManyMentions); } else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') { throw new ApiError(meta.errors.quoteDisabledForUser); + } else if (e.id === '33510210-8452-094c-6227-4a6c05d99f02') { + throw new ApiError(meta.errors.cannotQuoteCurrentPost); } else if (e.id === 'ea93b7c2-3d6c-4e10-946b-00d50b1a75cb') { throw new ApiError(meta.errors.cannotQuoteaQuoteOfCurrentPost); } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { throw new ApiError(meta.errors.cannotReRenote); + } else if (e.id === 'b6352a84-e5cd-4b05-a26c-63437a6b98ba') { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } else if (e.id === 'be9529e9-fe72-4de0-ae43-0b363c4938af') { + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + } else if (e.id === '3ac74a84-8fd5-4bb0-870f-01804f82ce15') { + throw new ApiError(meta.errors.cannotReplyToPureRenote); + } else if (e.id === 'b98980fa-3780-406c-a935-b6d0eeee10d1') { + throw new ApiError(meta.errors.cannotReplyToInvisibleNote); } } throw e;