From 14d71e20643a25fcc8acb5f94409c02f19853983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=82=8F=E3=82=8F=E3=82=8F=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:39:19 +0900 Subject: [PATCH 01/17] =?UTF-8?q?spec(SSO/JWT):=20=E3=83=88=E3=83=BC?= =?UTF-8?q?=E3=82=AF=E3=83=B3=E3=81=AE=E6=9C=89=E5=8A=B9=E6=9C=9F=E9=99=90?= =?UTF-8?q?=E3=82=922=E9=80=B1=E9=96=93=E3=81=AB=E5=A4=89=E6=9B=B4=20(Miss?= =?UTF-8?q?keyIO#746)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/sso/JWTIdentifyProviderService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/sso/JWTIdentifyProviderService.ts b/packages/backend/src/server/sso/JWTIdentifyProviderService.ts index f73966d5b51e..bb21a8e9874a 100644 --- a/packages/backend/src/server/sso/JWTIdentifyProviderService.ts +++ b/packages/backend/src/server/sso/JWTIdentifyProviderService.ts @@ -203,7 +203,7 @@ export class JWTIdentifyProviderService { .setIssuer(ssoServiceProvider.issuer) .setAudience(ssoServiceProvider.audience) .setIssuedAt() - .setExpirationTime('10m') + .setExpirationTime('2w') .setJti(randomUUID()) .setSubject(user.id) .encrypt(key); @@ -220,7 +220,7 @@ export class JWTIdentifyProviderService { .setIssuer(ssoServiceProvider.issuer) .setAudience(ssoServiceProvider.audience) .setIssuedAt() - .setExpirationTime('10m') + .setExpirationTime('2w') .setJti(randomUUID()) .setSubject(user.id) .sign(key); From 227c85c2cfaf0001287632fc14aae060a674fe5d Mon Sep 17 00:00:00 2001 From: riku6460 <17585784+riku6460@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:40:15 +0900 Subject: [PATCH 02/17] =?UTF-8?q?enhance(queue):=20deliver=20queue=20?= =?UTF-8?q?=E3=82=92=E8=A4=87=E6=95=B0=E5=80=8B=E8=A8=AD=E5=AE=9A=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=20(MisskeyIO#745)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/config.ts | 6 +- packages/backend/src/core/QueueModule.ts | 5 +- packages/backend/src/misc/queues.ts | 69 +++++++++++++++++++ .../src/queue/QueueProcessorService.ts | 50 +++++++------- .../src/server/web/ClientServerService.ts | 4 +- packages/frontend/vite.config.local-dev.ts | 1 + 6 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 packages/backend/src/misc/queues.ts diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 918fa60216f6..712ee5442221 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -50,7 +50,7 @@ type Source = { redisForJobQueue?: RedisOptionsSource; redisForSystemQueue?: RedisOptionsSource; redisForEndedPollNotificationQueue?: RedisOptionsSource; - redisForDeliverQueue?: RedisOptionsSource; + redisForDeliverQueues?: Array; redisForInboxQueue?: RedisOptionsSource; redisForDbQueue?: RedisOptionsSource; redisForRelationshipQueue?: RedisOptionsSource; @@ -220,7 +220,7 @@ export type Config = { redisForPubsub: RedisOptions & RedisOptionsSource; redisForSystemQueue: RedisOptions & RedisOptionsSource; redisForEndedPollNotificationQueue: RedisOptions & RedisOptionsSource; - redisForDeliverQueue: RedisOptions & RedisOptionsSource; + redisForDeliverQueues: Array; redisForInboxQueue: RedisOptions & RedisOptionsSource; redisForDbQueue: RedisOptions & RedisOptionsSource; redisForRelationshipQueue: RedisOptions & RedisOptionsSource; @@ -296,7 +296,7 @@ export function loadConfig(): Config { redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, redisForSystemQueue: config.redisForSystemQueue ? convertRedisOptions(config.redisForSystemQueue, host) : redisForJobQueue, redisForEndedPollNotificationQueue: config.redisForEndedPollNotificationQueue ? convertRedisOptions(config.redisForEndedPollNotificationQueue, host) : redisForJobQueue, - redisForDeliverQueue: config.redisForDeliverQueue ? convertRedisOptions(config.redisForDeliverQueue, host) : redisForJobQueue, + redisForDeliverQueues: config.redisForDeliverQueues ? config.redisForDeliverQueues.map(config => convertRedisOptions(config, host)) : [redisForJobQueue], redisForInboxQueue: config.redisForInboxQueue ? convertRedisOptions(config.redisForInboxQueue, host) : redisForJobQueue, redisForDbQueue: config.redisForDbQueue ? convertRedisOptions(config.redisForDbQueue, host) : redisForJobQueue, redisForRelationshipQueue: config.redisForRelationshipQueue ? convertRedisOptions(config.redisForRelationshipQueue, host) : redisForJobQueue, diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts index 1537deca64ce..85ea2f415a93 100644 --- a/packages/backend/src/core/QueueModule.ts +++ b/packages/backend/src/core/QueueModule.ts @@ -9,12 +9,13 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { QUEUE, baseQueueOptions } from '@/queue/const.js'; import { allSettled } from '@/misc/promise-tracker.js'; +import { Queues } from '@/misc/queues.js'; import type { Provider } from '@nestjs/common'; import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js'; export type SystemQueue = Bull.Queue>; export type EndedPollNotificationQueue = Bull.Queue; -export type DeliverQueue = Bull.Queue; +export type DeliverQueue = Queues; export type InboxQueue = Bull.Queue; export type DbQueue = Bull.Queue; export type RelationshipQueue = Bull.Queue; @@ -35,7 +36,7 @@ const $endedPollNotification: Provider = { const $deliver: Provider = { provide: 'queue:deliver', - useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config.redisForDeliverQueue, config.bullmqQueueOptions, QUEUE.DELIVER)), + useFactory: (config: Config) => new Queues(config.redisForDeliverQueues.map(queueConfig => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(queueConfig, config.bullmqQueueOptions, QUEUE.DELIVER)))), inject: [DI.config], }; diff --git a/packages/backend/src/misc/queues.ts b/packages/backend/src/misc/queues.ts new file mode 100644 index 000000000000..6a6fb9ba6148 --- /dev/null +++ b/packages/backend/src/misc/queues.ts @@ -0,0 +1,69 @@ +import { EventEmitter } from 'node:events'; +import * as Bull from 'bullmq'; + +export class Queues { + public readonly queues: ReadonlyArray>; + + constructor(queues: Bull.Queue[]) { + if (queues.length === 0) { + throw new Error('queues cannot be empty.'); + } + this.queues = queues; + } + + getRandomQueue(): Bull.Queue { + return this.queues[Math.floor(Math.random() * this.queues.length)]; + } + + add(name: NameType, data: DataType, opts?: Bull.JobsOptions): Promise> { + return this.getRandomQueue().add(name, data, opts); + } + + async addBulk(jobs: { name: NameType; data: DataType; opts?: Bull.BulkJobOptions }[]): Promise[]> { + return (await Promise.allSettled(jobs.map(job => this.add(job.name, job.data, job.opts)))) + .filter((value): value is PromiseFulfilledResult> => value.status === 'fulfilled') + .flatMap(value => value.value); + } + + async close(): Promise { + await Promise.allSettled(this.queues.map(queue => queue.close())); + } + + async getDelayed(start?: number, end?: number): Promise[]> { + return (await Promise.allSettled(this.queues.map(queue => queue.getDelayed(start, end)))) + .filter((value): value is PromiseFulfilledResult[]> => value.status === 'fulfilled') + .flatMap(value => value.value); + } + + async getJobCounts(...types: Bull.JobType[]): Promise<{ [p: string]: number }> { + return (await Promise.allSettled(this.queues.map(queue => queue.getJobCounts(...types)))) + .filter((value): value is PromiseFulfilledResult> => value.status === 'fulfilled') + .reduce((previousValue, currentValue) => { + for (const key in currentValue.value) { + previousValue[key] = (previousValue[key] || 0) + currentValue.value[key]; + } + return previousValue; + }, {} as Record); + } + + once>(event: U, listener: Bull.QueueListener[U]): void { + const e = new EventEmitter(); + e.once(event, listener); + + const listener1 = (...args: any[]) => e.emit(event, ...args); + this.queues.forEach(queue => queue.once(event, listener1)); + e.once(event, () => this.queues.forEach(queue => queue.off(event, listener1))); + } + + async clean(grace: number, limit: number, type?: 'completed' | 'wait' | 'active' | 'paused' | 'prioritized' | 'delayed' | 'failed'): Promise { + return (await Promise.allSettled(this.queues.map(queue => queue.clean(grace, limit, type)))) + .filter((value): value is PromiseFulfilledResult => value.status === 'fulfilled') + .flatMap(value => value.value); + } + + async getJobs(types?: Bull.JobType[] | Bull.JobType, start?: number, end?: number, asc?: boolean): Promise[]> { + return (await Promise.allSettled(this.queues.map(queue => queue.getJobs(types, start, end, asc)))) + .filter((value): value is PromiseFulfilledResult[]> => value.status === 'fulfilled') + .flatMap(value => value.value); + } +} diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 738ba9e25464..e1b369ab888c 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -75,7 +75,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private logger: Logger; private systemQueueWorker: Bull.Worker; private dbQueueWorker: Bull.Worker; - private deliverQueueWorker: Bull.Worker; + private deliverQueueWorkers: Bull.Worker[]; private inboxQueueWorker: Bull.Worker; private webhookDeliverQueueWorker: Bull.Worker; private relationshipQueueWorker: Bull.Worker; @@ -206,27 +206,31 @@ export class QueueProcessorService implements OnApplicationShutdown { //#endregion //#region deliver - this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), { - ...baseWorkerOptions(this.config.redisForDeliverQueue, this.config.bullmqWorkerOptions, QUEUE.DELIVER), - autorun: false, - concurrency: this.config.deliverJobConcurrency ?? 128, - limiter: { - max: this.config.deliverJobPerSec ?? 128, - duration: 1000, - }, - settings: { - backoffStrategy: httpRelatedBackoff, - }, + this.deliverQueueWorkers = this.config.redisForDeliverQueues + .filter((_, index) => process.env.QUEUE_WORKER_INDEX == null || index === Number.parseInt(process.env.QUEUE_WORKER_INDEX, 10)) + .map(config => new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), { + ...baseWorkerOptions(config, this.config.bullmqWorkerOptions, QUEUE.DELIVER), + autorun: false, + concurrency: this.config.deliverJobConcurrency ?? 128, + limiter: { + max: this.config.deliverJobPerSec ?? 128, + duration: 1000, + }, + settings: { + backoffStrategy: httpRelatedBackoff, + }, + })); + + this.deliverQueueWorkers.forEach((worker, index) => { + const deliverLogger = this.logger.createSubLogger(`deliver-${index}`); + + worker + .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) + .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) + .on('failed', (job, err) => deliverLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) + .on('error', (err: Error) => deliverLogger.error(`error ${err.stack}`, { error: renderError(err) })) + .on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`)); }); - - const deliverLogger = this.logger.createSubLogger('deliver'); - - this.deliverQueueWorker - .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => deliverLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) - .on('error', (err: Error) => deliverLogger.error(`error ${err.stack}`, { error: renderError(err) })) - .on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`)); //#endregion //#region inbox @@ -342,7 +346,7 @@ export class QueueProcessorService implements OnApplicationShutdown { await Promise.all([ this.systemQueueWorker.run(), this.dbQueueWorker.run(), - this.deliverQueueWorker.run(), + ...this.deliverQueueWorkers.map(worker => worker.run()), this.inboxQueueWorker.run(), this.webhookDeliverQueueWorker.run(), this.relationshipQueueWorker.run(), @@ -356,7 +360,7 @@ export class QueueProcessorService implements OnApplicationShutdown { await Promise.all([ this.systemQueueWorker.close(), this.dbQueueWorker.close(), - this.deliverQueueWorker.close(), + ...this.deliverQueueWorkers.map(worker => worker.close()), this.inboxQueueWorker.close(), this.webhookDeliverQueueWorker.close(), this.relationshipQueueWorker.close(), diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 027fe75ddc28..a0b0d603855a 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -235,12 +235,12 @@ export class ClientServerService { queues: [ this.systemQueue, this.endedPollNotificationQueue, - this.deliverQueue, this.inboxQueue, this.dbQueue, this.objectStorageQueue, this.webhookDeliverQueue, - ].map(q => new BullMQAdapter(q)), + ].map(q => new BullMQAdapter(q)) + .concat(this.deliverQueue.queues.map((q, index) => new BullMQAdapter(q, { prefix: `${index}-` }))), serverAdapter, }); diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts index 589e7c3dc210..04e8210834be 100644 --- a/packages/frontend/vite.config.local-dev.ts +++ b/packages/frontend/vite.config.local-dev.ts @@ -53,6 +53,7 @@ const devConfig = { '/cli': httpUrl, '/inbox': httpUrl, '/emoji/': httpUrl, + '/queue': httpUrl, '/notes': { target: httpUrl, headers: { From 8706fa0747d2aa517b176ceac7b601d53e7e731f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=82=8F=E3=82=8F=E3=82=8F=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:53:33 +0900 Subject: [PATCH 03/17] =?UTF-8?q?feat(moderation):=20=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=82=BF=E3=83=BC=E3=81=8C=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=AE=E5=90=8D=E5=89=8D=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=EF=BC=86=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3=E3=82=92=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E6=88=90=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(MisskeyIO#747)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ca-ES.yml | 4 +- locales/en-US.yml | 12 +- locales/es-ES.yml | 4 +- locales/id-ID.yml | 4 +- locales/index.d.ts | 24 +++- locales/it-IT.yml | 4 +- locales/ja-JP.yml | 12 +- locales/ja-KS.yml | 4 +- locales/ko-KR.yml | 8 +- locales/th-TH.yml | 4 +- locales/zh-CN.yml | 4 +- locales/zh-TW.yml | 4 +- .../backend/src/server/api/EndpointsModule.ts | 8 ++ packages/backend/src/server/api/endpoints.ts | 4 + .../endpoints/admin/regenerate-user-token.ts | 61 ++++++++ .../api/endpoints/admin/unset-user-avatar.ts | 2 +- .../api/endpoints/admin/unset-user-banner.ts | 2 +- .../endpoints/admin/unset-user-mutual-link.ts | 2 +- .../api/endpoints/admin/update-user-name.ts | 53 +++++++ packages/backend/src/types.ts | 14 ++ packages/frontend/src/pages/admin-user.vue | 32 ++++- .../src/pages/admin/modlog.ModLog.vue | 13 +- packages/misskey-js/etc/misskey-js.api.md | 18 ++- .../misskey-js/src/autogen/apiClientJSDoc.ts | 28 +++- packages/misskey-js/src/autogen/endpoint.ts | 4 + packages/misskey-js/src/autogen/entities.ts | 2 + packages/misskey-js/src/autogen/types.ts | 135 +++++++++++++++++- packages/misskey-js/src/consts.ts | 22 ++- packages/misskey-js/src/entities.ts | 6 + 29 files changed, 444 insertions(+), 50 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/admin/regenerate-user-token.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/update-user-name.ts diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index d035555c73af..3989868cee2d 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -2003,8 +2003,8 @@ _permissions: "read:admin:show-user": "Veure informació privada de l'usuari " "read:admin:show-users": "Veure informació privada de l'usuari " "write:admin:suspend-user": "Suspendre usuari" - "write:admin:unset-user-avatar": "Esborrar avatar d'usuari " - "write:admin:unset-user-banner": "Esborrar bàner de l'usuari " + "write:admin:user-avatar": "Esborrar avatar d'usuari " + "write:admin:user-banner": "Esborrar bàner de l'usuari " "write:admin:unsuspend-user": "Treure la suspensió d'un usuari" "write:admin:meta": "Gestionar les metadades de la instància" "write:admin:user-note": "Gestionar les notes de moderació " diff --git a/locales/en-US.yml b/locales/en-US.yml index 7265b9ee879c..5f62bc8ebc37 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -693,6 +693,7 @@ useGlobalSetting: "Use global settings" useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made." other: "Other" regenerateLoginToken: "Regenerate login token" +regenerateLoginTokenConfirm: "Are you sure you want to regenerate the login token? All devices will be logged out." regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." theKeywordWhenSearchingForCustomEmoji: "This is the keyword when searching for custom emojis." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." @@ -1060,7 +1061,7 @@ likeOnlyForRemote: "All (Only likes for remote instances)" nonSensitiveOnly: "Non-sensitive only" nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non-sensitive only (Only likes from remote)" rolesAssignedToMe: "Roles assigned to me" -resetPasswordConfirm: "Really reset your password?" +resetPasswordConfirm: "Are you sure you want to reset password?" sensitiveWords: "Sensitive words" sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks." sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression." @@ -1268,6 +1269,7 @@ inquiry: "Contact" here: "here" mutualLink: "Mutual Link" saveThisFile: "Save this file to Drive" +changeUserName: "Change name" _bubbleGame: howToPlay: "How to play" hold: "Hold" @@ -2111,6 +2113,7 @@ _permissions: "read:admin:user-ips": "View user IP addresses" "read:admin:meta": "View instance metadata" "write:admin:reset-password": "Reset user password" + "write:admin:regenerate-user-token": "Regenerate user login token" "write:admin:resolve-abuse-user-report": "Resolve user report" "write:admin:send-email": "Send email" "read:admin:server-info": "View server info" @@ -2118,11 +2121,12 @@ _permissions: "read:admin:show-user": "View private user info" "read:admin:show-users": "View private user info" "write:admin:suspend-user": "Suspend user" - "write:admin:unset-user-avatar": "Remove user avatar" - "write:admin:unset-user-banner": "Remove user banner" - "write:admin:unset-user-mutual-link": "Remove user mutual link" + "write:admin:user-avatar": "Remove user avatar" + "write:admin:user-banner": "Remove user banner" + "write:admin:user-mutual-link": "Remove user mutual link" "write:admin:unsuspend-user": "Unsuspend user" "write:admin:meta": "Manage instance metadata" + "write:admin:user-name": "Change user name" "write:admin:user-note": "Manage moderation note" "write:admin:roles": "Manage roles" "read:admin:roles": "View roles" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 2e05364c312f..61cdb4f06f01 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -2031,8 +2031,8 @@ _permissions: "read:admin:show-user": "Ver información privada de usuario" "read:admin:show-users": "Ver información privada de usuario" "write:admin:suspend-user": "Suspender cuentas de usuario" - "write:admin:unset-user-avatar": "Quitar avatares de usuario" - "write:admin:unset-user-banner": "Quitar banner de usuarios" + "write:admin:user-avatar": "Quitar avatares de usuario" + "write:admin:user-banner": "Quitar banner de usuarios" "write:admin:unsuspend-user": "Quitar suspensión de cuentas de usuario" "write:admin:meta": "Edición de metadatos de la instancia" "write:admin:user-note": "Moderación de notas" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index f8e645d63b57..c93824ccba11 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -2034,8 +2034,8 @@ _permissions: "read:admin:show-user": "Lihat informasi pengguna privat" "read:admin:show-users": "Lihat informasi pengguna privat" "write:admin:suspend-user": "Tangguhkan pengguna" - "write:admin:unset-user-avatar": "Hapus avatar pengguna" - "write:admin:unset-user-banner": "Hapus banner pengguna" + "write:admin:user-avatar": "Hapus avatar pengguna" + "write:admin:user-banner": "Hapus banner pengguna" "write:admin:unsuspend-user": "Batalkan penangguhan pengguna" "write:admin:meta": "Kelola metadata instansi" "write:admin:user-note": "Kelola moderasi catatan" diff --git a/locales/index.d.ts b/locales/index.d.ts index 69afc03744f4..5da7951fbb5a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2796,6 +2796,10 @@ export interface Locale extends ILocale { * ログイントークンを再生成 */ "regenerateLoginToken": string; + /** + * ログイントークンを再生成しますか? + */ + "regenerateLoginTokenConfirm": string; /** * ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。 */ @@ -4268,7 +4272,7 @@ export interface Locale extends ILocale { */ "rolesAssignedToMe": string; /** - * パスワードリセットしますか? + * パスワードをリセットしますか? */ "resetPasswordConfirm": string; /** @@ -5130,6 +5134,10 @@ export interface Locale extends ILocale { * このファイルをドライブに保存する */ "saveThisFile": string; + /** + * 名前を変更 + */ + "changeUserName": string; "_bubbleGame": { /** * 遊び方 @@ -8235,6 +8243,10 @@ export interface Locale extends ILocale { * ユーザーのパスワードをリセットする */ "write:admin:reset-password": string; + /** + * ユーザーのログイントークンを再生成する + */ + "write:admin:regenerate-user-token": string; /** * ユーザーからの通報を解決する */ @@ -8266,15 +8278,15 @@ export interface Locale extends ILocale { /** * ユーザーのアバターを削除する */ - "write:admin:unset-user-avatar": string; + "write:admin:user-avatar": string; /** * ユーザーのバーナーを削除する */ - "write:admin:unset-user-banner": string; + "write:admin:user-banner": string; /** * ユーザーの相互リンクを削除する */ - "write:admin:unset-user-mutual-link": string; + "write:admin:user-mutual-link": string; /** * ユーザーの凍結を解除する */ @@ -8283,6 +8295,10 @@ export interface Locale extends ILocale { * インスタンスのメタデータを操作する */ "write:admin:meta": string; + /** + * ユーザーの名前を変更する + */ + "write:admin:user-name": string; /** * モデレーションノートを操作する */ diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 0a250a2e289e..e563a1779383 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -2027,8 +2027,8 @@ _permissions: "read:admin:show-user": "Vedere le informazioni private degli account utente" "read:admin:show-users": "Vedere le informazioni private degli account utente" "write:admin:suspend-user": "Sospendere i profili" - "write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili" - "write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili" + "write:admin:user-avatar": "Rimuovere la foto profilo dai profili" + "write:admin:user-banner": "Rimuovere l'immagine testata dai profili" "write:admin:unsuspend-user": "Togliere la sospensione ai profili" "write:admin:meta": "Modificare i metadati dell'istanza" "write:admin:user-note": "Scrivere annotazioni di moderazione" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b01bcd90aabc..76611df5895a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -695,6 +695,7 @@ useGlobalSetting: "グローバル設定を使う" useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。" other: "その他" regenerateLoginToken: "ログイントークンを再生成" +regenerateLoginTokenConfirm: "ログイントークンを再生成しますか?" regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。" theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を検索する時のキーワードになります。" setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。" @@ -1062,7 +1063,7 @@ likeOnlyForRemote: "全て (リモートはいいねのみ)" nonSensitiveOnly: "非センシティブのみ" nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)" rolesAssignedToMe: "自分に割り当てられたロール" -resetPasswordConfirm: "パスワードリセットしますか?" +resetPasswordConfirm: "パスワードをリセットしますか?" sensitiveWords: "センシティブワード" sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。" sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。" @@ -1277,6 +1278,7 @@ muteThisUser: "このユーザーをミュートする" here: "こちら" mutualLink: "相互リンク" saveThisFile: "このファイルをドライブに保存する" +changeUserName: "名前を変更" _bubbleGame: howToPlay: "遊び方" @@ -2157,6 +2159,7 @@ _permissions: "read:admin:user-ips": "ユーザーのIPアドレスを見る" "read:admin:meta": "インスタンスのメタデータを見る" "write:admin:reset-password": "ユーザーのパスワードをリセットする" + "write:admin:regenerate-user-token": "ユーザーのログイントークンを再生成する" "write:admin:resolve-abuse-user-report": "ユーザーからの通報を解決する" "write:admin:send-email": "メールを送る" "read:admin:server-info": "サーバーの情報を見る" @@ -2164,11 +2167,12 @@ _permissions: "read:admin:show-user": "ユーザーのプライベートな情報を見る" "read:admin:show-users": "ユーザーのプライベートな情報を見る" "write:admin:suspend-user": "ユーザーを凍結する" - "write:admin:unset-user-avatar": "ユーザーのアバターを削除する" - "write:admin:unset-user-banner": "ユーザーのバーナーを削除する" - "write:admin:unset-user-mutual-link": "ユーザーの相互リンクを削除する" + "write:admin:user-avatar": "ユーザーのアバターを削除する" + "write:admin:user-banner": "ユーザーのバーナーを削除する" + "write:admin:user-mutual-link": "ユーザーの相互リンクを削除する" "write:admin:unsuspend-user": "ユーザーの凍結を解除する" "write:admin:meta": "インスタンスのメタデータを操作する" + "write:admin:user-name": "ユーザーの名前を変更する" "write:admin:user-note": "モデレーションノートを操作する" "write:admin:roles": "ロールを操作する" "read:admin:roles": "ロールを見る" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 301520ff5be9..1602d4f3a2dd 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -2034,8 +2034,8 @@ _permissions: "read:admin:show-user": "ユーザーのプライベートな情報見る" "read:admin:show-users": "ユーザーのプライベートな情報見る" "write:admin:suspend-user": "ユーザーを凍結" - "write:admin:unset-user-avatar": "ユーザーのアバターを削除" - "write:admin:unset-user-banner": "ユーザーのバナーを削除" + "write:admin:user-avatar": "ユーザーのアバターを削除" + "write:admin:user-banner": "ユーザーのバナーを削除" "write:admin:unsuspend-user": "ユーザーの凍結解除" "write:admin:meta": "インスタンスのメタデータいじる" "write:admin:user-note": "モデレーションノートいじる" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 4a6d505513cd..30daa72aa61b 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -692,6 +692,7 @@ useGlobalSetting: "글로벌 설정을 사용하기" useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용됩니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다." other: "기타" regenerateLoginToken: "로그인 토큰을 재생성" +regenerateLoginTokenConfirm: "정말 로그인 토큰을 재생성하시겠습니까? 이 작업을 실행하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다." regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다." theKeywordWhenSearchingForCustomEmoji: "맞춤 이모티콘을 검색할 때 키워드가 됩니다." setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다." @@ -1261,6 +1262,7 @@ useNativeUIForVideoAudioPlayer: "브라우저 UI에서 미디어 재생" here: "여기" mutualLink: "서로링크" saveThisFile: "이 파일을 드라이브에 저장" +changeUserName: "이름 변경" _bubbleGame: howToPlay: "설명" hold: "홀드" @@ -2102,9 +2104,9 @@ _permissions: "read:admin:show-user": "사용자 개인정보 보기" "read:admin:show-users": "사용자 개인정보 보기" "write:admin:suspend-user": "사용자 정지하기" - "write:admin:unset-user-avatar": "사용자 아바타 삭제하기" - "write:admin:unset-user-banner": "사용자 배너 삭제하기" - "write:admin:unset-user-mutual-link": "사용자의 서로링크 삭제하기" + "write:admin:user-avatar": "사용자 아바타 삭제하기" + "write:admin:user-banner": "사용자 배너 삭제하기" + "write:admin:user-mutual-link": "사용자의 서로링크 삭제하기" "write:admin:unsuspend-user": "사용자 정지 해제하기" "write:admin:meta": "인스턴스 메타데이터 수정하기" "write:admin:user-note": "조정 기록 수정하기" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 020b95485475..847e5d354540 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -2034,8 +2034,8 @@ _permissions: "read:admin:show-user": "ดูข้อมูลส่วนตัวของผู้ใช้" "read:admin:show-users": "ดูข้อมูลส่วนตัวของผู้ใช้" "write:admin:suspend-user": "ระงับผู้ใช้" - "write:admin:unset-user-avatar": "ลบอวตารผู้ใช้" - "write:admin:unset-user-banner": "ลบแบนเนอร์ผู้ใช้" + "write:admin:user-avatar": "ลบอวตารผู้ใช้" + "write:admin:user-banner": "ลบแบนเนอร์ผู้ใช้" "write:admin:unsuspend-user": "ยกเลิกการระงับผู้ใช้" "write:admin:meta": "จัดการข้อมูลเมตาของอินสแตนซ์" "write:admin:user-note": "จัดการโน้ตการกลั่นกรอง" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 4926bdf44848..25407db5e0f7 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -2052,8 +2052,8 @@ _permissions: "read:admin:show-user": "查看用户的非公开信息" "read:admin:show-users": "查看用户的非公开信息" "write:admin:suspend-user": "冻结用户" - "write:admin:unset-user-avatar": "删除用户头像" - "write:admin:unset-user-banner": "删除用户横幅" + "write:admin:user-avatar": "删除用户头像" + "write:admin:user-banner": "删除用户横幅" "write:admin:unsuspend-user": "解除用户冻结" "write:admin:meta": "编辑实例元数据" "write:admin:user-note": "编辑管理笔记" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 477e2131752c..2f083bb8ead6 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -2034,8 +2034,8 @@ _permissions: "read:admin:show-user": "查看使用者的私密資訊" "read:admin:show-users": "查看使用者的私密資訊" "write:admin:suspend-user": "凍結使用者" - "write:admin:unset-user-avatar": "刪除使用者的頭像" - "write:admin:unset-user-banner": "刪除使用者的橫幅" + "write:admin:user-avatar": "刪除使用者的頭像" + "write:admin:user-banner": "刪除使用者的橫幅" "write:admin:unsuspend-user": "解除凍結使用者" "write:admin:meta": "編輯實例的詮釋資料" "write:admin:user-note": "編輯審查筆記" diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 1b669a31c905..7b564affeb5d 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -73,6 +73,7 @@ import * as ep___admin_relays_add from './endpoints/admin/relays/add.js'; import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; +import * as ep___admin_regenerateUserToken from './endpoints/admin/regenerate-user-token.js'; import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; @@ -83,6 +84,7 @@ 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_updateUserName from './endpoints/admin/update-user-name.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'; @@ -465,6 +467,7 @@ const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default }; const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default }; const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default }; +const $admin_regenerateUserToken: Provider = { provide: 'ep:admin/regenerate-user-token', useClass: ep___admin_regenerateUserToken.default }; const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default }; const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default }; const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default }; @@ -475,6 +478,7 @@ 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_updateUserName: Provider = { provide: 'ep:admin/update-user-name', useClass: ep___admin_updateUserName.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 }; @@ -861,6 +865,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_relays_list, $admin_relays_remove, $admin_resetPassword, + $admin_regenerateUserToken, $admin_resolveAbuseUserReport, $admin_sendEmail, $admin_serverInfo, @@ -871,6 +876,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_suspendUser, $admin_unsuspendUser, $admin_updateMeta, + $admin_updateUserName, $admin_updateUserNote, $admin_roles_create, $admin_roles_delete, @@ -1251,6 +1257,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_relays_list, $admin_relays_remove, $admin_resetPassword, + $admin_regenerateUserToken, $admin_resolveAbuseUserReport, $admin_sendEmail, $admin_serverInfo, @@ -1261,6 +1268,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_suspendUser, $admin_unsuspendUser, $admin_updateMeta, + $admin_updateUserName, $admin_updateUserNote, $admin_roles_create, $admin_roles_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 0dc4d9ffb94a..a603a7077c74 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -73,6 +73,7 @@ import * as ep___admin_relays_add from './endpoints/admin/relays/add.js'; import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; +import * as ep___admin_regenerateUserToken from './endpoints/admin/regenerate-user-token.js'; import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; @@ -83,6 +84,7 @@ 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_updateUserName from './endpoints/admin/update-user-name.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'; @@ -463,6 +465,7 @@ const eps = [ ['admin/relays/list', ep___admin_relays_list], ['admin/relays/remove', ep___admin_relays_remove], ['admin/reset-password', ep___admin_resetPassword], + ['admin/regenerate-user-token', ep___admin_regenerateUserToken], ['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport], ['admin/send-email', ep___admin_sendEmail], ['admin/server-info', ep___admin_serverInfo], @@ -473,6 +476,7 @@ const eps = [ ['admin/suspend-user', ep___admin_suspendUser], ['admin/unsuspend-user', ep___admin_unsuspendUser], ['admin/update-meta', ep___admin_updateMeta], + ['admin/update-user-name', ep___admin_updateUserName], ['admin/update-user-note', ep___admin_updateUserNote], ['admin/roles/create', ep___admin_roles_create], ['admin/roles/delete', ep___admin_roles_delete], diff --git a/packages/backend/src/server/api/endpoints/admin/regenerate-user-token.ts b/packages/backend/src/server/api/endpoints/admin/regenerate-user-token.ts new file mode 100644 index 000000000000..97567882c73c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/regenerate-user-token.ts @@ -0,0 +1,61 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/_.js'; +import generateUserToken from '@/misc/generate-native-user-token.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:regenerate-user-token', +} 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 +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private globalEventService: GlobalEventService, + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + const oldToken = user.token; + if (oldToken == null) return; + + const newToken = generateUserToken(); + await this.usersRepository.update(user.id, { + token: newToken, + }); + + // Publish event + this.globalEventService.publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); + this.globalEventService.publishMainStream(user.id, 'myTokenRegenerated'); + + this.moderationLogService.log(me, 'regenerateUserToken', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts b/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts index ddab6f3a9d56..89bb35c51cfe 100644 --- a/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts +++ b/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts @@ -14,7 +14,7 @@ export const meta = { requireCredential: true, requireModerator: true, - kind: 'write:admin:unset-user-avatar', + kind: 'write:admin:user-avatar', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts b/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts index e16dad719ce1..218b60a4e232 100644 --- a/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts +++ b/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts @@ -14,7 +14,7 @@ export const meta = { requireCredential: true, requireModerator: true, - kind: 'write:admin:unset-user-banner', + kind: 'write:admin:user-banner', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts b/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts index ba2674656faf..84a1ccd401aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts +++ b/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts @@ -12,7 +12,7 @@ export const meta = { requireCredential: true, requireModerator: true, - kind: 'write:admin:unset-user-mutual-link', + kind: 'write:admin:user-mutual-link', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-name.ts b/packages/backend/src/server/api/endpoints/admin/update-user-name.ts new file mode 100644 index 000000000000..e95937877aa5 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/update-user-name.ts @@ -0,0 +1,53 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { UsersRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:user-name', +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + name: { type: 'string' }, + }, + required: ['userId'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + name: ps.name ?? null, + }); + + this.moderationLogService.log(me, 'updateUserName', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + before: user.name, + after: ps.name ?? null, + }); + }); + } +} diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 2b5e901fb90e..9e595e83efcd 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -55,6 +55,7 @@ export const moderationLogTypes = [ 'updateServerSettings', 'suspend', 'unsuspend', + 'updateUserName', 'updateUserNote', 'addCustomEmoji', 'updateCustomEmoji', @@ -75,6 +76,7 @@ export const moderationLogTypes = [ 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'resetPassword', + 'regenerateUserToken', 'suspendRemoteInstance', 'unsuspendRemoteInstance', 'updateRemoteInstanceNote', @@ -114,6 +116,13 @@ export type ModerationLogPayloads = { userUsername: string; userHost: string | null; }; + updateUserName: { + userId: string; + userUsername: string; + userHost: string | null; + before: string | null; + after: string | null; + }; updateUserNote: { userId: string; userUsername: string; @@ -217,6 +226,11 @@ export type ModerationLogPayloads = { userUsername: string; userHost: string | null; }; + regenerateUserToken: { + userId: string; + userUsername: string; + userHost: string | null; + }; suspendRemoteInstance: { id: string; host: string; diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 4f339e550175..aae2b77cec3d 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -63,7 +63,11 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.suspend }} - {{ i18n.ts.resetPassword }} +
+ {{ i18n.ts.resetPassword }} + {{ i18n.ts.regenerateLoginToken }} +
+ {{ i18n.ts.changeUserName }} {{ i18n.ts.unsetUserAvatar }} {{ i18n.ts.unsetUserBanner }} @@ -339,6 +343,18 @@ async function resetPassword() { } } +async function regenerateLoginToken() { + const confirm = await os.confirm({ + type: 'warning', + text: i18n.ts.regenerateLoginTokenConfirm, + }); + if (confirm.canceled) return; + + await os.apiWithDialog('admin/regenerate-user-token', { + userId: user.value.id, + }).then(refreshUser); +} + async function toggleSuspend(v) { const confirm = await os.confirm({ type: 'warning', @@ -353,6 +369,20 @@ async function toggleSuspend(v) { } } +async function updateUserName() { + const { canceled, result: name } = await os.inputText({ + type: 'text', + title: i18n.ts.enterUsername, + default: '', + }); + if (canceled) return; + + await os.apiWithDialog('admin/update-user-name', { + userId: user.value.id, + name: name || undefined, + }).then(refreshUser); +} + async function unsetUserAvatar() { const confirm = await os.confirm({ type: 'warning', diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index e33c88272187..7cfb3150669c 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -9,14 +9,19 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._moderationLogTypes[log.type] }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} {{ log.info.roleName }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} {{ log.info.roleName }} : {{ log.info.role.name }} @@ -59,6 +64,12 @@ SPDX-License-Identifier: AGPL-3.0-only
+ @@ -104,6 +112,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe flex-wrap: wrap; align-items: center; margin: 4px -2px 0 -2px; + max-width: 100%; &:empty { display: none; diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index de4db848d3b2..2445a14d59f0 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -12,6 +12,23 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + +
+
+ + +
+
+
+ @@ -119,7 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only