feat(server): 管理者用アカウント削除API実装

動作確認済み
Resolve #7735
This commit is contained in:
syuilo 2021-09-22 17:34:48 +09:00
parent 72a49f334a
commit 9208825975
6 changed files with 80 additions and 7 deletions

View file

@ -13,6 +13,8 @@
- ActivityPub: リモートユーザーのDeleteアクティビティに対応 - ActivityPub: リモートユーザーのDeleteアクティビティに対応
- ActivityPub: add resolver check for blocked instance - ActivityPub: add resolver check for blocked instance
- ActivityPub: deliverキューのメモリ使用量を削減 - ActivityPub: deliverキューのメモリ使用量を削減
- API: 管理者用アカウント削除APIを実装(/admin/accounts/delete)
- リモートユーザーの削除も可能に
- アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように - アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように
- 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように - 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように
- リスト、アンテナタイムラインを個別ページとして分割 - リスト、アンテナタイムラインを個別ページとして分割

View file

@ -173,9 +173,10 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']
}); });
} }
export function createDeleteAccountJob(user: ThinUser) { export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; }) {
return dbQueue.add('deleteAccount', { return dbQueue.add('deleteAccount', {
user: user user: user,
soft: opts.soft
}, { }, {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true removeOnFail: true

View file

@ -1,7 +1,7 @@
import * as Bull from 'bull'; import * as Bull from 'bull';
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import { DriveFiles, Notes, UserProfiles, Users } from '@/models/index'; import { DriveFiles, Notes, UserProfiles, Users } from '@/models/index';
import { DbUserJobData } from '@/queue/types'; import { DbUserDeleteJobData } from '@/queue/types';
import { Note } from '@/models/entities/note'; import { Note } from '@/models/entities/note';
import { DriveFile } from '@/models/entities/drive-file'; import { DriveFile } from '@/models/entities/drive-file';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
@ -10,7 +10,7 @@ import { sendEmail } from '@/services/send-email';
const logger = queueLogger.createSubLogger('delete-account'); const logger = queueLogger.createSubLogger('delete-account');
export async function deleteAccount(job: Bull.Job<DbUserJobData>): Promise<string | void> { export async function deleteAccount(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
logger.info(`Deleting account of ${job.data.user.id} ...`); logger.info(`Deleting account of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);
@ -83,7 +83,12 @@ export async function deleteAccount(job: Bull.Job<DbUserJobData>): Promise<strin
} }
} }
await Users.delete(job.data.user.id); // soft指定されている場合は物理削除しない
if (job.data.soft) {
// nop
} else {
await Users.delete(job.data.user.id);
}
return 'Account deleted'; return 'Account deleted';
} }

View file

@ -17,12 +17,17 @@ export type InboxJobData = {
signature: httpSignature.IParsedSignature; signature: httpSignature.IParsedSignature;
}; };
export type DbJobData = DbUserJobData | DbUserImportJobData; export type DbJobData = DbUserJobData | DbUserImportJobData | DbUserDeleteJobData;
export type DbUserJobData = { export type DbUserJobData = {
user: ThinUser; user: ThinUser;
}; };
export type DbUserDeleteJobData = {
user: ThinUser;
soft?: boolean;
};
export type DbUserImportJobData = { export type DbUserImportJobData = {
user: ThinUser; user: ThinUser;
fileId: DriveFile['id']; fileId: DriveFile['id'];

View file

@ -0,0 +1,58 @@
import $ from 'cafy';
import define from '../../../define';
import { Users } from '@/models/index';
import { doPostSuspend } from '@/services/suspend-user';
import { publishUserEvent } from '@/services/stream';
import { createDeleteAccountJob } from '@/queue';
import { ID } from '@/misc/cafy-id';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireModerator: true,
params: {
userId: {
validator: $.type(ID),
},
}
};
export default define(meta, async (ps, me) => {
const user = await Users.findOne(ps.userId);
if (user == null) {
throw new Error('user not found');
}
if (user.isAdmin) {
throw new Error('cannot suspend admin');
}
if (user.isModerator) {
throw new Error('cannot suspend moderator');
}
if (Users.isLocalUser(user)) {
// 物理削除する前にDelete activityを送信する
await doPostSuspend(user).catch(e => {});
createDeleteAccountJob(user, {
soft: false
});
} else {
createDeleteAccountJob(user, {
soft: true // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する
});
}
await Users.update(user.id, {
isDeleted: true,
});
if (Users.isLocalUser(user)) {
// Terminate streaming
publishUserEvent(user.id, 'terminate', {});
}
});

View file

@ -35,7 +35,9 @@ export default define(meta, async (ps, user) => {
// 物理削除する前にDelete activityを送信する // 物理削除する前にDelete activityを送信する
await doPostSuspend(user).catch(e => {}); await doPostSuspend(user).catch(e => {});
createDeleteAccountJob(user); createDeleteAccountJob(user, {
soft: false
});
await Users.update(user.id, { await Users.update(user.id, {
isDeleted: true, isDeleted: true,