From 0ec6a0fbfbed097b375517c0f8053df910daffd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B4=EB=9D=BC=EC=BF=A0=EB=AA=A8?= Date: Sat, 11 May 2024 21:55:40 +0900 Subject: [PATCH] feat(policies): account removal policy --- locales/index.d.ts | 8 ++++++ locales/ja-JP.yml | 2 ++ locales/ko-KR.yml | 2 ++ packages/backend/src/core/RoleService.ts | 3 +++ .../backend/src/models/json-schema/role.ts | 4 +++ .../server/api/endpoints/i/delete-account.ts | 27 +++++++++++++------ packages/frontend/src/const.ts | 1 + .../frontend/src/pages/settings/other.vue | 7 +++-- packages/misskey-js/src/autogen/types.ts | 1 + 9 files changed, 45 insertions(+), 10 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 59b4a6695..aa4147571 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -6744,6 +6744,10 @@ export interface Locale extends ILocale { * コンテンツの削除 */ "canDeleteContent": string; + /** + * アカウントの削除 + */ + "canUseAccountRemoval": string; /** * 完全なアカウントの削除 */ @@ -7049,6 +7053,10 @@ export interface Locale extends ILocale { * 削除が進行中 */ "inProgress": string; + /** + * 現在、アカウントの削除はできません。 + */ + "youCantUseThisTime": string; }; "_ad": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2736d98f5..a21fa979a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1741,6 +1741,7 @@ _role: canCreateContent: "コンテンツの作成" canUpdateContent: "コンテンツの編集" canDeleteContent: "コンテンツの削除" + canUseAccountRemoval: "アカウントの削除" canPurgeAccount: "完全なアカウントの削除" canUpdateAvatar: "アイコンの変更" canUpdateBanner: "バナーの変更" @@ -1825,6 +1826,7 @@ _accountDelete: requestAccountDelete: "アカウント削除をリクエスト" started: "削除処理が開始されました。" inProgress: "削除が進行中" + youCantUseThisTime: "現在、アカウントの削除はできません。" _ad: back: "戻る" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index f5224f79d..ee51c7ede 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1719,6 +1719,7 @@ _role: canCreateContent: "컨텐츠 생성 허용" canUpdateContent: "컨텐츠 수정 허용" canDeleteContent: "컨텐츠 삭제 허용" + canUseAccountRemoval: "계정 삭제 허용" canPurgeAccount: "완전한 계정 삭제 허용" canUpdateAvatar: "아바타 변경 허용" canUpdateBanner: "배너 변경 허용" @@ -1790,6 +1791,7 @@ _accountDelete: requestAccountDelete: "계정 삭제 요청" started: "삭제 작업이 시작되었습니다." inProgress: "삭제 진행 중" + youCantUseThisTime: "지금은 계정 삭제를 진행할 수 없습니다." _ad: back: "뒤로" reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 102111fd9..8f1daacb4 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -68,6 +68,7 @@ export type RolePolicies = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + canUseAccountRemoval: boolean; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -106,6 +107,7 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, + canUseAccountRemoval: true, }; @Injectable() @@ -389,6 +391,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canCreateContent: calc('canCreateContent', vs => vs.some(v => v === true)), canUpdateContent: calc('canUpdateContent', vs => vs.some(v => v === true)), canDeleteContent: calc('canDeleteContent', vs => vs.some(v => v === true)), + canUseAccountRemoval: calc('canUseAccountRemoval', vs => vs.some(v => v === true)), canPurgeAccount: calc('canPurgeAccount', vs => vs.some(v => v === true)), canUpdateAvatar: calc('canUpdateAvatar', vs => vs.some(v => v === true)), canUpdateBanner: calc('canUpdateBanner', vs => vs.some(v => v === true)), diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index 2963d6659..63dfdcf47 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -308,6 +308,10 @@ export const packedRolePoliciesSchema = { type: 'integer', optional: false, nullable: false, }, + canUseAccountRemoval: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; 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 70037b5dd..c659be4a4 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; +import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { UserAuthService } from '@/core/UserAuthService.js'; import { ApiError } from '@/server/api/error.js'; @@ -18,17 +19,21 @@ export const meta = { secure: true, errors: { - incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: '44326b04-08ea-4525-b01c-98cc117bdd2a', + removalDisabled: { + message: 'Account removal is disabled by your role.', + code: 'REMOVAL_DISABLED', + id: '453d954b-3d8b-4df0-a261-b26ec6660ea3', }, - authenticationFailed: { - message: 'Authentication failed.', + message: 'Your password or 2FA token is invalid.', code: 'AUTHENTICATION_FAILED', id: 'ea791cff-63e7-4b2a-92fc-646ab641794e', }, + alreadyRemoved: { + message: 'Your account is already removed.', + code: 'ACCOUNT_REMOVED', + id: '59b8f0e6-6eb2-4dc1-a080-1de3108416d0', + }, }, } as const; @@ -52,18 +57,24 @@ export default class extends Endpoint { // eslint- private userAuthService: UserAuthService, private deleteAccountService: DeleteAccountService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + const policies = await this.roleService.getUserPolicies(me.id); + if (!policies.canUseAccountRemoval) { + throw new ApiError(meta.errors.removalDisabled); + } + const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id }); if (userDetailed.isDeleted) { - return; + throw new ApiError(meta.errors.alreadyRemoved); } const passwordMatched = await bcrypt.compare(ps.password, profile.password!); if (!passwordMatched) { - throw new ApiError(meta.errors.incorrectPassword); + throw new ApiError(meta.errors.authenticationFailed); } if (profile.twoFactorEnabled) { diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 8521e2a0e..55434aeff 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -107,6 +107,7 @@ export const ROLE_POLICIES = [ 'userEachUserListsLimit', 'rateLimitFactor', 'avatarDecorationLimit', + 'canUseAccountRemoval', ] as const; // なんか動かない diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index c5aab5793..49a8a05ea 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -40,12 +40,15 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
{{ i18n.ts._accountDelete.mayTakeTime }} {{ i18n.ts._accountDelete.sendEmail }} {{ i18n.ts._accountDelete.requestAccountDelete }} {{ i18n.ts._accountDelete.inProgress }}
+
+ {{ i18n.ts._accountDelete.youCantUseThisTime }} +
@@ -105,7 +108,7 @@ import FormSection from '@/components/form/section.vue'; const $i = signinRequired(); -const reportError = computed(defaultStore.makeGetterSetter('reportError')); +// const reportError = computed(defaultStore.makeGetterSetter('reportError')); const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct')); const devMode = computed(defaultStore.makeGetterSetter('devMode')); const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies')); diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index f83f3c7d6..b743313de 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4925,6 +4925,7 @@ export type components = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + canUseAccountRemoval: boolean; }; ReversiGameLite: { /** Format: id */