From fbe210a39d39ccc98b5480b86936de8d368c32a8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 27 Jun 2022 23:49:16 +0900 Subject: [PATCH] feat: make possible to delete an account by admin Resolve #8830 --- CHANGELOG.md | 1 + locales/ja-JP.yml | 2 ++ packages/backend/src/server/api/endpoints.ts | 2 ++ .../api/endpoints/admin/delete-account.ts | 31 +++++++++++++++++++ .../server/api/endpoints/i/delete-account.ts | 20 ++---------- .../backend/src/services/delete-account.ts | 23 ++++++++++++++ packages/client/src/pages/user-info.vue | 29 ++++++++++++++++- 7 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/admin/delete-account.ts create mode 100644 packages/backend/src/services/delete-account.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f16df03ee..f97912c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ You should also include the user name that made the change. - Server: Add rate limit to i/notifications @tamaina - Client: Improve control panel @syuilo - Client: Show warning in control panel when there is an unresolved abuse report @syuilo +- Make possible to delete an account by admin @syuilo - Improve player detection in URL preview @mei23 - Add Badge Image to Push Notification #8012 @tamaina - Client: Removing entries from a clip @futchitwo diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d7fe4dcb2..3b9bf4485 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -855,6 +855,8 @@ check: "チェック" recentNHours: "直近{n}時間" recentNDays: "直近{n}日" isSystemAccount: "システムにより自動で作成・管理されているアカウントです。" +typeToConfirm: "この操作を行うには {x} と入力してください" +deleteAccount: "アカウント削除" _emailUnavailable: used: "既に使用されています" diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 11d9d7c02..93f93cef0 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -59,6 +59,7 @@ import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js'; import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js'; import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js'; import * as ep___admin_vacuum from './endpoints/admin/vacuum.js'; +import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; import * as ep___antennas_delete from './endpoints/antennas/delete.js'; @@ -370,6 +371,7 @@ const eps = [ ['admin/unsuspend-user', ep___admin_unsuspendUser], ['admin/update-meta', ep___admin_updateMeta], ['admin/vacuum', ep___admin_vacuum], + ['admin/delete-account', ep___admin_deleteAccount], ['announcements', ep___announcements], ['antennas/create', ep___antennas_create], ['antennas/delete', ep___antennas_delete], diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts new file mode 100644 index 000000000..2d7ef2f23 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -0,0 +1,31 @@ +import { Users } from '@/models/index.js'; +import { deleteAccount } from '@/services/delete-account.js'; +import define from '../../define.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireAdmin: true, + + res: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps) => { + const user = await Users.findOneByOrFail({ id: ps.userId }); + if (user.isDeleted) { + return; + } + + await deleteAccount(user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 184005eb5..ede4a9d03 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -1,9 +1,7 @@ import bcrypt from 'bcryptjs'; -import define from '../../define.js'; import { UserProfiles, Users } from '@/models/index.js'; -import { doPostSuspend } from '@/services/suspend-user.js'; -import { publishUserEvent } from '@/services/stream.js'; -import { createDeleteAccountJob } from '@/queue/index.js'; +import { deleteAccount } from '@/services/delete-account.js'; +import define from '../../define.js'; export const meta = { requireCredential: true, @@ -34,17 +32,5 @@ export default define(meta, paramDef, async (ps, user) => { throw new Error('incorrect password'); } - // 物理削除する前にDelete activityを送信する - await doPostSuspend(user).catch(e => {}); - - createDeleteAccountJob(user, { - soft: false, - }); - - await Users.update(user.id, { - isDeleted: true, - }); - - // Terminate streaming - publishUserEvent(user.id, 'terminate', {}); + await deleteAccount(user); }); diff --git a/packages/backend/src/services/delete-account.ts b/packages/backend/src/services/delete-account.ts new file mode 100644 index 000000000..0fdceb671 --- /dev/null +++ b/packages/backend/src/services/delete-account.ts @@ -0,0 +1,23 @@ +import { Users } from '@/models/index.js'; +import { createDeleteAccountJob } from '@/queue/index.js'; +import { publishUserEvent } from './stream.js'; +import { doPostSuspend } from './suspend-user.js'; + +export async function deleteAccount(user: { + id: string; + host: string | null; +}): Promise { + // 物理削除する前にDelete activityを送信する + await doPostSuspend(user).catch(e => {}); + + createDeleteAccountJob(user, { + soft: false, + }); + + await Users.update(user.id, { + isDeleted: true, + }); + + // Terminate streaming + publishUserEvent(user.id, 'terminate', {}); +} diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 86c1be8d0..9dfb2d87a 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -35,7 +35,10 @@ {{ $ts.silence }} {{ $ts.suspend }} {{ $ts.reflectMayTakeTime }} - {{ $ts.resetPassword }} +
+ {{ $ts.resetPassword }} + {{ $ts.deleteAccount }} +
@@ -233,6 +236,30 @@ async function deleteAllFiles() { await refreshUser(); } +async function deleteAccount() { + const confirm = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteAccountConfirm, + }); + if (confirm.canceled) return; + + const typed = await os.inputText({ + text: i18n.t('typeToConfirm', { x: user?.username }), + }); + if (typed.canceled) return; + + if (typed.result === user?.username) { + await os.apiWithDialog('admin/delete-account', { + userId: user.id, + }); + } else { + os.alert({ + type: 'error', + text: 'input not match', + }); + } +} + watch(() => props.userId, () => { init = createFetcher(); }, {