From 0d508db8a7a36218d38231af4e718aff0e94d9bc Mon Sep 17 00:00:00 2001 From: Daiki Mizukami Date: Fri, 9 Aug 2024 12:10:51 +0900 Subject: fix(backend): check visibility of following/followers of remote users / feat: moderators can see following/followers of all users (#14375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): check visibility of following/followers of remote users Resolves https://github.com/misskey-dev/misskey/issues/13362. * test(backend): add tests for visibility of following/followers of remote users * docs(changelog): update CHANGELOG.md * feat: moderators can see following/followers of all users * docs(changelog): update CHANGELOG.md * refactor(backend): minor refactoring `createPerson`と`if`の条件を統一するとともに、異常系の 処理をearly returnに追い出すための変更。 * feat(backend): moderators can see following/followers count of all users As per https://github.com/misskey-dev/misskey/pull/14375#issuecomment-2275044908. --- .../src/server/api/endpoints/users/followers.ts | 34 ++++++++++++---------- .../src/server/api/endpoints/users/following.ts | 34 ++++++++++++---------- 2 files changed, 38 insertions(+), 30 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 7ce7734f53..a8b4319a61 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -81,6 +82,7 @@ export default class extends Endpoint { // eslint- private utilityService: UtilityService, private followingEntityService: FollowingEntityService, private queryService: QueryService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy(ps.userId != null @@ -93,22 +95,24 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - if (profile.followersVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError(meta.errors.forbidden); - } - } else if (profile.followersVisibility === 'followers') { - if (me == null) { - throw new ApiError(meta.errors.forbidden); - } else if (me.id !== user.id) { - const isFollowing = await this.followingsRepository.exists({ - where: { - followeeId: user.id, - followerId: me.id, - }, - }); - if (!isFollowing) { + if (profile.followersVisibility !== 'public' && !await this.roleService.isModerator(me)) { + if (profile.followersVisibility === 'private') { + if (me == null || (me.id !== user.id)) { + throw new ApiError(meta.errors.forbidden); + } + } else if (profile.followersVisibility === 'followers') { + if (me == null) { throw new ApiError(meta.errors.forbidden); + } else if (me.id !== user.id) { + const isFollowing = await this.followingsRepository.exists({ + where: { + followeeId: user.id, + followerId: me.id, + }, + }); + if (!isFollowing) { + throw new ApiError(meta.errors.forbidden); + } } } } diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 6b3389f0b2..feda5bb353 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -12,6 +12,7 @@ import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -90,6 +91,7 @@ export default class extends Endpoint { // eslint- private utilityService: UtilityService, private followingEntityService: FollowingEntityService, private queryService: QueryService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy(ps.userId != null @@ -102,22 +104,24 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - if (profile.followingVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError(meta.errors.forbidden); - } - } else if (profile.followingVisibility === 'followers') { - if (me == null) { - throw new ApiError(meta.errors.forbidden); - } else if (me.id !== user.id) { - const isFollowing = await this.followingsRepository.exists({ - where: { - followeeId: user.id, - followerId: me.id, - }, - }); - if (!isFollowing) { + if (profile.followingVisibility !== 'public' && !await this.roleService.isModerator(me)) { + if (profile.followingVisibility === 'private') { + if (me == null || (me.id !== user.id)) { + throw new ApiError(meta.errors.forbidden); + } + } else if (profile.followingVisibility === 'followers') { + if (me == null) { throw new ApiError(meta.errors.forbidden); + } else if (me.id !== user.id) { + const isFollowing = await this.followingsRepository.exists({ + where: { + followeeId: user.id, + followerId: me.id, + }, + }); + if (!isFollowing) { + throw new ApiError(meta.errors.forbidden); + } } } } -- cgit v1.2.3-freya From f50941389d8724442ce2d7326afe9fbdadd3b58e Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 9 Aug 2024 16:04:41 +0900 Subject: fix: readAllNotifications message not working (#14374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: add and use isJsonObject * fix: readNotification message without body is not working * docs(changelog): WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 * Update CHANGELOG.md Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --------- Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- CHANGELOG.md | 4 +++- packages/backend/src/misc/json-value.ts | 4 ++++ .../backend/src/server/api/stream/Connection.ts | 27 +++++++++++++--------- .../src/server/api/stream/channels/queue-stats.ts | 3 ++- .../src/server/api/stream/channels/reversi-game.ts | 7 +++--- .../src/server/api/stream/channels/server-stats.ts | 3 ++- 6 files changed, 31 insertions(+), 17 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index c18cccc44e..fa0bc6282b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ - ### Server -- +- Fix: WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 #14374 + - 通知ページや通知カラム(デッキ)を開いている状態において、新たに発生した通知が既読されない問題が修正されます。 + - これにより、プッシュ通知が有効な同条件下の環境において、プッシュ通知が常に発生してしまう問題も修正されます。 ## 2024.7.0 diff --git a/packages/backend/src/misc/json-value.ts b/packages/backend/src/misc/json-value.ts index 7994441791..bd7fe12058 100644 --- a/packages/backend/src/misc/json-value.ts +++ b/packages/backend/src/misc/json-value.ts @@ -6,3 +6,7 @@ export type JsonValue = JsonArray | JsonObject | string | number | boolean | null; export type JsonObject = {[K in string]?: JsonValue}; export type JsonArray = JsonValue[]; + +export function isJsonObject(value: JsonValue | undefined): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index 96082827f8..7773150b74 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -14,7 +14,8 @@ import { CacheService } from '@/core/CacheService.js'; import { MiFollowing, MiUserProfile } from '@/models/_.js'; import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; -import type { JsonObject } from '@/misc/json-value.js'; +import { isJsonObject } from '@/misc/json-value.js'; +import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type { ChannelsService } from './ChannelsService.js'; import type { EventEmitter } from 'events'; import type Channel from './channel.js'; @@ -112,8 +113,6 @@ export default class Connection { const { type, body } = obj; - if (typeof body !== 'object' || body === null || Array.isArray(body)) return; - switch (type) { case 'readNotification': this.onReadNotification(body); break; case 'subNote': this.onSubscribeNote(body); break; @@ -154,7 +153,8 @@ export default class Connection { } @bindThis - private readNote(body: JsonObject) { + private readNote(body: JsonValue | undefined) { + if (!isJsonObject(body)) return; const id = body.id; const note = this.cachedNotes.find(n => n.id === id); @@ -166,7 +166,7 @@ export default class Connection { } @bindThis - private onReadNotification(payload: JsonObject) { + private onReadNotification(payload: JsonValue | undefined) { this.notificationService.readAllNotification(this.user!.id); } @@ -174,7 +174,8 @@ export default class Connection { * 投稿購読要求時 */ @bindThis - private onSubscribeNote(payload: JsonObject) { + private onSubscribeNote(payload: JsonValue | undefined) { + if (!isJsonObject(payload)) return; if (!payload.id || typeof payload.id !== 'string') return; const current = this.subscribingNotes[payload.id] ?? 0; @@ -190,7 +191,8 @@ export default class Connection { * 投稿購読解除要求時 */ @bindThis - private onUnsubscribeNote(payload: JsonObject) { + private onUnsubscribeNote(payload: JsonValue | undefined) { + if (!isJsonObject(payload)) return; if (!payload.id || typeof payload.id !== 'string') return; const current = this.subscribingNotes[payload.id]; @@ -216,12 +218,13 @@ export default class Connection { * チャンネル接続要求時 */ @bindThis - private onChannelConnectRequested(payload: JsonObject) { + private onChannelConnectRequested(payload: JsonValue | undefined) { + if (!isJsonObject(payload)) return; const { channel, id, params, pong } = payload; if (typeof id !== 'string') return; if (typeof channel !== 'string') return; if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return; - if (typeof params !== 'undefined' && (typeof params !== 'object' || params === null || Array.isArray(params))) return; + if (typeof params !== 'undefined' && !isJsonObject(params)) return; this.connectChannel(id, params, channel, pong ?? undefined); } @@ -229,7 +232,8 @@ export default class Connection { * チャンネル切断要求時 */ @bindThis - private onChannelDisconnectRequested(payload: JsonObject) { + private onChannelDisconnectRequested(payload: JsonValue | undefined) { + if (!isJsonObject(payload)) return; const { id } = payload; if (typeof id !== 'string') return; this.disconnectChannel(id); @@ -297,7 +301,8 @@ export default class Connection { * @param data メッセージ */ @bindThis - private onChannelMessageRequested(data: JsonObject) { + private onChannelMessageRequested(data: JsonValue | undefined) { + if (!isJsonObject(data)) return; if (typeof data.id !== 'string') return; if (typeof data.type !== 'string') return; if (typeof data.body === 'undefined') return; diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index ff7e740226..91b62255b4 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -6,6 +6,7 @@ import Xev from 'xev'; import { Injectable } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; +import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import Channel, { type MiChannelService } from '../channel.js'; @@ -36,7 +37,7 @@ class QueueStatsChannel extends Channel { public onMessage(type: string, body: JsonValue) { switch (type) { case 'requestLog': - if (typeof body !== 'object' || body === null || Array.isArray(body)) return; + if (!isJsonObject(body)) return; if (typeof body.id !== 'string') return; if (typeof body.length !== 'number') return; ev.once(`queueStatsLog:${body.id}`, statsLog => { diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts index 17823a164a..c6f4a4ae3b 100644 --- a/packages/backend/src/server/api/stream/channels/reversi-game.ts +++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { ReversiService } from '@/core/ReversiService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; +import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import Channel, { type MiChannelService } from '../channel.js'; @@ -44,16 +45,16 @@ class ReversiGameChannel extends Channel { this.ready(body); break; case 'updateSettings': - if (typeof body !== 'object' || body === null || Array.isArray(body)) return; + if (!isJsonObject(body)) return; if (typeof body.key !== 'string') return; - if (typeof body.value !== 'object' || body.value === null || Array.isArray(body.value)) return; + if (!isJsonObject(body.value)) return; this.updateSettings(body.key, body.value); break; case 'cancel': this.cancelGame(); break; case 'putStone': - if (typeof body !== 'object' || body === null || Array.isArray(body)) return; + if (!isJsonObject(body)) return; if (typeof body.pos !== 'number') return; if (typeof body.id !== 'string') return; this.putStone(body.pos, body.id); diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index 6258afba35..ec5352d12d 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -6,6 +6,7 @@ import Xev from 'xev'; import { Injectable } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; +import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import Channel, { type MiChannelService } from '../channel.js'; @@ -36,7 +37,7 @@ class ServerStatsChannel extends Channel { public onMessage(type: string, body: JsonValue) { switch (type) { case 'requestLog': - if (typeof body !== 'object' || body === null || Array.isArray(body)) return; + if (!isJsonObject(body)) return; ev.once(`serverStatsLog:${body.id}`, statsLog => { this.send('statsLog', statsLog); }); -- cgit v1.2.3-freya From cb10156f01d53e04c98639be0061f0ec25308edb Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:46:26 +0900 Subject: fix(frontend): iOSでacctなどがリンクとして誤検知されるのを抑制 (#14354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): iosでの自動リンク化を抑制 * Update Changelog * typo * typo * Update CHANGELOG.md --- CHANGELOG.md | 1 + packages/backend/src/server/web/views/base.pug | 1 + 2 files changed, 2 insertions(+) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 800161841c..d2ce0906e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Enhance: 「自分のPlay」ページにおいてPlayが非公開かどうかが一目でわかるように - Fix: Play編集時に公開範囲が「パブリック」にリセットされる問題を修正 - Fix: ページ遷移に失敗することがある問題を修正 +- Fix: iOSでユーザー名などがリンクとして誤検知される現象を抑制 ### Server - Fix: WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 #14374 diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index ec1325e4e9..151b7bca6c 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -28,6 +28,7 @@ html meta(property='og:site_name' content= instanceName || 'Misskey') meta(property='instance_url' content= instanceUrl) meta(name='viewport' content='width=device-width, initial-scale=1') + meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') link(rel='icon' href= icon || '/favicon.ico') link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') link(rel='manifest' href='/manifest.json') -- cgit v1.2.3-freya From 0aaf74ee22da1b8e4b690075c45c166d96f1b0aa Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 11 Aug 2024 11:28:07 +0900 Subject: fix(backend): InstanceEntityService.packMany に me が渡っていないのを修正 (#14360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: pass current user into `InstanceEntityService.packMany` (cherry picked from commit 858ba188768017764c61c4a5591bdf2524a850e7) * Update Changelog * origin * Update Changelog --------- Co-authored-by: Hazel K --- CHANGELOG.md | 2 ++ packages/backend/src/core/entities/InstanceEntityService.ts | 3 ++- packages/backend/src/server/api/endpoints/federation/instances.ts | 2 +- packages/backend/src/server/api/endpoints/federation/stats.ts | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5d228d17..141d1955f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - 通知ページや通知カラム(デッキ)を開いている状態において、新たに発生した通知が既読されない問題が修正されます。 - これにより、プッシュ通知が有効な同条件下の環境において、プッシュ通知が常に発生してしまう問題も修正されます。 - Fix: Play各種エンドポイントの返り値に`visibility`が含まれていない問題を修正 +- Fix: サーバー情報取得の際にモデレーター限定の情報が取得できないことがあるのを修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/582) ## 2024.7.0 diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 4c45c13167..4956bc22ce 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -63,8 +63,9 @@ export class InstanceEntityService { @bindThis public packMany( instances: MiInstance[], + me?: { id: MiUser['id']; } | null | undefined, ) { - return Promise.all(instances.map(x => this.pack(x))); + return Promise.all(instances.map(x => this.pack(x, me))); } } diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 36f4bf5aa6..41954129e6 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -170,7 +170,7 @@ export default class extends Endpoint { // eslint- const instances = await query.limit(ps.limit).offset(ps.offset).getMany(); - return await this.instanceEntityService.packMany(instances); + return await this.instanceEntityService.packMany(instances, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index bac54970ab..69900bff9a 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -107,9 +107,9 @@ export default class extends Endpoint { // eslint- const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0); return await awaitAll({ - topSubInstances: this.instanceEntityService.packMany(topSubInstances), + topSubInstances: this.instanceEntityService.packMany(topSubInstances, me), otherFollowersCount: Math.max(0, allSubCount - gotSubCount), - topPubInstances: this.instanceEntityService.packMany(topPubInstances), + topPubInstances: this.instanceEntityService.packMany(topPubInstances, me), otherFollowingCount: Math.max(0, allPubCount - gotPubCount), }); }); -- cgit v1.2.3-freya From cd210001e6ffd6232678cbc74f06f8e6d05a1d15 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:08:51 +0900 Subject: enhance(backend): 凍結されたアカウントのフォローリクエストを表示しないように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + packages/backend/src/core/UserSuspendService.ts | 79 +++++++++++++++++++++- .../src/server/api/endpoints/admin/suspend-user.ts | 51 +------------- .../server/api/endpoints/admin/unsuspend-user.ts | 14 +--- 4 files changed, 81 insertions(+), 64 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c90bb663..a3abadfe3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Fix: mCaptchaを使用していてもbotプロテクションに関する警告が消えないのを修正 ### Server +- Enhance: 凍結されたアカウントのフォローリクエストを表示しないように - Fix: WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 #14374 - 通知ページや通知カラム(デッキ)を開いている状態において、新たに発生した通知が既読されない問題が修正されます。 - これにより、プッシュ通知が有効な同条件下の環境において、プッシュ通知が常に発生してしまう問題も修正されます。 diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index d594a223f4..7920e58e36 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; -import type { FollowingsRepository } from '@/models/_.js'; +import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -13,24 +13,75 @@ import { DI } from '@/di-symbols.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import { RelationshipJobData } from '@/queue/types.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; @Injectable() export class UserSuspendService { constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, + private userEntityService: UserEntityService, private queueService: QueueService, private globalEventService: GlobalEventService, private apRendererService: ApRendererService, + private moderationLogService: ModerationLogService, ) { } @bindThis - public async doPostSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { + public async suspend(user: MiUser, moderator: MiUser): Promise { + await this.usersRepository.update(user.id, { + isSuspended: true, + }); + + this.moderationLogService.log(moderator, 'suspend', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + (async () => { + await this.postSuspend(user).catch(e => {}); + await this.unFollowAll(user).catch(e => {}); + })(); + } + + @bindThis + public async unsuspend(user: MiUser, moderator: MiUser): Promise { + await this.usersRepository.update(user.id, { + isSuspended: false, + }); + + this.moderationLogService.log(moderator, 'unsuspend', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + (async () => { + await this.postUnsuspend(user).catch(e => {}); + })(); + } + + @bindThis + private async postSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); + this.followRequestsRepository.delete({ + followeeId: user.id, + }); + this.followRequestsRepository.delete({ + followerId: user.id, + }); + if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); @@ -58,7 +109,7 @@ export class UserSuspendService { } @bindThis - public async doPostUnsuspend(user: MiUser): Promise { + private async postUnsuspend(user: MiUser): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); if (this.userEntityService.isLocalUser(user)) { @@ -86,4 +137,26 @@ export class UserSuspendService { } } } + + @bindThis + private async unFollowAll(follower: MiUser) { + const followings = await this.followingsRepository.find({ + where: { + followerId: follower.id, + followeeId: Not(IsNull()), + }, + }); + + const jobs: RelationshipJobData[] = []; + for (const following of followings) { + if (following.followeeId && following.followerId) { + jobs.push({ + from: { id: following.followerId }, + to: { id: following.followeeId }, + silent: true, + }); + } + } + this.queueService.createUnfollowJob(jobs); + } } diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 8a946405cc..9f7378945e 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -3,18 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { IsNull, Not } from 'typeorm'; +import { } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; -import type { MiUser } from '@/models/User.js'; -import type { RelationshipJobData } from '@/queue/types.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; +import type { UsersRepository } from '@/models/_.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; -import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; -import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -38,13 +33,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - private userSuspendService: UserSuspendService, private roleService: RoleService, - private moderationLogService: ModerationLogService, - private queueService: QueueService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -57,42 +47,7 @@ export default class extends Endpoint { // eslint- throw new Error('cannot suspend moderator account'); } - await this.usersRepository.update(user.id, { - isSuspended: true, - }); - - this.moderationLogService.log(me, 'suspend', { - userId: user.id, - userUsername: user.username, - userHost: user.host, - }); - - (async () => { - await this.userSuspendService.doPostSuspend(user).catch(e => {}); - await this.unFollowAll(user).catch(e => {}); - })(); - }); - } - - @bindThis - private async unFollowAll(follower: MiUser) { - const followings = await this.followingsRepository.find({ - where: { - followerId: follower.id, - followeeId: Not(IsNull()), - }, + await this.userSuspendService.suspend(user, me); }); - - const jobs: RelationshipJobData[] = []; - for (const following of followings) { - if (following.followeeId && following.followerId) { - jobs.push({ - from: { id: following.followerId }, - to: { id: following.followeeId }, - silent: true, - }); - } - } - this.queueService.createUnfollowJob(jobs); } } diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 2c2b1bf6f5..b52c638cdb 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -6,7 +6,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; @@ -33,7 +32,6 @@ export default class extends Endpoint { // eslint- private usersRepository: UsersRepository, private userSuspendService: UserSuspendService, - private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -42,17 +40,7 @@ export default class extends Endpoint { // eslint- throw new Error('user not found'); } - await this.usersRepository.update(user.id, { - isSuspended: false, - }); - - this.moderationLogService.log(me, 'unsuspend', { - userId: user.id, - userUsername: user.username, - userHost: user.host, - }); - - this.userSuspendService.doPostUnsuspend(user); + await this.userSuspendService.unsuspend(user, me); }); } } -- cgit v1.2.3-freya From 86dd4abadcb19c3fa4e082b7ab2dc80c5d93618b Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 14 Aug 2024 20:29:06 +0900 Subject: Revert "enhance(backend): 凍結されたアカウントのフォローリクエストを表示しないように" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cd210001e6ffd6232678cbc74f06f8e6d05a1d15. --- CHANGELOG.md | 1 - packages/backend/src/core/UserSuspendService.ts | 79 +--------------------- .../src/server/api/endpoints/admin/suspend-user.ts | 51 +++++++++++++- .../server/api/endpoints/admin/unsuspend-user.ts | 14 +++- 4 files changed, 64 insertions(+), 81 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index a3abadfe3b..89c90bb663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ - Fix: mCaptchaを使用していてもbotプロテクションに関する警告が消えないのを修正 ### Server -- Enhance: 凍結されたアカウントのフォローリクエストを表示しないように - Fix: WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 #14374 - 通知ページや通知カラム(デッキ)を開いている状態において、新たに発生した通知が既読されない問題が修正されます。 - これにより、プッシュ通知が有効な同条件下の環境において、プッシュ通知が常に発生してしまう問題も修正されます。 diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 7920e58e36..d594a223f4 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; -import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js'; +import type { FollowingsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -13,75 +13,24 @@ import { DI } from '@/di-symbols.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; -import { RelationshipJobData } from '@/queue/types.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; @Injectable() export class UserSuspendService { constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - @Inject(DI.followRequestsRepository) - private followRequestsRepository: FollowRequestsRepository, - private userEntityService: UserEntityService, private queueService: QueueService, private globalEventService: GlobalEventService, private apRendererService: ApRendererService, - private moderationLogService: ModerationLogService, ) { } @bindThis - public async suspend(user: MiUser, moderator: MiUser): Promise { - await this.usersRepository.update(user.id, { - isSuspended: true, - }); - - this.moderationLogService.log(moderator, 'suspend', { - userId: user.id, - userUsername: user.username, - userHost: user.host, - }); - - (async () => { - await this.postSuspend(user).catch(e => {}); - await this.unFollowAll(user).catch(e => {}); - })(); - } - - @bindThis - public async unsuspend(user: MiUser, moderator: MiUser): Promise { - await this.usersRepository.update(user.id, { - isSuspended: false, - }); - - this.moderationLogService.log(moderator, 'unsuspend', { - userId: user.id, - userUsername: user.username, - userHost: user.host, - }); - - (async () => { - await this.postUnsuspend(user).catch(e => {}); - })(); - } - - @bindThis - private async postSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { + public async doPostSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); - this.followRequestsRepository.delete({ - followeeId: user.id, - }); - this.followRequestsRepository.delete({ - followerId: user.id, - }); - if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); @@ -109,7 +58,7 @@ export class UserSuspendService { } @bindThis - private async postUnsuspend(user: MiUser): Promise { + public async doPostUnsuspend(user: MiUser): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); if (this.userEntityService.isLocalUser(user)) { @@ -137,26 +86,4 @@ export class UserSuspendService { } } } - - @bindThis - private async unFollowAll(follower: MiUser) { - const followings = await this.followingsRepository.find({ - where: { - followerId: follower.id, - followeeId: Not(IsNull()), - }, - }); - - const jobs: RelationshipJobData[] = []; - for (const following of followings) { - if (following.followeeId && following.followerId) { - jobs.push({ - from: { id: following.followerId }, - to: { id: following.followeeId }, - silent: true, - }); - } - } - this.queueService.createUnfollowJob(jobs); - } } diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 9f7378945e..8a946405cc 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -3,13 +3,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { } from 'typeorm'; +import { IsNull, Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/_.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; +import type { RelationshipJobData } from '@/queue/types.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -33,8 +38,13 @@ export default class extends Endpoint { // eslint- @Inject(DI.usersRepository) private usersRepository: UsersRepository, + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + private userSuspendService: UserSuspendService, private roleService: RoleService, + private moderationLogService: ModerationLogService, + private queueService: QueueService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -47,7 +57,42 @@ export default class extends Endpoint { // eslint- throw new Error('cannot suspend moderator account'); } - await this.userSuspendService.suspend(user, me); + await this.usersRepository.update(user.id, { + isSuspended: true, + }); + + this.moderationLogService.log(me, 'suspend', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + (async () => { + await this.userSuspendService.doPostSuspend(user).catch(e => {}); + await this.unFollowAll(user).catch(e => {}); + })(); + }); + } + + @bindThis + private async unFollowAll(follower: MiUser) { + const followings = await this.followingsRepository.find({ + where: { + followerId: follower.id, + followeeId: Not(IsNull()), + }, }); + + const jobs: RelationshipJobData[] = []; + for (const following of followings) { + if (following.followeeId && following.followerId) { + jobs.push({ + from: { id: following.followerId }, + to: { id: following.followeeId }, + silent: true, + }); + } + } + this.queueService.createUnfollowJob(jobs); } } diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index b52c638cdb..2c2b1bf6f5 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; @@ -32,6 +33,7 @@ export default class extends Endpoint { // eslint- private usersRepository: UsersRepository, private userSuspendService: UserSuspendService, + private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -40,7 +42,17 @@ export default class extends Endpoint { // eslint- throw new Error('user not found'); } - await this.userSuspendService.unsuspend(user, me); + await this.usersRepository.update(user.id, { + isSuspended: false, + }); + + this.moderationLogService.log(me, 'unsuspend', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + this.userSuspendService.doPostUnsuspend(user); }); } } -- cgit v1.2.3-freya From a8810af8d9a78c0819781001ab045a9c8cf9d171 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:02:12 +0900 Subject: fix(backend): リバーシの設定変更が反映されないのを修正 (#14404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): リバーシの設定変更が反映されないのを修正 * Update Changelog * add bindthis --- CHANGELOG.md | 1 + packages/backend/src/core/ReversiService.ts | 33 ++++++++++++++++++---- .../src/server/api/stream/channels/reversi-game.ts | 8 ++++-- packages/misskey-js/etc/misskey-js.api.md | 3 ++ packages/misskey-js/src/consts.ts | 13 ++++++--- packages/misskey-js/src/index.ts | 1 + 6 files changed, 47 insertions(+), 12 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c90bb663..9385842d62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/679) - Fix: ActivityPubのエンティティタイプ判定で不明なタイプを受け取った場合でも処理を継続するように - キュー処理のつまりが改善される可能性があります +- Fix: リバーシの対局設定の変更が反映されないのを修正 ## 2024.7.0 diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index 7f939b99c7..51dca3da59 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { ModuleRef } from '@nestjs/core'; +import { reversiUpdateKeys } from 'misskey-js'; import * as Reversi from 'misskey-reversi'; import { IsNull, LessThan, MoreThan } from 'typeorm'; import type { @@ -399,7 +400,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { } @bindThis - public async updateSettings(gameId: MiReversiGame['id'], user: MiUser, key: string, value: any) { + public isValidReversiUpdateKey(key: unknown): key is typeof reversiUpdateKeys[number] { + if (typeof key !== 'string') return false; + return (reversiUpdateKeys as string[]).includes(key); + } + + @bindThis + public isValidReversiUpdateValue(key: K, value: unknown): value is MiReversiGame[K] { + switch (key) { + case 'map': + return Array.isArray(value) && value.every(row => typeof row === 'string'); + case 'bw': + return typeof value === 'string' && ['random', '1', '2'].includes(value); + case 'isLlotheo': + return typeof value === 'boolean'; + case 'canPutEverywhere': + return typeof value === 'boolean'; + case 'loopedBoard': + return typeof value === 'boolean'; + case 'timeLimitForEachTurn': + return typeof value === 'number' && value >= 0; + default: + return false; + } + } + + @bindThis + public async updateSettings(gameId: MiReversiGame['id'], user: MiUser, key: K, value: MiReversiGame[K]) { const game = await this.get(gameId); if (game == null) throw new Error('game not found'); if (game.isStarted) return; @@ -407,10 +434,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { if ((game.user1Id === user.id) && game.user1Ready) return; if ((game.user2Id === user.id) && game.user2Ready) return; - if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard', 'timeLimitForEachTurn'].includes(key)) return; - - // TODO: より厳格なバリデーション - const updatedGame = { ...game, [key]: value, diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts index c6f4a4ae3b..7597a1cfa3 100644 --- a/packages/backend/src/server/api/stream/channels/reversi-game.ts +++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts @@ -12,6 +12,7 @@ import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityServi import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import Channel, { type MiChannelService } from '../channel.js'; +import { reversiUpdateKeys } from 'misskey-js'; class ReversiGameChannel extends Channel { public readonly chName = 'reversiGame'; @@ -46,8 +47,9 @@ class ReversiGameChannel extends Channel { break; case 'updateSettings': if (!isJsonObject(body)) return; - if (typeof body.key !== 'string') return; - if (!isJsonObject(body.value)) return; + if (!this.reversiService.isValidReversiUpdateKey(body.key)) return; + if (!this.reversiService.isValidReversiUpdateValue(body.key, body.value)) return; + this.updateSettings(body.key, body.value); break; case 'cancel': @@ -64,7 +66,7 @@ class ReversiGameChannel extends Channel { } @bindThis - private async updateSettings(key: string, value: JsonObject) { + private async updateSettings(key: K, value: MiReversiGame[K]) { if (this.user == null) return; this.reversiService.updateSettings(this.gameId!, this.user, key, value); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 16cb560a52..77a3820f46 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2829,6 +2829,9 @@ type ReversiShowGameResponse = operations['reversi___show-game']['responses']['2 // @public (undocumented) type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json']; +// @public (undocumented) +export const reversiUpdateKeys: ["map", "bw", "isLlotheo", "canPutEverywhere", "loopedBoard", "timeLimitForEachTurn"]; + // @public (undocumented) type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index aa8eeb48cb..5863a0b1dd 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -1,11 +1,16 @@ import type { operations } from './autogen/types.js'; import type { - AbuseReportNotificationRecipient, Ad, + AbuseReportNotificationRecipient, + Ad, Announcement, - EmojiDetailed, InviteCode, + EmojiDetailed, + InviteCode, MetaDetailed, Note, - Role, SystemWebhook, UserLite, + Role, + ReversiGameDetailed, + SystemWebhook, + UserLite, } from './autogen/models.js'; export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const; @@ -159,7 +164,7 @@ export const reversiUpdateKeys = [ 'canPutEverywhere', 'loopedBoard', 'timeLimitForEachTurn', -] as const; +] as const satisfies (keyof ReversiGameDetailed)[]; export type ReversiUpdateKey = typeof reversiUpdateKeys[number]; diff --git a/packages/misskey-js/src/index.ts b/packages/misskey-js/src/index.ts index 28007a8ade..7e0165d20b 100644 --- a/packages/misskey-js/src/index.ts +++ b/packages/misskey-js/src/index.ts @@ -22,6 +22,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons; export const followingVisibilities = consts.followingVisibilities; export const followersVisibilities = consts.followersVisibilities; export const moderationLogTypes = consts.moderationLogTypes; +export const reversiUpdateKeys = consts.reversiUpdateKeys; // api extractor not supported yet //export * as api from './api.js'; -- cgit v1.2.3-freya From d3cdc0880274bf90541a77812deff073ed05b1bc Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:30:03 +0900 Subject: fix(backend): 無制限にストリーミングのチャンネルに接続できる問題を修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + packages/backend/src/server/api/stream/Connection.ts | 6 ++++++ 2 files changed, 7 insertions(+) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cf454bb08..5007acb24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Fix: ActivityPubのエンティティタイプ判定で不明なタイプを受け取った場合でも処理を継続するように - キュー処理のつまりが改善される可能性があります - Fix: リバーシの対局設定の変更が反映されないのを修正 +- Fix: 無制限にストリーミングのチャンネルに接続できる問題を修正 ## 2024.7.0 diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index 7773150b74..0fb5238c78 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -20,6 +20,8 @@ import type { ChannelsService } from './ChannelsService.js'; import type { EventEmitter } from 'events'; import type Channel from './channel.js'; +const MAX_CHANNELS_PER_CONNECTION = 32; + /** * Main stream connection */ @@ -255,6 +257,10 @@ export default class Connection { */ @bindThis public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) { + if (this.channels.length >= MAX_CHANNELS_PER_CONNECTION) { + return; + } + const channelService = this.channelsService.getChannelService(channel); if (channelService.requireCredential && this.user == null) { -- cgit v1.2.3-freya From ef950a345bfd520a20336b55f2e7e095a4b9ebf0 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:57:28 +0900 Subject: suspend周りの改修 (#14409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(backend): 凍結されたアカウントのフォローリクエストを表示しないように * Update CHANGELOG.md * wip * Update gen-spec.ts * Update packages/backend/src/server/api/endpoints/admin/suspend-user.ts Co-authored-by: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com> * owa- * revert misskey-js related changes (#14414) --------- Co-authored-by: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com> Co-authored-by: anatawa12 --- CHANGELOG.md | 4 +- locales/index.d.ts | 4 ++ locales/ja-JP.yml | 1 + packages/backend/src/core/DeleteAccountService.ts | 60 +++++++++++++--- packages/backend/src/core/UserSuspendService.ts | 79 +++++++++++++++++++++- .../server/api/endpoints/admin/accounts/delete.ts | 23 +------ .../src/server/api/endpoints/admin/suspend-user.ts | 50 +------------- .../server/api/endpoints/admin/unsuspend-user.ts | 14 +--- packages/backend/src/types.ts | 6 ++ .../frontend/src/pages/admin/modlog.ModLog.vue | 7 +- packages/misskey-js/etc/misskey-js.api.md | 5 +- packages/misskey-js/src/consts.ts | 6 ++ packages/misskey-js/src/entities.ts | 3 + 13 files changed, 165 insertions(+), 97 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5007acb24e..74e3cb99a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ ## 2024.8.0 ### General -- Fix: リモートユーザのフォロー・フォロワーの一覧が非公開設定の場合も表示できてしまう問題を修正 - Enhance: モデレーターはすべてのユーザーのフォロー・フォロワーの一覧を見られるように +- Enhance: アカウントの削除のモデレーションログを残すように +- Fix: リモートユーザのフォロー・フォロワーの一覧が非公開設定の場合も表示できてしまう問題を修正 ### Client - Enhance: 「自分のPlay」ページにおいてPlayが非公開かどうかが一目でわかるように @@ -13,6 +14,7 @@ - Fix: ユーザーのモデレーションページにおいてユーザー名にドットが入っているとシステムアカウントとして表示されてしまう問題を修正 ### Server +- Enhance: 凍結されたアカウントのフォローリクエストを表示しないように - Fix: WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 #14374 - 通知ページや通知カラム(デッキ)を開いている状態において、新たに発生した通知が既読されない問題が修正されます。 - これにより、プッシュ通知が有効な同条件下の環境において、プッシュ通知が常に発生してしまう問題も修正されます。 diff --git a/locales/index.d.ts b/locales/index.d.ts index 9252b5ae10..2ab5f5797e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -9683,6 +9683,10 @@ export interface Locale extends ILocale { * 通報の通知先を削除 */ "deleteAbuseReportNotificationRecipient": string; + /** + * アカウントを削除 + */ + "deleteAccount": string; }; "_fileViewer": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c655e4678d..fac5b1e533 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2568,6 +2568,7 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "通報の通知先を作成" updateAbuseReportNotificationRecipient: "通報の通知先を更新" deleteAbuseReportNotificationRecipient: "通報の通知先を削除" + deleteAccount: "アカウントを削除" _fileViewer: title: "ファイルの詳細" diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index 79b614edba..7f1b8f3efb 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -4,12 +4,15 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/_.js'; +import { Not, IsNull } from 'typeorm'; +import type { FollowingsRepository, MiUser, UsersRepository } from '@/models/_.js'; import { QueueService } from '@/core/QueueService.js'; -import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; @Injectable() export class DeleteAccountService { @@ -17,9 +20,14 @@ export class DeleteAccountService { @Inject(DI.usersRepository) private usersRepository: UsersRepository, - private userSuspendService: UserSuspendService, + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private apRendererService: ApRendererService, private queueService: QueueService, private globalEventService: GlobalEventService, + private moderationLogService: ModerationLogService, ) { } @@ -27,16 +35,52 @@ export class DeleteAccountService { public async deleteAccount(user: { id: string; host: string | null; - }): Promise { + }, moderator?: MiUser): Promise { const _user = await this.usersRepository.findOneByOrFail({ id: user.id }); if (_user.isRoot) throw new Error('cannot delete a root account'); + if (moderator != null) { + this.moderationLogService.log(moderator, 'deleteAccount', { + userId: user.id, + userUsername: _user.username, + userHost: user.host, + }); + } + // 物理削除する前にDelete activityを送信する - await this.userSuspendService.doPostSuspend(user).catch(e => {}); + if (this.userEntityService.isLocalUser(user)) { + // 知り得る全SharedInboxにDelete配信 + const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); - this.queueService.createDeleteAccountJob(user, { - soft: false, - }); + const queue: string[] = []; + + const followings = await this.followingsRepository.find({ + where: [ + { followerSharedInbox: Not(IsNull()) }, + { followeeSharedInbox: Not(IsNull()) }, + ], + select: ['followerSharedInbox', 'followeeSharedInbox'], + }); + + const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox); + + for (const inbox of inboxes) { + if (inbox != null && !queue.includes(inbox)) queue.push(inbox); + } + + for (const inbox of queue) { + this.queueService.deliver(user, content, inbox, true); + } + + this.queueService.createDeleteAccountJob(user, { + soft: false, + }); + } else { + // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する + this.queueService.createDeleteAccountJob(user, { + soft: true, + }); + } await this.usersRepository.update(user.id, { isDeleted: true, diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index d594a223f4..7920e58e36 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; -import type { FollowingsRepository } from '@/models/_.js'; +import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -13,24 +13,75 @@ import { DI } from '@/di-symbols.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import { RelationshipJobData } from '@/queue/types.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; @Injectable() export class UserSuspendService { constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, + private userEntityService: UserEntityService, private queueService: QueueService, private globalEventService: GlobalEventService, private apRendererService: ApRendererService, + private moderationLogService: ModerationLogService, ) { } @bindThis - public async doPostSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { + public async suspend(user: MiUser, moderator: MiUser): Promise { + await this.usersRepository.update(user.id, { + isSuspended: true, + }); + + this.moderationLogService.log(moderator, 'suspend', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + (async () => { + await this.postSuspend(user).catch(e => {}); + await this.unFollowAll(user).catch(e => {}); + })(); + } + + @bindThis + public async unsuspend(user: MiUser, moderator: MiUser): Promise { + await this.usersRepository.update(user.id, { + isSuspended: false, + }); + + this.moderationLogService.log(moderator, 'unsuspend', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + (async () => { + await this.postUnsuspend(user).catch(e => {}); + })(); + } + + @bindThis + private async postSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); + this.followRequestsRepository.delete({ + followeeId: user.id, + }); + this.followRequestsRepository.delete({ + followerId: user.id, + }); + if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); @@ -58,7 +109,7 @@ export class UserSuspendService { } @bindThis - public async doPostUnsuspend(user: MiUser): Promise { + private async postUnsuspend(user: MiUser): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); if (this.userEntityService.isLocalUser(user)) { @@ -86,4 +137,26 @@ export class UserSuspendService { } } } + + @bindThis + private async unFollowAll(follower: MiUser) { + const followings = await this.followingsRepository.find({ + where: { + followerId: follower.id, + followeeId: Not(IsNull()), + }, + }); + + const jobs: RelationshipJobData[] = []; + for (const following of followings) { + if (following.followeeId && following.followerId) { + jobs.push({ + from: { id: following.followerId }, + to: { id: following.followeeId }, + silent: true, + }); + } + } + this.queueService.createUnfollowJob(jobs); + } } diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 4074e416b8..01dea703a3 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -7,9 +7,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; import { QueueService } from '@/core/QueueService.js'; -import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DeleteAccountService } from '@/core/DeleteAccountService.js'; export const meta = { tags: ['admin'], @@ -33,9 +33,7 @@ export default class extends Endpoint { // eslint- @Inject(DI.usersRepository) private usersRepository: UsersRepository, - private userEntityService: UserEntityService, - private queueService: QueueService, - private userSuspendService: UserSuspendService, + private deleteAccoountService: DeleteAccountService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -48,22 +46,7 @@ export default class extends Endpoint { // eslint- throw new Error('cannot delete a root account'); } - if (this.userEntityService.isLocalUser(user)) { - // 物理削除する前にDelete activityを送信する - await this.userSuspendService.doPostSuspend(user).catch(err => {}); - - this.queueService.createDeleteAccountJob(user, { - soft: false, - }); - } else { - this.queueService.createDeleteAccountJob(user, { - soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する - }); - } - - await this.usersRepository.update(user.id, { - isDeleted: true, - }); + await this.deleteAccoountService.deleteAccount(user); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 8a946405cc..bea1bdc4ed 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -3,18 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { IsNull, Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; -import type { MiUser } from '@/models/User.js'; -import type { RelationshipJobData } from '@/queue/types.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; +import type { UsersRepository } from '@/models/_.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; -import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; -import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -38,13 +32,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - private userSuspendService: UserSuspendService, private roleService: RoleService, - private moderationLogService: ModerationLogService, - private queueService: QueueService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -57,42 +46,7 @@ export default class extends Endpoint { // eslint- throw new Error('cannot suspend moderator account'); } - await this.usersRepository.update(user.id, { - isSuspended: true, - }); - - this.moderationLogService.log(me, 'suspend', { - userId: user.id, - userUsername: user.username, - userHost: user.host, - }); - - (async () => { - await this.userSuspendService.doPostSuspend(user).catch(e => {}); - await this.unFollowAll(user).catch(e => {}); - })(); - }); - } - - @bindThis - private async unFollowAll(follower: MiUser) { - const followings = await this.followingsRepository.find({ - where: { - followerId: follower.id, - followeeId: Not(IsNull()), - }, + await this.userSuspendService.suspend(user, me); }); - - const jobs: RelationshipJobData[] = []; - for (const following of followings) { - if (following.followeeId && following.followerId) { - jobs.push({ - from: { id: following.followerId }, - to: { id: following.followeeId }, - silent: true, - }); - } - } - this.queueService.createUnfollowJob(jobs); } } diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 2c2b1bf6f5..b52c638cdb 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -6,7 +6,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; @@ -33,7 +32,6 @@ export default class extends Endpoint { // eslint- private usersRepository: UsersRepository, private userSuspendService: UserSuspendService, - private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -42,17 +40,7 @@ export default class extends Endpoint { // eslint- throw new Error('user not found'); } - await this.usersRepository.update(user.id, { - isSuspended: false, - }); - - this.moderationLogService.log(me, 'unsuspend', { - userId: user.id, - userUsername: user.username, - userHost: user.host, - }); - - this.userSuspendService.doPostUnsuspend(user); + await this.userSuspendService.unsuspend(user, me); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index ecbbee4eff..dbeb232926 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -96,6 +96,7 @@ export const moderationLogTypes = [ 'createAbuseReportNotificationRecipient', 'updateAbuseReportNotificationRecipient', 'deleteAbuseReportNotificationRecipient', + 'deleteAccount', ] as const; export type ModerationLogPayloads = { @@ -314,6 +315,11 @@ export type ModerationLogPayloads = { recipientId: string; recipient: any; }; + deleteAccount: { + userId: string; + userUsername: string; + userHost: string | null; + }; }; export type Serialized = { diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 91f1c7c5e6..eb50d068d9 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -21,12 +21,12 @@ SPDX-License-Identifier: AGPL-3.0-only ].includes(log.type), [$style.logYellow]: [ 'markSensitiveDriveFile', - 'resetPassword' + 'resetPassword', + 'suspendRemoteInstance', ].includes(log.type), [$style.logRed]: [ 'suspend', 'deleteRole', - 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', @@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only 'deleteAvatarDecoration', 'deleteSystemWebhook', 'deleteAbuseReportNotificationRecipient', + 'deleteAccount', ].includes(log.type) }" >{{ i18n.ts._moderationLogTypes[log.type] }} @@ -72,6 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only : {{ log.info.recipient.name }} : {{ log.info.before.name }} : {{ log.info.recipient.name }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}