enhance: コンテンツ削除を制限されていてもアカウントの閉鎖ができるように (MisskeyIO#532)

This commit is contained in:
まっちゃとーにゅ 2024-03-18 13:09:13 +09:00 committed by GitHub
parent 2564fc7346
commit 075ec2d7df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 390 additions and 403 deletions

View file

@ -27,11 +27,11 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
@ -79,7 +79,6 @@ import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
import * as ep___admin_suspendUser from './endpoints/admin/suspend-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_deleteAccount from './endpoints/admin/delete-account.js';
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
@ -413,11 +412,11 @@ const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-de
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
const $admin_drive_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/drive/delete-all-files-of-a-user', useClass: ep___admin_drive_deleteAllFilesOfAUser.default };
const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
@ -465,7 +464,6 @@ const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: e
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default };
const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default };
const $admin_roles_create: Provider = { provide: 'ep:admin/roles/create', useClass: ep___admin_roles_create.default };
const $admin_roles_delete: Provider = { provide: 'ep:admin/roles/delete', useClass: ep___admin_roles_delete.default };
@ -803,11 +801,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_avatarDecorations_delete,
$admin_avatarDecorations_list,
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_deleteAllFilesOfAUser,
$admin_drive_files,
$admin_drive_showFile,
$admin_emoji_addAliasesBulk,
@ -855,7 +853,6 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_suspendUser,
$admin_unsuspendUser,
$admin_updateMeta,
$admin_deleteAccount,
$admin_updateUserNote,
$admin_roles_create,
$admin_roles_delete,
@ -1187,11 +1184,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_avatarDecorations_delete,
$admin_avatarDecorations_list,
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_deleteAllFilesOfAUser,
$admin_drive_files,
$admin_drive_showFile,
$admin_emoji_addAliasesBulk,
@ -1239,7 +1236,6 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_suspendUser,
$admin_unsuspendUser,
$admin_updateMeta,
$admin_deleteAccount,
$admin_updateUserNote,
$admin_roles_create,
$admin_roles_delete,

View file

@ -124,6 +124,13 @@ export class SigninApiService {
});
}
if (user.isDeleted && user.isSuspended) {
logger.error('No such user. (logical deletion)');
return error(404, {
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
});
}
if (user.isSuspended) {
logger.error('User is suspended.');
return error(403, {

View file

@ -27,11 +27,11 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
@ -79,7 +79,6 @@ import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
import * as ep___admin_suspendUser from './endpoints/admin/suspend-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_deleteAccount from './endpoints/admin/delete-account.js';
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
@ -411,11 +410,11 @@ const eps = [
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
['admin/unset-user-banner', ep___admin_unsetUserBanner],
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
['admin/drive/cleanup', ep___admin_drive_cleanup],
['admin/drive/delete-all-files-of-a-user', ep___admin_drive_deleteAllFilesOfAUser],
['admin/drive/files', ep___admin_drive_files],
['admin/drive/show-file', ep___admin_drive_showFile],
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
@ -463,7 +462,6 @@ const eps = [
['admin/suspend-user', ep___admin_suspendUser],
['admin/unsuspend-user', ep___admin_unsuspendUser],
['admin/update-meta', ep___admin_updateMeta],
['admin/delete-account', ep___admin_deleteAccount],
['admin/update-user-note', ep___admin_updateUserNote],
['admin/roles/create', ep___admin_roles_create],
['admin/roles/delete', ep___admin_roles_delete],

View file

@ -5,11 +5,11 @@
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 type { UsersRepository } from '@/models/_.js';
import { RoleService } from '@/core/RoleService.js';
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['admin'],
@ -17,6 +17,20 @@ export const meta = {
requireCredential: true,
requireAdmin: true,
kind: 'write:admin:account',
errors: {
userNotFound: {
message: 'User not found.',
code: 'USER_NOT_FOUND',
id: '6c45276a-525e-46b0-892f-17a5036258bf',
},
cannotDeleteModerator: {
message: 'Cannot delete a moderator.',
code: 'CANNOT_DELETE_MODERATOR',
id: 'd195c621-f21a-4c2f-a634-484c2a616311',
},
},
} as const;
export const paramDef = {
@ -33,37 +47,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private userEntityService: UserEntityService,
private queueService: QueueService,
private userSuspendService: UserSuspendService,
private roleService: RoleService,
private deleteAccountService: DeleteAccountService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
throw new Error('user not found');
}
if (user == null) throw new ApiError(meta.errors.userNotFound);
if (await this.roleService.isModerator(user)) throw new ApiError(meta.errors.cannotDeleteModerator);
if (user.isRoot) {
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,
});
// 管理者からの削除ということはモデレーション行為なので、soft delete にする
await this.deleteAccountService.deleteAccount(user, true, me);
});
}
}

View file

@ -1,46 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DriveService } from '@/core/DriveService.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
kind: 'write:admin:delete-all-files-of-a-user',
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
const files = await this.driveFilesRepository.findBy({
userId: ps.userId,
});
for (const file of files) {
this.driveService.deleteFile(file);
}
});
}
}

View file

@ -4,17 +4,17 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/_.js';
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
kind: 'write:admin:delete-account',
requireModerator: true,
kind: 'write:admin:drive',
} as const;
export const paramDef = {
@ -33,13 +33,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private deleteAccountService: DeleteAccountService,
) {
super(meta, paramDef, async (ps) => {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneByOrFail({ id: ps.userId });
if (user.isDeleted) {
return;
}
await this.deleteAccountService.deleteAccount(user);
await this.deleteAccountService.deleteAllDriveFiles(user, me);
});
}
}

View file

@ -115,6 +115,14 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
isLimited: {
type: 'boolean',
optional: false, nullable: false,
},
isDeleted: {
type: 'boolean',
optional: false, nullable: false,
},
isSuspended: {
type: 'boolean',
optional: false, nullable: false,
@ -242,6 +250,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isModerator: isModerator,
isSilenced: isSilenced,
isLimited: isLimited,
isDeleted: user.isDeleted,
isSuspended: user.isSuspended,
isHibernated: user.isHibernated,
lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null,

View file

@ -14,7 +14,6 @@ import { ApiError } from '@/server/api/error.js';
export const meta = {
requireCredential: true,
requireRolePolicy: 'canDeleteContent',
secure: true,
@ -76,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userAuthService.twoFactorAuthenticate(profile, token);
}
await this.deleteAccountService.deleteAccount(me);
await this.deleteAccountService.deleteAccount(me, false, me);
});
}
}