enhance(backend): refine system account (#15530)
* wip * wip * wip * Update SystemAccountService.ts * Update 1740121393164-system-accounts.js * Update DeleteAccountService.ts * wip * wip * wip * wip * Update 1740121393164-system-accounts.js * Update RepositoryModule.ts * wip * wip * wip * Update ApRendererService.ts * wip * wip * Update SystemAccountService.ts * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * add print logs * ログが長すぎて出てないかもしれない * fix migration * refactor * fix fed-tests * Update RelayService.ts * merge * Update user.test.ts * chore: emit log * fix: tweak sleep duration * fix: exit 1 * fix: wait for misskey processes to become healthy * fix: longer sleep for user deletion * fix: make sleep longer again * デッドロック解消の試み https://github.com/misskey-dev/misskey/issues/15005 * Revert "デッドロック解消の試み" This reverts commit 266141f66fb584371bbb56ef7eba04e14bcff94d. * wip * Update SystemAccountService.ts --------- Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com> Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
This commit is contained in:
parent
7114523d84
commit
616cccf251
16
.github/workflows/test-federation.yml
vendored
16
.github/workflows/test-federation.yml
vendored
@ -62,14 +62,30 @@ jobs:
|
|||||||
bash ./setup.sh
|
bash ./setup.sh
|
||||||
sudo chmod 644 ./certificates/*.test.key
|
sudo chmod 644 ./certificates/*.test.key
|
||||||
- name: Start servers
|
- name: Start servers
|
||||||
|
id: start_servers
|
||||||
|
continue-on-error: true
|
||||||
# https://github.com/docker/compose/issues/1294#issuecomment-374847206
|
# https://github.com/docker/compose/issues/1294#issuecomment-374847206
|
||||||
run: |
|
run: |
|
||||||
cd packages/backend/test-federation
|
cd packages/backend/test-federation
|
||||||
docker compose up -d --scale tester=0
|
docker compose up -d --scale tester=0
|
||||||
|
- name: Print start_servers error
|
||||||
|
if: ${{ steps.start_servers.outcome == 'failure' }}
|
||||||
|
run: |
|
||||||
|
cd packages/backend/test-federation
|
||||||
|
docker compose logs | tail -n 300
|
||||||
|
exit 1
|
||||||
- name: Test
|
- name: Test
|
||||||
|
id: test
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
cd packages/backend/test-federation
|
cd packages/backend/test-federation
|
||||||
docker compose run --no-deps tester
|
docker compose run --no-deps tester
|
||||||
|
- name: Log
|
||||||
|
if: ${{ steps.test.outcome == 'failure' }}
|
||||||
|
run: |
|
||||||
|
cd packages/backend/test-federation
|
||||||
|
docker compose logs
|
||||||
|
exit 1
|
||||||
- name: Stop servers
|
- name: Stop servers
|
||||||
run: |
|
run: |
|
||||||
cd packages/backend/test-federation
|
cd packages/backend/test-federation
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### General
|
### General
|
||||||
-
|
- Enhance: プロキシアカウントをシステムアカウントとして作成するように
|
||||||
|
- Fix: システムアカウントが削除できる問題を修正
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
-
|
-
|
||||||
|
@ -233,7 +233,7 @@ describe('After user setup', () => {
|
|||||||
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
|
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
|
||||||
cy.get('[data-cy-open-post-form-submit]').click();
|
cy.get('[data-cy-open-post-form-submit]').click();
|
||||||
|
|
||||||
cy.contains('Hello, Misskey!');
|
cy.contains('Hello, Misskey!', { timeout: 15000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('open note form with hotkey', () => {
|
it('open note form with hotkey', () => {
|
||||||
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
@ -10058,6 +10058,10 @@ export interface Locale extends ILocale {
|
|||||||
* ギャラリーの投稿を削除
|
* ギャラリーの投稿を削除
|
||||||
*/
|
*/
|
||||||
"deleteGalleryPost": string;
|
"deleteGalleryPost": string;
|
||||||
|
/**
|
||||||
|
* プロキシアカウントの説明を更新
|
||||||
|
*/
|
||||||
|
"updateProxyAccountDescription": string;
|
||||||
};
|
};
|
||||||
"_fileViewer": {
|
"_fileViewer": {
|
||||||
/**
|
/**
|
||||||
|
@ -2664,6 +2664,7 @@ _moderationLogTypes:
|
|||||||
deletePage: "ページを削除"
|
deletePage: "ページを削除"
|
||||||
deleteFlash: "Playを削除"
|
deleteFlash: "Playを削除"
|
||||||
deleteGalleryPost: "ギャラリーの投稿を削除"
|
deleteGalleryPost: "ギャラリーの投稿を削除"
|
||||||
|
updateProxyAccountDescription: "プロキシアカウントの説明を更新"
|
||||||
|
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "ファイルの詳細"
|
title: "ファイルの詳細"
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
"migrate": "cd packages/backend && pnpm migrate",
|
"migrate": "cd packages/backend && pnpm migrate",
|
||||||
"revert": "cd packages/backend && pnpm revert",
|
"revert": "cd packages/backend && pnpm revert",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||||
"cy:run": "pnpm cypress run",
|
"cy:run": "pnpm cypress run",
|
||||||
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||||
"e2e-dev-container": "cp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
"e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||||
"jest": "cd packages/backend && pnpm jest",
|
"jest": "cd packages/backend && pnpm jest",
|
||||||
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
||||||
"test": "pnpm -r test",
|
"test": "pnpm -r test",
|
||||||
|
37
packages/backend/migration/1740121393164-system-accounts.js
Normal file
37
packages/backend/migration/1740121393164-system-accounts.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts1740121393164 {
|
||||||
|
name = 'SystemAccounts1740121393164'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "system_account" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "type" character varying(256) NOT NULL, CONSTRAINT "PK_edb56f4aaf9ddd50ee556da97ba" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_41a3c87a37aea616ee459369e1" ON "system_account" ("userId") `);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c362033aee0ea51011386a5a7e" ON "system_account" ("type") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "system_account" ADD CONSTRAINT "FK_41a3c87a37aea616ee459369e12" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
|
||||||
|
const instanceActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'instance.actor'`);
|
||||||
|
if (instanceActor.length > 0) {
|
||||||
|
await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${instanceActor[0].id}', '${instanceActor[0].id}', 'actor')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relayActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'relay.actor'`);
|
||||||
|
if (relayActor.length > 0) {
|
||||||
|
await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${relayActor[0].id}', '${relayActor[0].id}', 'relay')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = await queryRunner.query(`SELECT "proxyAccountId" FROM "meta" ORDER BY "id" DESC LIMIT 1`);
|
||||||
|
if (!meta && meta.length >= 1 && meta[0].proxyAccountId) {
|
||||||
|
await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${meta[0].proxyAccountId}', '${meta[0].proxyAccountId}', 'proxy')`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "system_account" DROP CONSTRAINT "FK_41a3c87a37aea616ee459369e12"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_c362033aee0ea51011386a5a7e"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_41a3c87a37aea616ee459369e1"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "system_account"`);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts21740129169650 {
|
||||||
|
name = 'SystemAccounts21740129169650'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyAccountId"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyAccountId" character varying(32)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad" FOREIGN KEY ("proxyAccountId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts31740133121105 {
|
||||||
|
name = 'SystemAccounts31740133121105'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "rootUserId" character varying(32)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc" FOREIGN KEY ("rootUserId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||||
|
|
||||||
|
const users = await queryRunner.query(`SELECT "id" FROM "user" WHERE "isRoot" = true LIMIT 1`);
|
||||||
|
if (users.length > 0) {
|
||||||
|
await queryRunner.query(`UPDATE "meta" SET "rootUserId" = $1`, [users[0].id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "rootUserId"`);
|
||||||
|
}
|
||||||
|
}
|
@ -133,7 +133,7 @@ const $meta: Provider = {
|
|||||||
for (const key in body.after) {
|
for (const key in body.after) {
|
||||||
(meta as any)[key] = (body.after as any)[key];
|
(meta as any)[key] = (body.after as any)[key];
|
||||||
}
|
}
|
||||||
meta.proxyAccount = null; // joinなカラムは通常取ってこないので
|
meta.rootUser = null; // joinなカラムは通常取ってこないので
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -10,9 +10,9 @@ import { bindThis } from '@/decorators.js';
|
|||||||
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
|
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
|
||||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import { IdService } from './IdService.js';
|
import { IdService } from './IdService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -27,7 +27,7 @@ export class AbuseReportService {
|
|||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private abuseReportNotificationService: AbuseReportNotificationService,
|
private abuseReportNotificationService: AbuseReportNotificationService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
) {
|
) {
|
||||||
@ -136,7 +136,7 @@ export class AbuseReportService {
|
|||||||
forwarded: true,
|
forwarded: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const actor = await this.instanceActorService.getInstanceActor();
|
const actor = await this.systemAccountService.fetch('actor');
|
||||||
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
||||||
|
|
||||||
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
|
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
|
||||||
|
@ -20,10 +20,10 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
|||||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountMoveService {
|
export class AccountMoveService {
|
||||||
@ -55,12 +55,12 @@ export class AccountMoveService {
|
|||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private apDeliverManagerService: ApDeliverManagerService,
|
private apDeliverManagerService: ApDeliverManagerService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private proxyAccountService: ProxyAccountService,
|
|
||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
private relayService: RelayService,
|
private relayService: RelayService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,11 +126,11 @@ export class AccountMoveService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// follow the new account
|
// follow the new account
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
const followings = await this.followingsRepository.findBy({
|
const followings = await this.followingsRepository.findBy({
|
||||||
followeeId: src.id,
|
followeeId: src.id,
|
||||||
followerHost: IsNull(), // follower is local
|
followerHost: IsNull(), // follower is local
|
||||||
followerId: proxy ? Not(proxy.id) : undefined,
|
followerId: Not(proxy.id),
|
||||||
});
|
});
|
||||||
const followJobs = followings.map(following => ({
|
const followJobs = followings.map(following => ({
|
||||||
from: { id: following.followerId },
|
from: { id: following.followerId },
|
||||||
@ -250,10 +250,8 @@ export class AccountMoveService {
|
|||||||
|
|
||||||
// Have the proxy account follow the new account in the same way as UserListService.push
|
// Have the proxy account follow the new account in the same way as UserListService.push
|
||||||
if (this.userEntityService.isRemoteUser(dst)) {
|
if (this.userEntityService.isRemoteUser(dst)) {
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
if (proxy) {
|
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
|
||||||
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import { AppLockService } from './AppLockService.js';
|
|||||||
import { AchievementService } from './AchievementService.js';
|
import { AchievementService } from './AchievementService.js';
|
||||||
import { AvatarDecorationService } from './AvatarDecorationService.js';
|
import { AvatarDecorationService } from './AvatarDecorationService.js';
|
||||||
import { CaptchaService } from './CaptchaService.js';
|
import { CaptchaService } from './CaptchaService.js';
|
||||||
import { CreateSystemUserService } from './CreateSystemUserService.js';
|
|
||||||
import { CustomEmojiService } from './CustomEmojiService.js';
|
import { CustomEmojiService } from './CustomEmojiService.js';
|
||||||
import { DeleteAccountService } from './DeleteAccountService.js';
|
import { DeleteAccountService } from './DeleteAccountService.js';
|
||||||
import { DownloadService } from './DownloadService.js';
|
import { DownloadService } from './DownloadService.js';
|
||||||
@ -37,7 +36,7 @@ import { HashtagService } from './HashtagService.js';
|
|||||||
import { HttpRequestService } from './HttpRequestService.js';
|
import { HttpRequestService } from './HttpRequestService.js';
|
||||||
import { IdService } from './IdService.js';
|
import { IdService } from './IdService.js';
|
||||||
import { ImageProcessingService } from './ImageProcessingService.js';
|
import { ImageProcessingService } from './ImageProcessingService.js';
|
||||||
import { InstanceActorService } from './InstanceActorService.js';
|
import { SystemAccountService } from './SystemAccountService.js';
|
||||||
import { InternalStorageService } from './InternalStorageService.js';
|
import { InternalStorageService } from './InternalStorageService.js';
|
||||||
import { MetaService } from './MetaService.js';
|
import { MetaService } from './MetaService.js';
|
||||||
import { MfmService } from './MfmService.js';
|
import { MfmService } from './MfmService.js';
|
||||||
@ -69,7 +68,6 @@ import { UserSuspendService } from './UserSuspendService.js';
|
|||||||
import { UserAuthService } from './UserAuthService.js';
|
import { UserAuthService } from './UserAuthService.js';
|
||||||
import { VideoProcessingService } from './VideoProcessingService.js';
|
import { VideoProcessingService } from './VideoProcessingService.js';
|
||||||
import { UserWebhookService } from './UserWebhookService.js';
|
import { UserWebhookService } from './UserWebhookService.js';
|
||||||
import { ProxyAccountService } from './ProxyAccountService.js';
|
|
||||||
import { UtilityService } from './UtilityService.js';
|
import { UtilityService } from './UtilityService.js';
|
||||||
import { FileInfoService } from './FileInfoService.js';
|
import { FileInfoService } from './FileInfoService.js';
|
||||||
import { SearchService } from './SearchService.js';
|
import { SearchService } from './SearchService.js';
|
||||||
@ -167,7 +165,6 @@ const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppL
|
|||||||
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
||||||
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
||||||
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
||||||
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
|
|
||||||
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
||||||
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
|
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
|
||||||
const $DownloadService: Provider = { provide: 'DownloadService', useExisting: DownloadService };
|
const $DownloadService: Provider = { provide: 'DownloadService', useExisting: DownloadService };
|
||||||
@ -180,7 +177,6 @@ const $HashtagService: Provider = { provide: 'HashtagService', useExisting: Hash
|
|||||||
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
|
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
|
||||||
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
|
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
|
||||||
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
|
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
|
||||||
const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
|
|
||||||
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
|
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
|
||||||
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
|
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
|
||||||
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
||||||
@ -191,7 +187,7 @@ const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting
|
|||||||
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
||||||
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
||||||
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
||||||
const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExisting: ProxyAccountService };
|
const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService };
|
||||||
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
||||||
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
||||||
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
||||||
@ -318,7 +314,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
AchievementService,
|
AchievementService,
|
||||||
AvatarDecorationService,
|
AvatarDecorationService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
DeleteAccountService,
|
DeleteAccountService,
|
||||||
DownloadService,
|
DownloadService,
|
||||||
@ -331,7 +326,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
HttpRequestService,
|
HttpRequestService,
|
||||||
IdService,
|
IdService,
|
||||||
ImageProcessingService,
|
ImageProcessingService,
|
||||||
InstanceActorService,
|
|
||||||
InternalStorageService,
|
InternalStorageService,
|
||||||
MetaService,
|
MetaService,
|
||||||
MfmService,
|
MfmService,
|
||||||
@ -342,7 +336,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
NoteReadService,
|
NoteReadService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
PollService,
|
PollService,
|
||||||
ProxyAccountService,
|
SystemAccountService,
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
@ -465,7 +459,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$AchievementService,
|
$AchievementService,
|
||||||
$AvatarDecorationService,
|
$AvatarDecorationService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
$DeleteAccountService,
|
$DeleteAccountService,
|
||||||
$DownloadService,
|
$DownloadService,
|
||||||
@ -478,7 +471,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$HttpRequestService,
|
$HttpRequestService,
|
||||||
$IdService,
|
$IdService,
|
||||||
$ImageProcessingService,
|
$ImageProcessingService,
|
||||||
$InstanceActorService,
|
|
||||||
$InternalStorageService,
|
$InternalStorageService,
|
||||||
$MetaService,
|
$MetaService,
|
||||||
$MfmService,
|
$MfmService,
|
||||||
@ -489,7 +481,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$NoteReadService,
|
$NoteReadService,
|
||||||
$NotificationService,
|
$NotificationService,
|
||||||
$PollService,
|
$PollService,
|
||||||
$ProxyAccountService,
|
$SystemAccountService,
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
@ -613,7 +605,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
AchievementService,
|
AchievementService,
|
||||||
AvatarDecorationService,
|
AvatarDecorationService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
DeleteAccountService,
|
DeleteAccountService,
|
||||||
DownloadService,
|
DownloadService,
|
||||||
@ -626,7 +617,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
HttpRequestService,
|
HttpRequestService,
|
||||||
IdService,
|
IdService,
|
||||||
ImageProcessingService,
|
ImageProcessingService,
|
||||||
InstanceActorService,
|
|
||||||
InternalStorageService,
|
InternalStorageService,
|
||||||
MetaService,
|
MetaService,
|
||||||
MfmService,
|
MfmService,
|
||||||
@ -637,7 +627,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
NoteReadService,
|
NoteReadService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
PollService,
|
PollService,
|
||||||
ProxyAccountService,
|
SystemAccountService,
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
@ -759,7 +749,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$AchievementService,
|
$AchievementService,
|
||||||
$AvatarDecorationService,
|
$AvatarDecorationService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
$DeleteAccountService,
|
$DeleteAccountService,
|
||||||
$DownloadService,
|
$DownloadService,
|
||||||
@ -772,7 +761,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$HttpRequestService,
|
$HttpRequestService,
|
||||||
$IdService,
|
$IdService,
|
||||||
$ImageProcessingService,
|
$ImageProcessingService,
|
||||||
$InstanceActorService,
|
|
||||||
$InternalStorageService,
|
$InternalStorageService,
|
||||||
$MetaService,
|
$MetaService,
|
||||||
$MfmService,
|
$MfmService,
|
||||||
@ -783,7 +771,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$NoteReadService,
|
$NoteReadService,
|
||||||
$NotificationService,
|
$NotificationService,
|
||||||
$PollService,
|
$PollService,
|
||||||
$ProxyAccountService,
|
$SystemAccountService,
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { randomUUID } from 'node:crypto';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import bcrypt from 'bcryptjs';
|
|
||||||
import { IsNull, DataSource } from 'typeorm';
|
|
||||||
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
|
||||||
import { MiUser } from '@/models/User.js';
|
|
||||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { generateNativeUserToken } from '@/misc/token.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreateSystemUserService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.db)
|
|
||||||
private db: DataSource,
|
|
||||||
|
|
||||||
private idService: IdService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async createSystemUser(username: string): Promise<MiUser> {
|
|
||||||
const password = randomUUID();
|
|
||||||
|
|
||||||
// Generate hash of password
|
|
||||||
const salt = await bcrypt.genSalt(8);
|
|
||||||
const hash = await bcrypt.hash(password, salt);
|
|
||||||
|
|
||||||
// Generate secret
|
|
||||||
const secret = generateNativeUserToken();
|
|
||||||
|
|
||||||
const keyPair = await genRsaKeyPair();
|
|
||||||
|
|
||||||
let account!: MiUser;
|
|
||||||
|
|
||||||
// Start transaction
|
|
||||||
await this.db.transaction(async transactionalEntityManager => {
|
|
||||||
const exist = await transactionalEntityManager.findOneBy(MiUser, {
|
|
||||||
usernameLower: username.toLowerCase(),
|
|
||||||
host: IsNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exist) throw new Error('the user is already exists');
|
|
||||||
|
|
||||||
account = await transactionalEntityManager.insert(MiUser, {
|
|
||||||
id: this.idService.gen(),
|
|
||||||
username: username,
|
|
||||||
usernameLower: username.toLowerCase(),
|
|
||||||
host: null,
|
|
||||||
token: secret,
|
|
||||||
isRoot: false,
|
|
||||||
isLocked: true,
|
|
||||||
isExplorable: false,
|
|
||||||
isBot: true,
|
|
||||||
}).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0]));
|
|
||||||
|
|
||||||
await transactionalEntityManager.insert(MiUserKeypair, {
|
|
||||||
publicKey: keyPair.publicKey,
|
|
||||||
privateKey: keyPair.privateKey,
|
|
||||||
userId: account.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await transactionalEntityManager.insert(MiUserProfile, {
|
|
||||||
userId: account.id,
|
|
||||||
autoAcceptFollowed: false,
|
|
||||||
password: hash,
|
|
||||||
});
|
|
||||||
|
|
||||||
await transactionalEntityManager.insert(MiUsedUsername, {
|
|
||||||
createdAt: new Date(),
|
|
||||||
username: username.toLowerCase(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Not, IsNull } from 'typeorm';
|
import { Not, IsNull } from 'typeorm';
|
||||||
import type { FollowingsRepository, MiUser, UsersRepository } from '@/models/_.js';
|
import type { FollowingsRepository, MiMeta, MiUser, UsersRepository } from '@/models/_.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
@ -13,10 +13,14 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountService {
|
export class DeleteAccountService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@ -28,6 +32,7 @@ export class DeleteAccountService {
|
|||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,8 +41,13 @@ export class DeleteAccountService {
|
|||||||
id: string;
|
id: string;
|
||||||
host: string | null;
|
host: string | null;
|
||||||
}, moderator?: MiUser): Promise<void> {
|
}, moderator?: MiUser): Promise<void> {
|
||||||
|
if (this.meta.rootUserId === user.id) throw new Error('cannot delete a root account');
|
||||||
|
|
||||||
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
if (_user.isRoot) throw new Error('cannot delete a root account');
|
|
||||||
|
if (user.host === null && _user.username.includes('.')) {
|
||||||
|
throw new Error('cannot delete a system account');
|
||||||
|
}
|
||||||
|
|
||||||
if (moderator != null) {
|
if (moderator != null) {
|
||||||
this.moderationLogService.log(moderator, 'deleteAccount', {
|
this.moderationLogService.log(moderator, 'deleteAccount', {
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { IsNull, Not } from 'typeorm';
|
|
||||||
import type { MiLocalUser } from '@/models/User.js';
|
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
const ACTOR_USERNAME = 'instance.actor' as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class InstanceActorService {
|
|
||||||
private cache: MemorySingleCache<MiLocalUser>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
private createSystemUserService: CreateSystemUserService,
|
|
||||||
) {
|
|
||||||
this.cache = new MemorySingleCache<MiLocalUser>(Infinity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async realLocalUsersPresent(): Promise<boolean> {
|
|
||||||
return await this.usersRepository.existsBy({
|
|
||||||
host: IsNull(),
|
|
||||||
username: Not(ACTOR_USERNAME),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async getInstanceActor(): Promise<MiLocalUser> {
|
|
||||||
const cached = this.cache.get();
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
|
||||||
host: IsNull(),
|
|
||||||
username: ACTOR_USERNAME,
|
|
||||||
}) as MiLocalUser | undefined;
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
this.cache.set(user);
|
|
||||||
return user;
|
|
||||||
} else {
|
|
||||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as MiLocalUser;
|
|
||||||
this.cache.set(created);
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -53,7 +53,7 @@ export class MetaService implements OnApplicationShutdown {
|
|||||||
case 'metaUpdated': {
|
case 'metaUpdated': {
|
||||||
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||||
...(body.after),
|
...(body.after),
|
||||||
proxyAccount: null, // joinなカラムは通常取ってこないので
|
rootUser: null, // joinなカラムは通常取ってこないので
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -113,17 +113,20 @@ export class MetaService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
if (before) {
|
if (before) {
|
||||||
await transactionalEntityManager.update(MiMeta, before.id, data);
|
await transactionalEntityManager.update(MiMeta, before.id, data);
|
||||||
|
|
||||||
const metas = await transactionalEntityManager.find(MiMeta, {
|
|
||||||
order: {
|
|
||||||
id: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return metas[0];
|
|
||||||
} else {
|
} else {
|
||||||
return await transactionalEntityManager.save(MiMeta, data);
|
await transactionalEntityManager.save(MiMeta, {
|
||||||
|
...data,
|
||||||
|
id: 'x',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const afters = await transactionalEntityManager.find(MiMeta, {
|
||||||
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return afters[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.hiddenTags) {
|
if (data.hiddenTags) {
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
|
||||||
import type { MiLocalUser } from '@/models/User.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ProxyAccountService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.meta)
|
|
||||||
private meta: MiMeta,
|
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async fetch(): Promise<MiLocalUser | null> {
|
|
||||||
if (this.meta.proxyAccountId == null) return null;
|
|
||||||
return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser;
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,53 +4,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
import type { RelaysRepository } from '@/models/_.js';
|
||||||
import type { RelaysRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
import { MemorySingleCache } from '@/misc/cache.js';
|
||||||
import type { MiRelay } from '@/models/Relay.js';
|
import type { MiRelay } from '@/models/Relay.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { deepClone } from '@/misc/clone.js';
|
import { deepClone } from '@/misc/clone.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
const ACTOR_USERNAME = 'relay.actor' as const;
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RelayService {
|
export class RelayService {
|
||||||
private relaysCache: MemorySingleCache<MiRelay[]>;
|
private relaysCache: MemorySingleCache<MiRelay[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
@Inject(DI.relaysRepository)
|
@Inject(DI.relaysRepository)
|
||||||
private relaysRepository: RelaysRepository,
|
private relaysRepository: RelaysRepository,
|
||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private createSystemUserService: CreateSystemUserService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
) {
|
) {
|
||||||
this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
|
this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private async getRelayActor(): Promise<MiLocalUser> {
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
|
||||||
host: IsNull(),
|
|
||||||
username: ACTOR_USERNAME,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) return user as MiLocalUser;
|
|
||||||
|
|
||||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME);
|
|
||||||
return created as MiLocalUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async addRelay(inbox: string): Promise<MiRelay> {
|
public async addRelay(inbox: string): Promise<MiRelay> {
|
||||||
const relay = await this.relaysRepository.insertOne({
|
const relay = await this.relaysRepository.insertOne({
|
||||||
@ -59,8 +40,8 @@ export class RelayService {
|
|||||||
status: 'requesting',
|
status: 'requesting',
|
||||||
});
|
});
|
||||||
|
|
||||||
const relayActor = await this.getRelayActor();
|
const relayActor = await this.systemAccountService.fetch('relay');
|
||||||
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
const activity = this.apRendererService.addContext(follow);
|
const activity = this.apRendererService.addContext(follow);
|
||||||
this.queueService.deliver(relayActor, activity, relay.inbox, false);
|
this.queueService.deliver(relayActor, activity, relay.inbox, false);
|
||||||
|
|
||||||
@ -77,7 +58,7 @@ export class RelayService {
|
|||||||
throw new Error('relay not found');
|
throw new Error('relay not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const relayActor = await this.getRelayActor();
|
const relayActor = await this.systemAccountService.fetch('relay');
|
||||||
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
const undo = this.apRendererService.renderUndo(follow, relayActor);
|
const undo = this.apRendererService.renderUndo(follow, relayActor);
|
||||||
const activity = this.apRendererService.addContext(undo);
|
const activity = this.apRendererService.addContext(undo);
|
||||||
|
@ -101,7 +101,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
private rootUserIdCache: MemorySingleCache<MiUser['id']>;
|
|
||||||
private rolesCache: MemorySingleCache<MiRole[]>;
|
private rolesCache: MemorySingleCache<MiRole[]>;
|
||||||
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
|
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
|
||||||
private notificationService: NotificationService;
|
private notificationService: NotificationService;
|
||||||
@ -137,7 +136,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
private fanoutTimelineService: FanoutTimelineService,
|
private fanoutTimelineService: FanoutTimelineService,
|
||||||
) {
|
) {
|
||||||
this.rootUserIdCache = new MemorySingleCache<MiUser['id']>(1000 * 60 * 60 * 24 * 7); // 1week. rootユーザのIDは不変なので長めに
|
|
||||||
this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
|
this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
|
||||||
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
|
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
|
||||||
|
|
||||||
@ -406,15 +404,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async isModerator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
|
public async isModerator(user: { id: MiUser['id'] } | null): Promise<boolean> {
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
|
return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async isAdministrator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
|
public async isAdministrator(user: { id: MiUser['id'] } | null): Promise<boolean> {
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -463,16 +461,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
.map(a => a.userId),
|
.map(a => a.userId),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (includeRoot) {
|
if (includeRoot && this.meta.rootUserId) {
|
||||||
const rootUserId = await this.rootUserIdCache.fetch(async () => {
|
resultSet.add(this.meta.rootUserId);
|
||||||
const it = await this.usersRepository.createQueryBuilder('users')
|
|
||||||
.select('id')
|
|
||||||
.where({ isRoot: true })
|
|
||||||
.getRawOne<{ id: string }>();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
return it!.id;
|
|
||||||
});
|
|
||||||
resultSet.add(rootUserId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...resultSet].sort((x, y) => x.localeCompare(y));
|
return [...resultSet].sort((x, y) => x.localeCompare(y));
|
||||||
|
@ -16,11 +16,12 @@ import { MiUserKeypair } from '@/models/UserKeypair.js';
|
|||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
||||||
import { generateNativeUserToken } from '@/misc/token.js';
|
import { generateNativeUserToken } from '@/misc/token.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserService } from '@/core/UserService.js';
|
import { UserService } from '@/core/UserService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignupService {
|
export class SignupService {
|
||||||
@ -41,7 +42,8 @@ export class SignupService {
|
|||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
|
private metaService: MetaService,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@ -86,9 +88,7 @@ export class SignupService {
|
|||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
|
if (!opts.ignorePreservedUsernames && this.meta.rootUserId != null) {
|
||||||
|
|
||||||
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
|
||||||
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||||
if (isPreserved) {
|
if (isPreserved) {
|
||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
@ -129,7 +129,6 @@ export class SignupService {
|
|||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: this.utilityService.toPunyNullable(host),
|
host: this.utilityService.toPunyNullable(host),
|
||||||
token: secret,
|
token: secret,
|
||||||
isRoot: isTheFirstUser,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await transactionalEntityManager.save(new MiUserKeypair({
|
await transactionalEntityManager.save(new MiUserKeypair({
|
||||||
@ -153,6 +152,10 @@ export class SignupService {
|
|||||||
this.usersChart.update(account, true);
|
this.usersChart.update(account, true);
|
||||||
this.userService.notifySystemWebhook(account, 'userCreated');
|
this.userService.notifySystemWebhook(account, 'userCreated');
|
||||||
|
|
||||||
|
if (this.meta.rootUserId == null) {
|
||||||
|
await this.metaService.update({ rootUserId: account.id });
|
||||||
|
}
|
||||||
|
|
||||||
return { account, secret };
|
return { account, secret };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
172
packages/backend/src/core/SystemAccountService.ts
Normal file
172
packages/backend/src/core/SystemAccountService.ts
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource, IsNull } from 'typeorm';
|
||||||
|
import bcrypt from 'bcryptjs';
|
||||||
|
import { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
|
import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js';
|
||||||
|
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||||
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { generateNativeUserToken } from '@/misc/token.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||||
|
|
||||||
|
export const SYSTEM_ACCOUNT_TYPES = ['actor', 'relay', 'proxy'] as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SystemAccountService {
|
||||||
|
private cache: MemoryKVCache<MiLocalUser>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.db)
|
||||||
|
private db: DataSource,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
|
@Inject(DI.systemAccountsRepository)
|
||||||
|
private systemAccountsRepository: SystemAccountsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
this.cache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 10); // 10m
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async list(): Promise<MiSystemAccount[]> {
|
||||||
|
const accounts = await this.systemAccountsRepository.findBy({});
|
||||||
|
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async fetch(type: typeof SYSTEM_ACCOUNT_TYPES[number]): Promise<MiLocalUser> {
|
||||||
|
const cached = this.cache.get(type);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const systemAccount = await this.systemAccountsRepository.findOne({
|
||||||
|
where: { type: type },
|
||||||
|
relations: ['user'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (systemAccount) {
|
||||||
|
this.cache.set(type, systemAccount.user as MiLocalUser);
|
||||||
|
return systemAccount.user as MiLocalUser;
|
||||||
|
} else {
|
||||||
|
const created = await this.createCorrespondingUser(type, {
|
||||||
|
username: `system.${type}`, // NOTE: (できれば避けたいが) . が含まれるかどうかでシステムアカウントかどうかを判定している処理もあるので変えないように
|
||||||
|
name: this.meta.name,
|
||||||
|
});
|
||||||
|
this.cache.set(type, created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async createCorrespondingUser(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
|
||||||
|
username: MiUser['username'];
|
||||||
|
name?: MiUser['name'];
|
||||||
|
}): Promise<MiLocalUser> {
|
||||||
|
const password = randomUUID();
|
||||||
|
|
||||||
|
// Generate hash of password
|
||||||
|
const salt = await bcrypt.genSalt(8);
|
||||||
|
const hash = await bcrypt.hash(password, salt);
|
||||||
|
|
||||||
|
// Generate secret
|
||||||
|
const secret = generateNativeUserToken();
|
||||||
|
|
||||||
|
const keyPair = await genRsaKeyPair();
|
||||||
|
|
||||||
|
let account!: MiUser;
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
await this.db.transaction(async transactionalEntityManager => {
|
||||||
|
const exist = await transactionalEntityManager.findOneBy(MiUser, {
|
||||||
|
usernameLower: extra.username.toLowerCase(),
|
||||||
|
host: IsNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist) {
|
||||||
|
account = exist;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
account = await transactionalEntityManager.insert(MiUser, {
|
||||||
|
id: this.idService.gen(),
|
||||||
|
username: extra.username,
|
||||||
|
usernameLower: extra.username.toLowerCase(),
|
||||||
|
host: null,
|
||||||
|
token: secret,
|
||||||
|
isLocked: true,
|
||||||
|
isExplorable: false,
|
||||||
|
isBot: true,
|
||||||
|
name: extra.name,
|
||||||
|
}).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0]));
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiUserKeypair, {
|
||||||
|
publicKey: keyPair.publicKey,
|
||||||
|
privateKey: keyPair.privateKey,
|
||||||
|
userId: account.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiUserProfile, {
|
||||||
|
userId: account.id,
|
||||||
|
autoAcceptFollowed: false,
|
||||||
|
password: hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiUsedUsername, {
|
||||||
|
createdAt: new Date(),
|
||||||
|
username: extra.username.toLowerCase(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiSystemAccount, {
|
||||||
|
id: this.idService.gen(),
|
||||||
|
userId: account.id,
|
||||||
|
type: type,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return account as MiLocalUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
|
||||||
|
name?: string;
|
||||||
|
description?: MiUserProfile['description'];
|
||||||
|
}): Promise<MiLocalUser> {
|
||||||
|
const user = await this.fetch(type);
|
||||||
|
|
||||||
|
const updates = {} as Partial<MiUser>;
|
||||||
|
if (extra.name !== undefined) updates.name = extra.name;
|
||||||
|
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
await this.usersRepository.update(user.id, updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileUpdates = {} as Partial<MiUserProfile>;
|
||||||
|
if (extra.description !== undefined) profileUpdates.description = extra.description;
|
||||||
|
|
||||||
|
if (Object.keys(profileUpdates).length > 0) {
|
||||||
|
await this.userProfilesRepository.update(user.id, profileUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await this.usersRepository.findOneByOrFail({ id: user.id }) as MiLocalUser;
|
||||||
|
this.cache.set(type, updated);
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
}
|
@ -15,11 +15,11 @@ import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
|||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { RedisKVCache } from '@/misc/cache.js';
|
import { RedisKVCache } from '@/misc/cache.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
||||||
@ -43,8 +43,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private proxyAccountService: ProxyAccountService,
|
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
|
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
|
||||||
lifetime: 1000 * 60 * 30, // 30m
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
@ -111,10 +111,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
|
|
||||||
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
|
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
|
||||||
if (this.userEntityService.isRemoteUser(target)) {
|
if (this.userEntityService.isRemoteUser(target)) {
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
if (proxy) {
|
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
|
||||||
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,6 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
|
|||||||
isLocked: false,
|
isLocked: false,
|
||||||
isBot: false,
|
isBot: false,
|
||||||
isCat: true,
|
isCat: true,
|
||||||
isRoot: false,
|
|
||||||
isExplorable: true,
|
isExplorable: true,
|
||||||
isHibernated: false,
|
isHibernated: false,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
@ -23,7 +23,7 @@ import { MfmService } from '@/core/MfmService.js';
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, MiMeta } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
@ -39,6 +39,9 @@ export class ApRendererService {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@ -186,7 +189,7 @@ export class ApRendererService {
|
|||||||
url: emoji.publicUrl || emoji.originalUrl,
|
url: emoji.publicUrl || emoji.originalUrl,
|
||||||
},
|
},
|
||||||
_misskey_license: {
|
_misskey_license: {
|
||||||
freeText: emoji.license
|
freeText: emoji.license,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -255,6 +258,38 @@ export class ApRendererService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public renderIdenticon(user: MiLocalUser): IApImage {
|
||||||
|
return {
|
||||||
|
type: 'Image',
|
||||||
|
url: this.userEntityService.getIdenticonUrl(user),
|
||||||
|
sensitive: false,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public renderSystemAvatar(user: MiLocalUser): IApImage {
|
||||||
|
if (this.meta.iconUrl == null) return this.renderIdenticon(user);
|
||||||
|
return {
|
||||||
|
type: 'Image',
|
||||||
|
url: this.meta.iconUrl,
|
||||||
|
sensitive: false,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public renderSystemBanner(): IApImage | null {
|
||||||
|
if (this.meta.bannerUrl == null) return null;
|
||||||
|
return {
|
||||||
|
type: 'Image',
|
||||||
|
url: this.meta.bannerUrl,
|
||||||
|
sensitive: false,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
|
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
|
||||||
return {
|
return {
|
||||||
@ -503,8 +538,8 @@ export class ApRendererService {
|
|||||||
_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
|
_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
|
||||||
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
|
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
|
||||||
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
|
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
|
||||||
icon: avatar ? this.renderImage(avatar) : null,
|
icon: avatar ? this.renderImage(avatar) : isSystem ? this.renderSystemAvatar(user) : this.renderIdenticon(user),
|
||||||
image: banner ? this.renderImage(banner) : null,
|
image: banner ? this.renderImage(banner) : isSystem ? this.renderSystemBanner() : null,
|
||||||
tag,
|
tag,
|
||||||
manuallyApprovesFollowers: user.isLocked,
|
manuallyApprovesFollowers: user.isLocked,
|
||||||
discoverable: user.isExplorable,
|
discoverable: user.isExplorable,
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { IsNull, Not } from 'typeorm';
|
||||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
@ -15,13 +14,14 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { isCollectionOrOrderedCollection } from './type.js';
|
import { isCollectionOrOrderedCollection } from './type.js';
|
||||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
|
||||||
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
|
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
|
||||||
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
@ -37,7 +37,7 @@ export class Resolver {
|
|||||||
private noteReactionsRepository: NoteReactionsRepository,
|
private noteReactionsRepository: NoteReactionsRepository,
|
||||||
private followRequestsRepository: FollowRequestsRepository,
|
private followRequestsRepository: FollowRequestsRepository,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRequestService: ApRequestService,
|
private apRequestService: ApRequestService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
@ -105,7 +105,7 @@ export class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.signToActivityPubGet && !this.user) {
|
if (this.config.signToActivityPubGet && !this.user) {
|
||||||
this.user = await this.instanceActorService.getInstanceActor();
|
this.user = await this.systemAccountService.fetch('actor');
|
||||||
}
|
}
|
||||||
|
|
||||||
const object = (this.user
|
const object = (this.user
|
||||||
@ -119,7 +119,7 @@ export class Resolver {
|
|||||||
) {
|
) {
|
||||||
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ export class ApResolverService {
|
|||||||
private followRequestsRepository: FollowRequestsRepository,
|
private followRequestsRepository: FollowRequestsRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRequestService: ApRequestService,
|
private apRequestService: ApRequestService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
@ -222,7 +222,7 @@ export class ApResolverService {
|
|||||||
this.noteReactionsRepository,
|
this.noteReactionsRepository,
|
||||||
this.followRequestsRepository,
|
this.followRequestsRepository,
|
||||||
this.utilityService,
|
this.utilityService,
|
||||||
this.instanceActorService,
|
this.systemAccountService,
|
||||||
this.apRequestService,
|
this.apRequestService,
|
||||||
this.httpRequestService,
|
this.httpRequestService,
|
||||||
this.apRendererService,
|
this.apRendererService,
|
||||||
|
@ -11,8 +11,7 @@ import type { MiMeta } from '@/models/Meta.js';
|
|||||||
import type { AdsRepository } from '@/models/_.js';
|
import type { AdsRepository } from '@/models/_.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
@ -29,8 +28,7 @@ export class MetaEntityService {
|
|||||||
@Inject(DI.adsRepository)
|
@Inject(DI.adsRepository)
|
||||||
private adsRepository: AdsRepository,
|
private adsRepository: AdsRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private systemAccountService: SystemAccountService,
|
||||||
private instanceActorService: InstanceActorService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -149,14 +147,14 @@ export class MetaEntityService {
|
|||||||
|
|
||||||
const packed = await this.pack(instance);
|
const packed = await this.pack(instance);
|
||||||
|
|
||||||
const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
|
const proxyAccount = await this.systemAccountService.fetch('proxy');
|
||||||
|
|
||||||
const packDetailed: Packed<'MetaDetailed'> = {
|
const packDetailed: Packed<'MetaDetailed'> = {
|
||||||
...packed,
|
...packed,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
||||||
requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
|
requireSetup: this.meta.rootUserId == null,
|
||||||
proxyAccountName: proxyAccount ? proxyAccount.username : null,
|
proxyAccountName: proxyAccount.username,
|
||||||
features: {
|
features: {
|
||||||
localTimeline: instance.policies.ltlAvailable,
|
localTimeline: instance.policies.ltlAvailable,
|
||||||
globalTimeline: instance.policies.gtlAvailable,
|
globalTimeline: instance.policies.gtlAvailable,
|
||||||
|
@ -28,6 +28,7 @@ import type {
|
|||||||
FollowingsRepository,
|
FollowingsRepository,
|
||||||
FollowRequestsRepository,
|
FollowRequestsRepository,
|
||||||
MiFollowing,
|
MiFollowing,
|
||||||
|
MiMeta,
|
||||||
MiUserNotePining,
|
MiUserNotePining,
|
||||||
MiUserProfile,
|
MiUserProfile,
|
||||||
MutingsRepository,
|
MutingsRepository,
|
||||||
@ -100,6 +101,9 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@ -381,7 +385,11 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getIdenticonUrl(user: MiUser): string {
|
public getIdenticonUrl(user: MiUser): string {
|
||||||
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
|
if ((user.host == null || user.host === this.config.host) && user.username.includes('.') && this.meta.iconUrl) { // ローカルのシステムアカウントの場合
|
||||||
|
return this.meta.iconUrl;
|
||||||
|
} else {
|
||||||
|
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -74,6 +74,7 @@ export const DI = {
|
|||||||
registryItemsRepository: Symbol('registryItemsRepository'),
|
registryItemsRepository: Symbol('registryItemsRepository'),
|
||||||
webhooksRepository: Symbol('webhooksRepository'),
|
webhooksRepository: Symbol('webhooksRepository'),
|
||||||
systemWebhooksRepository: Symbol('systemWebhooksRepository'),
|
systemWebhooksRepository: Symbol('systemWebhooksRepository'),
|
||||||
|
systemAccountsRepository: Symbol('systemAccountsRepository'),
|
||||||
adsRepository: Symbol('adsRepository'),
|
adsRepository: Symbol('adsRepository'),
|
||||||
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
|
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
|
||||||
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
|
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||||
import { id } from './util/id.js';
|
import { id } from './util/id.js';
|
||||||
import { MiUser } from './User.js';
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
@ -15,6 +15,18 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public rootUserId: MiUser['id'] | null;
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public rootUser: MiUser | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024, nullable: true,
|
length: 1024, nullable: true,
|
||||||
})
|
})
|
||||||
@ -172,18 +184,6 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public cacheRemoteSensitiveFiles: boolean;
|
public cacheRemoteSensitiveFiles: boolean;
|
||||||
|
|
||||||
@Column({
|
|
||||||
...id(),
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public proxyAccountId: MiUser['id'] | null;
|
|
||||||
|
|
||||||
@ManyToOne(type => MiUser, {
|
|
||||||
onDelete: 'SET NULL',
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public proxyAccount: MiUser | null;
|
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Provider } from '@nestjs/common';
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import {
|
import {
|
||||||
@ -63,6 +62,7 @@ import {
|
|||||||
MiRoleAssignment,
|
MiRoleAssignment,
|
||||||
MiSignin,
|
MiSignin,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
|
MiSystemAccount,
|
||||||
MiSystemWebhook,
|
MiSystemWebhook,
|
||||||
MiUsedUsername,
|
MiUsedUsername,
|
||||||
MiUser,
|
MiUser,
|
||||||
@ -77,8 +77,9 @@ import {
|
|||||||
MiUserProfile,
|
MiUserProfile,
|
||||||
MiUserPublickey,
|
MiUserPublickey,
|
||||||
MiUserSecurityKey,
|
MiUserSecurityKey,
|
||||||
MiWebhook
|
MiWebhook,
|
||||||
} from './_.js';
|
} from './_.js';
|
||||||
|
import type { Provider } from '@nestjs/common';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
|
|
||||||
const $usersRepository: Provider = {
|
const $usersRepository: Provider = {
|
||||||
@ -285,6 +286,12 @@ const $swSubscriptionsRepository: Provider = {
|
|||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $systemAccountsRepository: Provider = {
|
||||||
|
provide: DI.systemAccountsRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiSystemAccount),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $hashtagsRepository: Provider = {
|
const $hashtagsRepository: Provider = {
|
||||||
provide: DI.hashtagsRepository,
|
provide: DI.hashtagsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
|
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
|
||||||
@ -532,6 +539,7 @@ const $reversiGamesRepository: Provider = {
|
|||||||
$renoteMutingsRepository,
|
$renoteMutingsRepository,
|
||||||
$blockingsRepository,
|
$blockingsRepository,
|
||||||
$swSubscriptionsRepository,
|
$swSubscriptionsRepository,
|
||||||
|
$systemAccountsRepository,
|
||||||
$hashtagsRepository,
|
$hashtagsRepository,
|
||||||
$abuseUserReportsRepository,
|
$abuseUserReportsRepository,
|
||||||
$abuseReportNotificationRecipientRepository,
|
$abuseReportNotificationRecipientRepository,
|
||||||
@ -603,6 +611,7 @@ const $reversiGamesRepository: Provider = {
|
|||||||
$renoteMutingsRepository,
|
$renoteMutingsRepository,
|
||||||
$blockingsRepository,
|
$blockingsRepository,
|
||||||
$swSubscriptionsRepository,
|
$swSubscriptionsRepository,
|
||||||
|
$systemAccountsRepository,
|
||||||
$hashtagsRepository,
|
$hashtagsRepository,
|
||||||
$abuseUserReportsRepository,
|
$abuseUserReportsRepository,
|
||||||
$abuseReportNotificationRecipientRepository,
|
$abuseReportNotificationRecipientRepository,
|
||||||
|
31
packages/backend/src/models/SystemAccount.ts
Normal file
31
packages/backend/src/models/SystemAccount.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { Serialized } from '@/types.js';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
|
@Entity('system_account')
|
||||||
|
@Index(['type'], { unique: true })
|
||||||
|
export class MiSystemAccount {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column(id())
|
||||||
|
public userId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: MiUser | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256,
|
||||||
|
})
|
||||||
|
public type: string;
|
||||||
|
}
|
@ -184,12 +184,6 @@ export class MiUser {
|
|||||||
})
|
})
|
||||||
public isCat: boolean;
|
public isCat: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
|
||||||
default: false,
|
|
||||||
comment: 'Whether the User is the root.',
|
|
||||||
})
|
|
||||||
public isRoot: boolean;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -56,6 +56,7 @@ import { MiRegistryItem } from '@/models/RegistryItem.js';
|
|||||||
import { MiRelay } from '@/models/Relay.js';
|
import { MiRelay } from '@/models/Relay.js';
|
||||||
import { MiSignin } from '@/models/Signin.js';
|
import { MiSignin } from '@/models/Signin.js';
|
||||||
import { MiSwSubscription } from '@/models/SwSubscription.js';
|
import { MiSwSubscription } from '@/models/SwSubscription.js';
|
||||||
|
import { MiSystemAccount } from '@/models/SystemAccount.js';
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
import { MiUserIp } from '@/models/UserIp.js';
|
import { MiUserIp } from '@/models/UserIp.js';
|
||||||
@ -171,6 +172,7 @@ export {
|
|||||||
MiRelay,
|
MiRelay,
|
||||||
MiSignin,
|
MiSignin,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
|
MiSystemAccount,
|
||||||
MiUsedUsername,
|
MiUsedUsername,
|
||||||
MiUser,
|
MiUser,
|
||||||
MiUserIp,
|
MiUserIp,
|
||||||
@ -242,6 +244,7 @@ export type RegistryItemsRepository = Repository<MiRegistryItem> & MiRepository<
|
|||||||
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
|
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
|
||||||
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
|
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
|
||||||
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
|
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
|
||||||
|
export type SystemAccountsRepository = Repository<MiSystemAccount> & MiRepository<MiSystemAccount>;
|
||||||
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
|
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
|
||||||
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
|
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
|
||||||
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
|
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
|
||||||
|
@ -82,6 +82,7 @@ import { MiReversiGame } from '@/models/ReversiGame.js';
|
|||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
import MisskeyLogger from '@/logger.js';
|
import MisskeyLogger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { MiSystemAccount } from './models/SystemAccount.js';
|
||||||
|
|
||||||
pg.types.setTypeParser(20, Number);
|
pg.types.setTypeParser(20, Number);
|
||||||
|
|
||||||
@ -206,6 +207,7 @@ export const entities = [
|
|||||||
MiEmoji,
|
MiEmoji,
|
||||||
MiHashtag,
|
MiHashtag,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
|
MiSystemAccount,
|
||||||
MiAbuseUserReport,
|
MiAbuseUserReport,
|
||||||
MiAbuseReportNotificationRecipient,
|
MiAbuseReportNotificationRecipient,
|
||||||
MiRegistrationTicket,
|
MiRegistrationTicket,
|
||||||
|
@ -9,11 +9,11 @@ import type { Config } from '@/config.js';
|
|||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
import { MemorySingleCache } from '@/misc/cache.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import NotesChart from '@/core/chart/charts/notes.js';
|
import NotesChart from '@/core/chart/charts/notes.js';
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
||||||
const nodeinfo2_1path = '/nodeinfo/2.1';
|
const nodeinfo2_1path = '/nodeinfo/2.1';
|
||||||
@ -26,7 +26,7 @@ export class NodeinfoServerService {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private systemAccountService: SystemAccountService,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private notesChart: NotesChart,
|
private notesChart: NotesChart,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
@ -70,7 +70,7 @@ export class NodeinfoServerService {
|
|||||||
const activeHalfyear = null;
|
const activeHalfyear = null;
|
||||||
const activeMonth = null;
|
const activeMonth = null;
|
||||||
|
|
||||||
const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;
|
const proxyAccount = await this.systemAccountService.fetch('proxy');
|
||||||
|
|
||||||
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
|
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ export class NodeinfoServerService {
|
|||||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
||||||
enableEmail: meta.enableEmail,
|
enableEmail: meta.enableEmail,
|
||||||
enableServiceWorker: meta.enableServiceWorker,
|
enableServiceWorker: meta.enableServiceWorker,
|
||||||
proxyAccountName: proxyAccount ? proxyAccount.username : null,
|
proxyAccountName: proxyAccount.username,
|
||||||
themeColor: meta.themeColor ?? '#86b300',
|
themeColor: meta.themeColor ?? '#86b300',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -371,7 +371,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||||
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
@ -391,7 +391,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.requireRolePolicy != null && !user!.isRoot) {
|
if (ep.meta.requireRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||||
const policies = await this.roleService.getUserPolicies(user!.id);
|
const policies = await this.roleService.getUserPolicies(user!.id);
|
||||||
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||||
|
@ -100,6 +100,7 @@ export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.
|
|||||||
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
|
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
|
||||||
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
|
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
|
||||||
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
|
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
|
||||||
|
export * as 'admin/update-proxy-account' from './endpoints/admin/update-proxy-account.js';
|
||||||
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
|
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
|
||||||
export * as 'announcements' from './endpoints/announcements.js';
|
export * as 'announcements' from './endpoints/announcements.js';
|
||||||
export * as 'announcements/show' from './endpoints/announcements/show.js';
|
export * as 'announcements/show' from './endpoints/announcements/show.js';
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||||
import { SignupService } from '@/core/SignupService.js';
|
import { SignupService } from '@/core/SignupService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
@ -62,18 +60,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private signupService: SignupService,
|
private signupService: SignupService,
|
||||||
private instanceActorService: InstanceActorService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, _me, token) => {
|
super(meta, paramDef, async (ps, _me, token) => {
|
||||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||||
const realUsers = await this.instanceActorService.realLocalUsersPresent();
|
|
||||||
|
|
||||||
if (!realUsers && me == null && token == null) {
|
if (this.serverSettings.rootUserId == null && me == null && token == null) {
|
||||||
// 初回セットアップの場合
|
// 初回セットアップの場合
|
||||||
if (this.config.setupPassword != null) {
|
if (this.config.setupPassword != null) {
|
||||||
// 初期パスワードが設定されている場合
|
// 初期パスワードが設定されている場合
|
||||||
@ -85,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
||||||
throw new ApiError(meta.errors.wrongInitialPassword);
|
throw new ApiError(meta.errors.wrongInitialPassword);
|
||||||
}
|
}
|
||||||
} else if ((realUsers && !me?.isRoot) || token !== null) {
|
} else if ((this.serverSettings.rootUserId != null && (this.serverSettings.rootUserId !== me?.id)) || token !== null) {
|
||||||
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
||||||
throw new ApiError(meta.errors.accessDenied);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isRoot) {
|
|
||||||
throw new Error('cannot delete a root account');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.deleteAccoountService.deleteAccount(user, me);
|
await this.deleteAccoountService.deleteAccount(user, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import { MetaService } from '@/core/MetaService.js';
|
|||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
@ -237,7 +238,7 @@ export const meta = {
|
|||||||
},
|
},
|
||||||
proxyAccountId: {
|
proxyAccountId: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: false,
|
||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
@ -545,10 +546,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async () => {
|
super(meta, paramDef, async () => {
|
||||||
const instance = await this.metaService.fetch(true);
|
const instance = await this.metaService.fetch(true);
|
||||||
|
|
||||||
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
maintainerName: instance.maintainerName,
|
maintainerName: instance.maintainerName,
|
||||||
maintainerEmail: instance.maintainerEmail,
|
maintainerEmail: instance.maintainerEmail,
|
||||||
@ -613,7 +617,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
|
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
|
||||||
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
||||||
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
||||||
proxyAccountId: instance.proxyAccountId,
|
proxyAccountId: proxy.id,
|
||||||
email: instance.email,
|
email: instance.email,
|
||||||
smtpSecure: instance.smtpSecure,
|
smtpSecure: instance.smtpSecure,
|
||||||
smtpHost: instance.smtpHost,
|
smtpHost: instance.smtpHost,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
@ -43,6 +43,9 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@ -58,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isRoot) {
|
if (this.serverSettings.rootUserId === user.id) {
|
||||||
throw new Error('cannot reset password of root');
|
throw new Error('cannot reset password of root');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,6 @@ export const paramDef = {
|
|||||||
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
||||||
setSensitiveFlagAutomatically: { type: 'boolean' },
|
setSensitiveFlagAutomatically: { type: 'boolean' },
|
||||||
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
|
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
|
||||||
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
|
|
||||||
maintainerName: { type: 'string', nullable: true },
|
maintainerName: { type: 'string', nullable: true },
|
||||||
maintainerEmail: { type: 'string', nullable: true },
|
maintainerEmail: { type: 'string', nullable: true },
|
||||||
langs: {
|
langs: {
|
||||||
@ -394,10 +393,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
|
set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.proxyAccountId !== undefined) {
|
|
||||||
set.proxyAccountId = ps.proxyAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.maintainerName !== undefined) {
|
if (ps.maintainerName !== undefined) {
|
||||||
set.maintainerName = ps.maintainerName;
|
set.maintainerName = ps.maintainerName;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import {
|
||||||
|
descriptionSchema,
|
||||||
|
} from '@/models/User.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:account',
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
ref: 'UserDetailed',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
description: { ...descriptionSchema, nullable: true },
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const proxy = await this.systemAccountService.updateCorrespondingUserProfile('proxy', {
|
||||||
|
description: ps.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updated = await this.userEntityService.pack(proxy.id, proxy, {
|
||||||
|
schema: 'MeDetailed',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ps.description !== undefined) {
|
||||||
|
this.moderationLogService.log(me, 'updateProxyAccountDescription', {
|
||||||
|
before: null, //TODO
|
||||||
|
after: ps.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
@ -19,6 +19,8 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
@ -81,6 +83,9 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private remoteUserResolveService: RemoteUserResolveService,
|
private remoteUserResolveService: RemoteUserResolveService,
|
||||||
private apiLoggerService: ApiLoggerService,
|
private apiLoggerService: ApiLoggerService,
|
||||||
private accountMoveService: AccountMoveService,
|
private accountMoveService: AccountMoveService,
|
||||||
@ -92,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
// check parameter
|
// check parameter
|
||||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
||||||
// abort if user is the root
|
// abort if user is the root
|
||||||
if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
|
if (this.serverSettings.rootUserId === me.id) throw new ApiError(meta.errors.rootForbidden);
|
||||||
// abort if user has already moved
|
// abort if user has already moved
|
||||||
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||||
|
|
||||||
|
@ -6,9 +6,12 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { resetDb } from '@/misc/reset-db.js';
|
import { resetDb } from '@/misc/reset-db.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['non-productive'],
|
tags: ['non-productive'],
|
||||||
@ -36,13 +39,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
|
private loggerService: LoggerService,
|
||||||
|
private metaService: MetaService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
||||||
|
|
||||||
await redisClient.flushdb();
|
const logger = this.loggerService.getLogger('reset-db');
|
||||||
|
logger.info('---- Resetting database...');
|
||||||
|
|
||||||
|
await this.redisClient.flushdb();
|
||||||
await resetDb(this.db);
|
await resetDb(this.db);
|
||||||
|
|
||||||
|
// DIコンテナで管理しているmetaのインスタンスには上記のリセット処理が届かないため、
|
||||||
|
// 初期値を流して明示的にリフレッシュする
|
||||||
|
const meta = await this.metaService.fetch(true);
|
||||||
|
this.globalEventService.publishInternalEvent('metaUpdated', { after: meta });
|
||||||
|
|
||||||
|
logger.info('---- Database reset complete.');
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,7 @@ export const moderationLogTypes = [
|
|||||||
'deletePage',
|
'deletePage',
|
||||||
'deleteFlash',
|
'deleteFlash',
|
||||||
'deleteGalleryPost',
|
'deleteGalleryPost',
|
||||||
|
'updateProxyAccountDescription',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
@ -374,25 +375,29 @@ export type ModerationLogPayloads = {
|
|||||||
postUserUsername: string;
|
postUserUsername: string;
|
||||||
post: any;
|
post: any;
|
||||||
};
|
};
|
||||||
|
updateProxyAccountDescription: {
|
||||||
|
before: string | null;
|
||||||
|
after: string | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Serialized<T> = {
|
export type Serialized<T> = {
|
||||||
[K in keyof T]:
|
[K in keyof T]:
|
||||||
T[K] extends Date
|
T[K] extends Date
|
||||||
? string
|
? string
|
||||||
: T[K] extends (Date | null)
|
: T[K] extends (Date | null)
|
||||||
? (string | null)
|
? (string | null)
|
||||||
: T[K] extends Record<string, any>
|
: T[K] extends Record<string, any>
|
||||||
? Serialized<T[K]>
|
? Serialized<T[K]>
|
||||||
: T[K] extends (Record<string, any> | null)
|
: T[K] extends (Record<string, any> | null)
|
||||||
? (Serialized<T[K]> | null)
|
? (Serialized<T[K]> | null)
|
||||||
: T[K] extends (Record<string, any> | undefined)
|
: T[K] extends (Record<string, any> | undefined)
|
||||||
? (Serialized<T[K]> | undefined)
|
? (Serialized<T[K]> | undefined)
|
||||||
: T[K];
|
: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterUnionByProperty<
|
export type FilterUnionByProperty<
|
||||||
Union,
|
Union,
|
||||||
Property extends string | number | symbol,
|
Property extends string | number | symbol,
|
||||||
Condition
|
Condition,
|
||||||
> = Union extends Record<Property, Condition> ? Union : never;
|
> = Union extends Record<Property, Condition> ? Union : never;
|
||||||
|
@ -20,8 +20,12 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
a.test:
|
a.test:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
misskey.a.test:
|
||||||
|
condition: service_healthy
|
||||||
b.test:
|
b.test:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
misskey.b.test:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
|
- NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
|
||||||
|
@ -35,7 +35,7 @@ describe('Abuse report', () => {
|
|||||||
const reportsInB = await bModerator.client.request('admin/abuse-user-reports', {});
|
const reportsInB = await bModerator.client.request('admin/abuse-user-reports', {});
|
||||||
const reportInB = reportsInB.filter(report => report.comment.includes(comment))[0];
|
const reportInB = reportsInB.filter(report => report.comment.includes(comment))[0];
|
||||||
// NOTE: reporter is not Alice, and is not moderator in A
|
// NOTE: reporter is not Alice, and is not moderator in A
|
||||||
strictEqual(reportInB.reporter.url, 'https://a.test/@instance.actor');
|
strictEqual(reportInB.reporter.url, 'https://a.test/@system.actor');
|
||||||
strictEqual(reportInB.targetUserId, bob.id);
|
strictEqual(reportInB.targetUserId, bob.id);
|
||||||
|
|
||||||
// NOTE: cannot forward multiple times
|
// NOTE: cannot forward multiple times
|
||||||
|
@ -37,6 +37,7 @@ describe('User', () => {
|
|||||||
'id',
|
'id',
|
||||||
'host',
|
'host',
|
||||||
'avatarUrl',
|
'avatarUrl',
|
||||||
|
'avatarBlurhash',
|
||||||
'instance',
|
'instance',
|
||||||
'badgeRoles',
|
'badgeRoles',
|
||||||
'url',
|
'url',
|
||||||
@ -379,7 +380,8 @@ describe('User', () => {
|
|||||||
strictEqual(followers.length, 1); // followed by Bob
|
strictEqual(followers.length, 1); // followed by Bob
|
||||||
|
|
||||||
await alice.client.request('i/delete-account', { password: alice.password });
|
await alice.client.request('i/delete-account', { password: alice.password });
|
||||||
await sleep();
|
// NOTE: user deletion query is slow
|
||||||
|
await sleep(4000);
|
||||||
|
|
||||||
const following = await bob.client.request('users/following', { userId: bob.id });
|
const following = await bob.client.request('users/following', { userId: bob.id });
|
||||||
strictEqual(following.length, 0); // no following relation
|
strictEqual(following.length, 0); // no following relation
|
||||||
@ -477,7 +479,8 @@ describe('User', () => {
|
|||||||
strictEqual(followers.length, 1); // followed by Bob
|
strictEqual(followers.length, 1); // followed by Bob
|
||||||
|
|
||||||
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
|
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
|
||||||
await sleep();
|
// NOTE: user deletion query is slow
|
||||||
|
await sleep(4000);
|
||||||
|
|
||||||
const following = await bob.client.request('users/following', { userId: bob.id });
|
const following = await bob.client.request('users/following', { userId: bob.id });
|
||||||
strictEqual(following.length, 0); // no following relation
|
strictEqual(following.length, 0); // no following relation
|
||||||
|
@ -36,7 +36,7 @@ export type Request = <
|
|||||||
|
|
||||||
type Host = 'a.test' | 'b.test';
|
type Host = 'a.test' | 'b.test';
|
||||||
|
|
||||||
export async function sleep(ms = 200): Promise<void> {
|
export async function sleep(ms = 250): Promise<void> {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,14 +7,10 @@ import type { Config } from '@/config.js';
|
|||||||
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
||||||
import { Resolver } from '@/core/activitypub/ApResolverService.js';
|
|
||||||
import type { IObject } from '@/core/activitypub/type.js';
|
import type { IObject } from '@/core/activitypub/type.js';
|
||||||
import type { HttpRequestService } from '@/core/HttpRequestService.js';
|
import type { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import type { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import type { LoggerService } from '@/core/LoggerService.js';
|
import type { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { MetaService } from '@/core/MetaService.js';
|
|
||||||
import type { UtilityService } from '@/core/UtilityService.js';
|
import type { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import type {
|
import type {
|
||||||
FollowRequestsRepository,
|
FollowRequestsRepository,
|
||||||
MiMeta,
|
MiMeta,
|
||||||
@ -23,6 +19,9 @@ import type {
|
|||||||
PollsRepository,
|
PollsRepository,
|
||||||
UsersRepository,
|
UsersRepository,
|
||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { Resolver } from '@/core/activitypub/ApResolverService.js';
|
||||||
|
|
||||||
type MockResponse = {
|
type MockResponse = {
|
||||||
type: string;
|
type: string;
|
||||||
@ -43,7 +42,7 @@ export class MockResolver extends Resolver {
|
|||||||
{} as NoteReactionsRepository,
|
{} as NoteReactionsRepository,
|
||||||
{} as FollowRequestsRepository,
|
{} as FollowRequestsRepository,
|
||||||
{} as UtilityService,
|
{} as UtilityService,
|
||||||
{} as InstanceActorService,
|
{} as SystemAccountService,
|
||||||
{} as ApRequestService,
|
{} as ApRequestService,
|
||||||
{} as HttpRequestService,
|
{} as HttpRequestService,
|
||||||
{} as ApRendererService,
|
{} as ApRendererService,
|
||||||
|
@ -149,9 +149,9 @@ describe('AbuseReportNotificationService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
alice = await createUser({ username: 'alice', usernameLower: 'alice' });
|
||||||
bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false });
|
bob = await createUser({ username: 'bob', usernameLower: 'bob' });
|
||||||
systemWebhook1 = await createWebhook();
|
systemWebhook1 = await createWebhook();
|
||||||
systemWebhook2 = await createWebhook();
|
systemWebhook2 = await createWebhook();
|
||||||
|
|
||||||
|
@ -79,9 +79,9 @@ describe('FlashService', () => {
|
|||||||
userProfilesRepository = app.get(DI.userProfilesRepository);
|
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||||
idService = app.get(IdService);
|
idService = app.get(IdService);
|
||||||
|
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
alice = await createUser({ username: 'alice', usernameLower: 'alice' });
|
||||||
bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false });
|
bob = await createUser({ username: 'bob', usernameLower: 'bob' });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -3,24 +3,21 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import { jest } from '@jest/globals';
|
import { jest } from '@jest/globals';
|
||||||
import { ModuleMocker } from 'jest-mock';
|
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { ModuleMocker } from 'jest-mock';
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import type { RelaysRepository } from '@/models/_.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { TestingModule } from '@nestjs/testing';
|
import type { TestingModule } from '@nestjs/testing';
|
||||||
import type { MockFunctionMetadata } from 'jest-mock';
|
import type { MockFunctionMetadata } from 'jest-mock';
|
||||||
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
|
||||||
const moduleMocker = new ModuleMocker(global);
|
const moduleMocker = new ModuleMocker(global);
|
||||||
|
|
||||||
@ -28,8 +25,6 @@ describe('RelayService', () => {
|
|||||||
let app: TestingModule;
|
let app: TestingModule;
|
||||||
let relayService: RelayService;
|
let relayService: RelayService;
|
||||||
let queueService: jest.Mocked<QueueService>;
|
let queueService: jest.Mocked<QueueService>;
|
||||||
let relaysRepository: RelaysRepository;
|
|
||||||
let userEntityService: UserEntityService;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await Test.createTestingModule({
|
app = await Test.createTestingModule({
|
||||||
@ -38,10 +33,10 @@ describe('RelayService', () => {
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
IdService,
|
IdService,
|
||||||
CreateSystemUserService,
|
|
||||||
ApRendererService,
|
ApRendererService,
|
||||||
RelayService,
|
RelayService,
|
||||||
UserEntityService,
|
UserEntityService,
|
||||||
|
SystemAccountService,
|
||||||
UtilityService,
|
UtilityService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ -61,8 +56,6 @@ describe('RelayService', () => {
|
|||||||
|
|
||||||
relayService = app.get<RelayService>(RelayService);
|
relayService = app.get<RelayService>(RelayService);
|
||||||
queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>;
|
queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>;
|
||||||
relaysRepository = app.get<RelaysRepository>(DI.relaysRepository);
|
|
||||||
userEntityService = app.get<UserEntityService>(UserEntityService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -57,6 +57,12 @@ describe('RoleService', () => {
|
|||||||
return await usersRepository.findOneByOrFail(x.identifiers[0]);
|
return await usersRepository.findOneByOrFail(x.identifiers[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createRoot(data: Partial<MiUser> = {}) {
|
||||||
|
const user = await createUser(data);
|
||||||
|
meta.rootUserId = user.id;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
async function createRole(data: Partial<MiRole> = {}) {
|
async function createRole(data: Partial<MiRole> = {}) {
|
||||||
const x = await rolesRepository.insert({
|
const x = await rolesRepository.insert({
|
||||||
id: genAidx(Date.now()),
|
id: genAidx(Date.now()),
|
||||||
@ -279,7 +285,7 @@ describe('RoleService', () => {
|
|||||||
describe('getModeratorIds', () => {
|
describe('getModeratorIds', () => {
|
||||||
test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
|
test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -305,7 +311,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
|
test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -331,7 +337,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
|
test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -357,7 +363,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
|
test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -383,7 +389,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
|
test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -409,7 +415,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('root has moderator role', async () => {
|
test('root has moderator role', async () => {
|
||||||
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -433,7 +439,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('root has administrator role', async () => {
|
test('root has administrator role', async () => {
|
||||||
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@ -457,7 +463,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('root has moderator role(expire)', async () => {
|
test('root has moderator role(expire)', async () => {
|
||||||
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
|
@ -97,7 +97,7 @@ describe('SystemWebhookService', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function beforeEachImpl() {
|
async function beforeEachImpl() {
|
||||||
root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function afterEachImpl() {
|
async function afterEachImpl() {
|
||||||
|
@ -113,7 +113,7 @@ describe('UserSearchService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
|
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
|
||||||
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
|
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
|
||||||
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
|
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
|
||||||
|
@ -91,7 +91,7 @@ describe('UserWebhookService', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function beforeEachImpl() {
|
async function beforeEachImpl() {
|
||||||
root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function afterEachImpl() {
|
async function afterEachImpl() {
|
||||||
|
@ -88,8 +88,8 @@ describe('WebhookTestService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
alice = await createUser({ username: 'alice', usernameLower: 'alice' });
|
||||||
|
|
||||||
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
|
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
|
||||||
{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
|
{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
|
||||||
|
@ -316,7 +316,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
|
|||||||
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
||||||
createUser({}, { email: null, emailVerified: false }),
|
createUser({}, { email: null, emailVerified: false }),
|
||||||
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
||||||
createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }),
|
createUser({}, { email: 'root@example.com', emailVerified: true }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockModeratorRole([user1, user2, user3, root]);
|
mockModeratorRole([user1, user2, user3, root]);
|
||||||
@ -349,7 +349,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
|
|||||||
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
||||||
createUser({}, { email: null, emailVerified: false }),
|
createUser({}, { email: null, emailVerified: false }),
|
||||||
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
||||||
createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }),
|
createUser({}, { email: 'root@example.com', emailVerified: true }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockModeratorRole([user1, user2, user3, root]);
|
mockModeratorRole([user1, user2, user3, root]);
|
||||||
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkInfo v-if="['instance.actor', 'relay.actor'].includes(user.username)">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||||
|
|
||||||
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
||||||
|
|
||||||
@ -37,21 +37,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
-->
|
-->
|
||||||
<MkKeyValue oneline>
|
<template v-if="!isSystem">
|
||||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
<MkKeyValue oneline>
|
||||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||||
</MkKeyValue>
|
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||||
<MkKeyValue v-if="info" oneline>
|
</MkKeyValue>
|
||||||
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
<MkKeyValue v-if="info" oneline>
|
||||||
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
||||||
</MkKeyValue>
|
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
||||||
<MkKeyValue v-if="info" oneline>
|
</MkKeyValue>
|
||||||
<template #key>{{ i18n.ts.email }}</template>
|
<MkKeyValue v-if="info" oneline>
|
||||||
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
<template #key>{{ i18n.ts.email }}</template>
|
||||||
</MkKeyValue>
|
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkTextarea v-model="moderationNote" manualSave>
|
<MkTextarea v-if="!isSystem" v-model="moderationNote" manualSave>
|
||||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
@ -92,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</FormSection>
|
</FormSection>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<FormSection>
|
<FormSection v-if="!isSystem">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||||
|
|
||||||
@ -252,6 +254,7 @@ const ap = ref<any>(null);
|
|||||||
const moderator = ref(false);
|
const moderator = ref(false);
|
||||||
const silenced = ref(false);
|
const silenced = ref(false);
|
||||||
const suspended = ref(false);
|
const suspended = ref(false);
|
||||||
|
const isSystem = ref(false);
|
||||||
const moderationNote = ref('');
|
const moderationNote = ref('');
|
||||||
const filesPagination = {
|
const filesPagination = {
|
||||||
endpoint: 'admin/drive/files' as const,
|
endpoint: 'admin/drive/files' as const,
|
||||||
@ -288,6 +291,7 @@ function createFetcher() {
|
|||||||
silenced.value = info.value.isSilenced;
|
silenced.value = info.value.isSilenced;
|
||||||
suspended.value = info.value.isSuspended;
|
suspended.value = info.value.isSuspended;
|
||||||
moderationNote.value = info.value.moderationNote;
|
moderationNote.value = info.value.moderationNote;
|
||||||
|
isSystem.value = user.value.host == null && user.value.username.includes('.');
|
||||||
|
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
||||||
@ -507,7 +511,15 @@ watch(user, () => {
|
|||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => isSystem.value ? [{
|
||||||
|
key: 'overview',
|
||||||
|
title: i18n.ts.overview,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
}, {
|
||||||
|
key: 'raw',
|
||||||
|
title: 'Raw',
|
||||||
|
icon: 'ti ti-code',
|
||||||
|
}] : [{
|
||||||
key: 'overview',
|
key: 'overview',
|
||||||
title: i18n.ts.overview,
|
title: i18n.ts.overview,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
|
@ -170,6 +170,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
|
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="log.type === 'updateProxyAccountDescription'">
|
||||||
|
<div :class="$style.diff">
|
||||||
|
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>raw</summary>
|
<summary>raw</summary>
|
||||||
|
@ -238,15 +238,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #icon><i class="ti ti-ghost"></i></template>
|
<template #icon><i class="ti ti-ghost"></i></template>
|
||||||
<template #label>{{ i18n.ts.proxyAccount }}</template>
|
<template #label>{{ i18n.ts.proxyAccount }}</template>
|
||||||
|
<template v-if="proxyAccountForm.modified.value" #footer>
|
||||||
|
<MkFormFooter :form="proxyAccountForm"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.proxyAccount }}</template>
|
|
||||||
<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
|
|
||||||
<MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton>
|
<MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
|
||||||
|
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||||
|
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||||
|
</MkTextarea>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
@ -256,7 +258,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, reactive } from 'vue';
|
||||||
import XHeader from './_header_.vue';
|
import XHeader from './_header_.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
@ -277,7 +279,7 @@ import MkRadios from '@/components/MkRadios.vue';
|
|||||||
|
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
|
|
||||||
const proxyAccount = ref(meta.proxyAccountId ? await misskeyApi('users/show', { userId: meta.proxyAccountId }) : null);
|
const proxyAccount = await misskeyApi('users/show', { userId: meta.proxyAccountId });
|
||||||
|
|
||||||
const infoForm = useForm({
|
const infoForm = useForm({
|
||||||
name: meta.name ?? '',
|
name: meta.name ?? '',
|
||||||
@ -378,16 +380,14 @@ const federationForm = useForm({
|
|||||||
fetchInstance(true);
|
fetchInstance(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
function chooseProxyAccount() {
|
const proxyAccountForm = useForm({
|
||||||
os.selectUser({ localOnly: true }).then(user => {
|
description: proxyAccount.description,
|
||||||
proxyAccount.value = user;
|
}, async (state) => {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
await os.apiWithDialog('admin/update-proxy-account', {
|
||||||
proxyAccountId: user.id,
|
description: state.description,
|
||||||
}).then(() => {
|
|
||||||
fetchInstance(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
fetchInstance(true);
|
||||||
|
});
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<div class="profile _gaps">
|
<div class="profile _gaps">
|
||||||
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
||||||
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
|
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!"/>
|
||||||
|
<MkInfo v-if="user.host == null && user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||||
|
|
||||||
<div :key="user.id" class="main _panel">
|
<div :key="user.id" class="main _panel">
|
||||||
<div class="banner-container" :style="style">
|
<div class="banner-container" :style="style">
|
||||||
|
@ -42,8 +42,6 @@ describe('XHome', () => {
|
|||||||
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
|
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
|
||||||
assert.exists(anchor, 'anchor to the remote exists');
|
assert.exists(anchor, 'anchor to the remote exists');
|
||||||
assert.strictEqual(anchor?.href, 'https://example.com/@user/profile');
|
assert.strictEqual(anchor?.href, 'https://example.com/@user/profile');
|
||||||
|
|
||||||
assert.ok(anchor?.parentElement?.classList.contains('warn'), 'the parent is a warning');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The remote caution should fall back to uri if url is null', async () => {
|
test('The remote caution should fall back to uri if url is null', async () => {
|
||||||
|
@ -398,6 +398,12 @@ type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-r
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
|
type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminUpdateProxyAccountRequest = operations['admin___update-proxy-account']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminUpdateProxyAccountResponse = operations['admin___update-proxy-account']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
|
type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -1357,6 +1363,8 @@ declare namespace entities {
|
|||||||
AdminUnsuspendUserRequest,
|
AdminUnsuspendUserRequest,
|
||||||
AdminUpdateAbuseUserReportRequest,
|
AdminUpdateAbuseUserReportRequest,
|
||||||
AdminUpdateMetaRequest,
|
AdminUpdateMetaRequest,
|
||||||
|
AdminUpdateProxyAccountRequest,
|
||||||
|
AdminUpdateProxyAccountResponse,
|
||||||
AdminUpdateUserNoteRequest,
|
AdminUpdateUserNoteRequest,
|
||||||
AnnouncementsRequest,
|
AnnouncementsRequest,
|
||||||
AnnouncementsResponse,
|
AnnouncementsResponse,
|
||||||
|
@ -1005,6 +1005,17 @@ declare module '../api.js' {
|
|||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:account*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/update-proxy-account', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -122,6 +122,8 @@ import type {
|
|||||||
AdminUnsuspendUserRequest,
|
AdminUnsuspendUserRequest,
|
||||||
AdminUpdateAbuseUserReportRequest,
|
AdminUpdateAbuseUserReportRequest,
|
||||||
AdminUpdateMetaRequest,
|
AdminUpdateMetaRequest,
|
||||||
|
AdminUpdateProxyAccountRequest,
|
||||||
|
AdminUpdateProxyAccountResponse,
|
||||||
AdminUpdateUserNoteRequest,
|
AdminUpdateUserNoteRequest,
|
||||||
AnnouncementsRequest,
|
AnnouncementsRequest,
|
||||||
AnnouncementsResponse,
|
AnnouncementsResponse,
|
||||||
@ -676,6 +678,7 @@ export type Endpoints = {
|
|||||||
'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
|
'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
|
||||||
'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse };
|
'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse };
|
||||||
'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
|
'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
|
||||||
|
'admin/update-proxy-account': { req: AdminUpdateProxyAccountRequest; res: AdminUpdateProxyAccountResponse };
|
||||||
'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse };
|
'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse };
|
||||||
'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse };
|
'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse };
|
||||||
'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse };
|
'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse };
|
||||||
|
@ -125,6 +125,8 @@ export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner'
|
|||||||
export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
|
export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
|
||||||
export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json'];
|
export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json'];
|
||||||
export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
|
export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminUpdateProxyAccountRequest = operations['admin___update-proxy-account']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminUpdateProxyAccountResponse = operations['admin___update-proxy-account']['responses']['200']['content']['application/json'];
|
||||||
export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
|
export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
|
||||||
export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
|
export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
|
||||||
export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
|
export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
|
||||||
|
@ -834,6 +834,15 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations['admin___update-meta'];
|
post: operations['admin___update-meta'];
|
||||||
};
|
};
|
||||||
|
'/admin/update-proxy-account': {
|
||||||
|
/**
|
||||||
|
* admin/update-proxy-account
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:account*
|
||||||
|
*/
|
||||||
|
post: operations['admin___update-proxy-account'];
|
||||||
|
};
|
||||||
'/admin/update-user-note': {
|
'/admin/update-user-note': {
|
||||||
/**
|
/**
|
||||||
* admin/update-user-note
|
* admin/update-user-note
|
||||||
@ -8285,7 +8294,7 @@ export type operations = {
|
|||||||
setSensitiveFlagAutomatically: boolean;
|
setSensitiveFlagAutomatically: boolean;
|
||||||
enableSensitiveMediaDetectionForVideos: boolean;
|
enableSensitiveMediaDetectionForVideos: boolean;
|
||||||
/** Format: id */
|
/** Format: id */
|
||||||
proxyAccountId: string | null;
|
proxyAccountId: string;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
smtpSecure: boolean;
|
smtpSecure: boolean;
|
||||||
smtpHost: string | null;
|
smtpHost: string | null;
|
||||||
@ -10626,8 +10635,6 @@ export type operations = {
|
|||||||
sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh';
|
sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh';
|
||||||
setSensitiveFlagAutomatically?: boolean;
|
setSensitiveFlagAutomatically?: boolean;
|
||||||
enableSensitiveMediaDetectionForVideos?: boolean;
|
enableSensitiveMediaDetectionForVideos?: boolean;
|
||||||
/** Format: misskey:id */
|
|
||||||
proxyAccountId?: string | null;
|
|
||||||
maintainerName?: string | null;
|
maintainerName?: string | null;
|
||||||
maintainerEmail?: string | null;
|
maintainerEmail?: string | null;
|
||||||
langs?: string[];
|
langs?: string[];
|
||||||
@ -10739,6 +10746,59 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/update-proxy-account
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:account*
|
||||||
|
*/
|
||||||
|
'admin___update-proxy-account': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
description?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['UserDetailed'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* admin/update-user-note
|
* admin/update-user-note
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user