feat: make possible to delete an account by admin

Resolve #8830
This commit is contained in:
syuilo 2022-06-27 23:49:16 +09:00 committed by Johann150
parent 9b008b9684
commit fbe210a39d
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
7 changed files with 90 additions and 18 deletions

View file

@ -16,6 +16,7 @@ You should also include the user name that made the change.
- Server: Add rate limit to i/notifications @tamaina - Server: Add rate limit to i/notifications @tamaina
- Client: Improve control panel @syuilo - Client: Improve control panel @syuilo
- Client: Show warning in control panel when there is an unresolved abuse report @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 - Improve player detection in URL preview @mei23
- Add Badge Image to Push Notification #8012 @tamaina - Add Badge Image to Push Notification #8012 @tamaina
- Client: Removing entries from a clip @futchitwo - Client: Removing entries from a clip @futchitwo

View file

@ -855,6 +855,8 @@ check: "チェック"
recentNHours: "直近{n}時間" recentNHours: "直近{n}時間"
recentNDays: "直近{n}日" recentNDays: "直近{n}日"
isSystemAccount: "システムにより自動で作成・管理されているアカウントです。" isSystemAccount: "システムにより自動で作成・管理されているアカウントです。"
typeToConfirm: "この操作を行うには {x} と入力してください"
deleteAccount: "アカウント削除"
_emailUnavailable: _emailUnavailable:
used: "既に使用されています" used: "既に使用されています"

View file

@ -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_unsuspendUser from './endpoints/admin/unsuspend-user.js';
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.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_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___announcements from './endpoints/announcements.js';
import * as ep___antennas_create from './endpoints/antennas/create.js'; import * as ep___antennas_create from './endpoints/antennas/create.js';
import * as ep___antennas_delete from './endpoints/antennas/delete.js'; import * as ep___antennas_delete from './endpoints/antennas/delete.js';
@ -370,6 +371,7 @@ const eps = [
['admin/unsuspend-user', ep___admin_unsuspendUser], ['admin/unsuspend-user', ep___admin_unsuspendUser],
['admin/update-meta', ep___admin_updateMeta], ['admin/update-meta', ep___admin_updateMeta],
['admin/vacuum', ep___admin_vacuum], ['admin/vacuum', ep___admin_vacuum],
['admin/delete-account', ep___admin_deleteAccount],
['announcements', ep___announcements], ['announcements', ep___announcements],
['antennas/create', ep___antennas_create], ['antennas/create', ep___antennas_create],
['antennas/delete', ep___antennas_delete], ['antennas/delete', ep___antennas_delete],

View file

@ -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);
});

View file

@ -1,9 +1,7 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import define from '../../define.js';
import { UserProfiles, Users } from '@/models/index.js'; import { UserProfiles, Users } from '@/models/index.js';
import { doPostSuspend } from '@/services/suspend-user.js'; import { deleteAccount } from '@/services/delete-account.js';
import { publishUserEvent } from '@/services/stream.js'; import define from '../../define.js';
import { createDeleteAccountJob } from '@/queue/index.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
@ -34,17 +32,5 @@ export default define(meta, paramDef, async (ps, user) => {
throw new Error('incorrect password'); throw new Error('incorrect password');
} }
// 物理削除する前にDelete activityを送信する await deleteAccount(user);
await doPostSuspend(user).catch(e => {});
createDeleteAccountJob(user, {
soft: false,
});
await Users.update(user.id, {
isDeleted: true,
});
// Terminate streaming
publishUserEvent(user.id, 'terminate', {});
}); });

View file

@ -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<void> {
// 物理削除する前にDelete activityを送信する
await doPostSuspend(user).catch(e => {});
createDeleteAccountJob(user, {
soft: false,
});
await Users.update(user.id, {
isDeleted: true,
});
// Terminate streaming
publishUserEvent(user.id, 'terminate', {});
}

View file

@ -35,7 +35,10 @@
<FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch>
{{ $ts.reflectMayTakeTime }} {{ $ts.reflectMayTakeTime }}
<FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> <div class="_formBlock">
<FormButton v-if="user.host == null && iAmModerator" inline style="margin-right: 8px;" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
<FormButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ $ts.deleteAccount }}</FormButton>
</div>
</FormSection> </FormSection>
<FormSection> <FormSection>
@ -233,6 +236,30 @@ async function deleteAllFiles() {
await refreshUser(); 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, () => { watch(() => props.userId, () => {
init = createFetcher(); init = createFetcher();
}, { }, {