From 6f8e3fe36633836d36c5447d78636f370b36cd84 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Fri, 24 Jun 2022 19:22:19 +0900 Subject: enhance: Redisをioredisに統一してIPv6サポート (#8869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use ioredis, Supports IPv6 host https://github.com/misskey-dev/misskey/issues/8862 * Fix import * order * a * i * fix * flushdb * family * CHANGELOG * redis_version Co-authored-by: syuilo --- packages/backend/src/server/api/endpoints/admin/server-info.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 9c150420b1..85c6fb82e7 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -99,12 +99,16 @@ export default define(meta, paramDef, async () => { const fsStats = await si.fsSize(); const netInterface = await si.networkInterfaceDefault(); + const redisServerInfo = await redisClient.info('Server'); + const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm')); + const redis_version = m?.[1]; + return { machine: os.hostname(), os: os.platform(), node: process.version, psql: await db.query('SHOW server_version').then(x => x[0].server_version), - redis: redisClient.server_info.redis_version, + redis: redis_version, cpu: { model: os.cpus()[0].model, cores: os.cpus().length, -- cgit v1.2.3-freya From 696e8add005837481bc40fb3cd7cb9392439e0ac Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 24 Jun 2022 21:43:28 +0900 Subject: feat: 管理者が特定ユーザーのアップロードしたファイル一覧を見れるように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- .../src/server/api/endpoints/admin/drive/files.ts | 23 +++--- .../client/src/components/file-list-for-admin.vue | 93 ++++++++++++++++++++++ packages/client/src/pages/admin/files.vue | 82 ++----------------- packages/client/src/pages/user-info.vue | 19 ++++- 5 files changed, 133 insertions(+), 86 deletions(-) create mode 100644 packages/client/src/components/file-list-for-admin.vue (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc75e6f92..bc3e1a4223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ You should also include the user name that made the change. ### Improvements - Server: Add rate limit to i/notifications @tamaina -- Client: Improve files page of control panel @syuilo +- Client: Improve control panel @syuilo - Client: Show warning in control panel when there is an unresolved abuse report @syuilo - Improve player detection in URL preview @mei23 - Add Badge Image to Push Notification #8012 @tamaina diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 119c4db19b..ba32aac431 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import { DriveFiles } from '@/models/index.js'; +import define from '../../../define.js'; import { makePaginationQuery } from '../../../common/make-pagination-query.js'; export const meta = { @@ -25,8 +25,9 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + userId: { type: 'string', format: 'misskey:id', nullable: true }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, + origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, hostname: { type: 'string', nullable: true, @@ -41,14 +42,18 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId); - if (ps.origin === 'local') { - query.andWhere('file.userHost IS NULL'); - } else if (ps.origin === 'remote') { - query.andWhere('file.userHost IS NOT NULL'); - } + if (ps.userId) { + query.andWhere('file.userId = :userId', { userId: ps.userId }); + } else { + if (ps.origin === 'local') { + query.andWhere('file.userHost IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('file.userHost IS NOT NULL'); + } - if (ps.hostname) { - query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); + if (ps.hostname) { + query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); + } } if (ps.type) { diff --git a/packages/client/src/components/file-list-for-admin.vue b/packages/client/src/components/file-list-for-admin.vue new file mode 100644 index 0000000000..992b41a4fc --- /dev/null +++ b/packages/client/src/components/file-list-for-admin.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 18bf4f9a8c..1d037fc4e4 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -16,32 +16,15 @@ -
+
+ + +
- - - +
@@ -56,9 +39,7 @@ import XHeader from './_header_.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; -import MkPagination from '@/components/ui/pagination.vue'; -import MkContainer from '@/components/ui/container.vue'; -import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue'; +import MkFileListForAdmin from '@/components/file-list-for-admin.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -67,12 +48,14 @@ import { definePageMetadata } from '@/scripts/page-metadata'; let origin = $ref('local'); let type = $ref(null); let searchHost = $ref(''); +let userId = $ref(''); let viewMode = $ref('grid'); const pagination = { endpoint: 'admin/drive/files' as const, limit: 10, params: computed(() => ({ type: (type && type !== '') ? type : null, + userId: (userId && userId !== '') ? userId : null, origin: origin, hostname: (searchHost && searchHost !== '') ? searchHost : null, })), @@ -134,54 +117,5 @@ definePageMetadata(computed(() => ({ diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index cc187b9df3..b4c4aedfcd 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -76,6 +76,9 @@ +
+ +
@@ -105,6 +108,7 @@ import FormButton from '@/components/ui/button.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkSelect from '@/components/form/select.vue'; import FormSuspense from '@/components/form/suspense.vue'; +import MkFileListForAdmin from '@/components/file-list-for-admin.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; @@ -127,6 +131,13 @@ let ap = $ref(null); let moderator = $ref(false); let silenced = $ref(false); let suspended = $ref(false); +const filesPagination = { + endpoint: 'admin/drive/files' as const, + limit: 10, + params: computed(() => ({ + userId: props.userId, + })), +}; function createFetcher() { if (iAmModerator) { @@ -244,7 +255,11 @@ const headerTabs = $computed(() => [{ key: 'chart', title: i18n.ts.charts, icon: 'fas fa-chart-simple', -}, { +}, iAmModerator ? { + key: 'files', + title: i18n.ts.files, + icon: 'fas fa-cloud', +} : null, { key: 'ap', title: 'AP', icon: 'fas fa-share-alt', @@ -252,7 +267,7 @@ const headerTabs = $computed(() => [{ key: 'raw', title: 'Raw data', icon: 'fas fa-code', -}]); +}].filter(x => x != null)); definePageMetadata(computed(() => ({ title: user ? acct(user) : i18n.ts.userInfo, -- cgit v1.2.3-freya From 329f055a976dc3b2e12f2a0141bfab2c57ae9193 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 27 Jun 2022 23:49:16 +0900 Subject: 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 ++ .../server/api/endpoints/admin/delete-account.ts | 31 ++++++++++++++++++++++ .../src/server/api/endpoints/i/delete-account.ts | 20 +++----------- packages/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 (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b147ed4b6..e9bb600ff9 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 7788a04dc3..f813389225 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -855,6 +855,8 @@ thereIsUnresolvedAbuseReportWarning: "未対応の通報があります。" recommended: "推奨" check: "チェック" 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 11d9d7c026..93f93cef0c 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 0000000000..2d7ef2f236 --- /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 184005eb53..ede4a9d03b 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 0000000000..0fdceb671b --- /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 86c1be8d06..9dfb2d87a0 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(); }, { -- cgit v1.2.3-freya From b773d516d3a604fb8506c0b5ee449e2461024efa Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 2 Jul 2022 12:22:52 +0900 Subject: chore(client): tweak ui --- locales/ja-JP.yml | 1 + packages/backend/src/server/api/endpoints/admin/show-user.ts | 3 ++- packages/client/src/pages/user-info.vue | 10 +++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1f52c2c259..b97b64dc59 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -861,6 +861,7 @@ document: "ドキュメント" numberOfPageCache: "ページキャッシュ数" numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。" logoutConfirm: "ログアウトしますか?" +lastActiveDate: "最終利用日時" _emailUnavailable: used: "既に使用されています" diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 78033aed58..36384c2b3d 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -25,7 +25,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { const [user, profile] = await Promise.all([ Users.findOneBy({ id: ps.userId }), - UserProfiles.findOneBy({ userId: ps.userId }) + UserProfiles.findOneBy({ userId: ps.userId }), ]); if (user == null || profile == null) { @@ -68,6 +68,7 @@ export default define(meta, paramDef, async (ps, me) => { isModerator: user.isModerator, isSilenced: user.isSilenced, isSuspended: user.isSuspended, + lastActiveDate: user.lastActiveDate, signins, }; }); diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 76b772ece2..b3292290ea 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -1,7 +1,7 @@ @@ -92,6 +93,7 @@ const host = toUnicode(config.host); let hcaptcha = $ref(); let recaptcha = $ref(); +let turnstile = $ref(); let username: string = $ref(''); let password: string = $ref(''); @@ -106,12 +108,14 @@ let submitting: boolean = $ref(false); let ToSAgreement: boolean = $ref(false); let hCaptchaResponse = $ref(null); let reCaptchaResponse = $ref(null); +let turnstileResponse = $ref(null); const shouldDisableSubmitting = $computed((): boolean => { return submitting || instance.tosUrl && !ToSAgreement || instance.enableHcaptcha && !hCaptchaResponse || instance.enableRecaptcha && !reCaptchaResponse || + instance.enableTurnstile && !turnstileResponse || passwordRetypeState === 'not-match'; }); @@ -198,6 +202,7 @@ function onSubmit(): void { invitationCode, 'hcaptcha-response': hCaptchaResponse, 'g-recaptcha-response': reCaptchaResponse, + 'turnstile-response': turnstileResponse, }).then(() => { if (instance.emailRequiredForSignup) { os.alert({ @@ -222,6 +227,7 @@ function onSubmit(): void { submitting = false; hcaptcha.reset?.(); recaptcha.reset?.(); + turnstile.reset?.(); os.alert({ type: 'error', diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 72d5e379de..484a9d1a1a 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -6,6 +6,7 @@ + + {{ i18n.ts.save }}
@@ -61,6 +76,8 @@ let hcaptchaSiteKey: string | null = $ref(null); let hcaptchaSecretKey: string | null = $ref(null); let recaptchaSiteKey: string | null = $ref(null); let recaptchaSecretKey: string | null = $ref(null); +let turnstileSiteKey: string | null = $ref(null); +let turnstileSecretKey: string | null = $ref(null); async function init() { const meta = await os.api('admin/meta'); @@ -68,8 +85,10 @@ async function init() { hcaptchaSecretKey = meta.hcaptchaSecretKey; recaptchaSiteKey = meta.recaptchaSiteKey; recaptchaSecretKey = meta.recaptchaSecretKey; + turnstileSiteKey = meta.turnstileSiteKey; + turnstileSecretKey = meta.turnstileSecretKey; - provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null; + provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null; } function save() { @@ -80,6 +99,9 @@ function save() { enableRecaptcha: provider === 'recaptcha', recaptchaSiteKey, recaptchaSecretKey, + enableTurnstile: provider === 'turnstile', + turnstileSiteKey, + turnstileSecretKey, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 9200b5d547..20f82bba28 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -53,7 +53,7 @@ let view = $ref(null); let el = $ref(null); let pageProps = $ref({}); let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); -let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha; +let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile; let noEmailServer = !instance.enableEmail; let thereIsUnresolvedAbuseReport = $ref(false); let currentPage = $computed(() => router.currentRef.value.child); diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index c36cedb312..65d079c2cf 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -9,6 +9,7 @@ + @@ -120,6 +121,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; let summalyProxy: string = $ref(''); let enableHcaptcha: boolean = $ref(false); let enableRecaptcha: boolean = $ref(false); +let enableTurnstile: boolean = $ref(false); let sensitiveMediaDetection: string = $ref('none'); let sensitiveMediaDetectionSensitivity: number = $ref(0); let setSensitiveFlagAutomatically: boolean = $ref(false); @@ -132,6 +134,7 @@ async function init() { summalyProxy = meta.summalyProxy; enableHcaptcha = meta.enableHcaptcha; enableRecaptcha = meta.enableRecaptcha; + enableTurnstile = meta.enableTurnstile; sensitiveMediaDetection = meta.sensitiveMediaDetection; sensitiveMediaDetectionSensitivity = meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : -- cgit v1.2.3-freya