summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/admin
diff options
context:
space:
mode:
authorJulia <julia@insertdomain.name>2024-11-05 03:59:23 +0000
committerJulia <julia@insertdomain.name>2024-11-05 03:59:23 +0000
commit680e3ac7a3084313ed4857ffca2c582c5b3c7348 (patch)
tree5621986847b8390b7c4f8e2f63cc53b180982b67 /packages/backend/src/server/api/endpoints/admin
parentmerge: Bump version (!635) (diff)
parentcomment out sharkey-specific crowdin link (diff)
downloadsharkey-680e3ac7a3084313ed4857ffca2c582c5b3c7348.tar.gz
sharkey-680e3ac7a3084313ed4857ffca2c582c5b3c7348.tar.bz2
sharkey-680e3ac7a3084313ed4857ffca2c582c5b3c7348.zip
merge: release 2024.9.1 (!733)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/733 Approved-by: Marie <github@yuugi.dev> Approved-by: Julia <julia@insertdomain.name>
Diffstat (limited to 'packages/backend/src/server/api/endpoints/admin')
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/create.ts63
-rw-r--r--packages/backend/src/server/api/endpoints/admin/decline-user.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts50
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/admin/reset-password.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-user.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts77
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts52
11 files changed, 371 insertions, 35 deletions
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index a7e8a3b018..7754899b95 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -3,16 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
+import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository } from '@/models/_.js';
+import { MiAccessToken, MiUser } from '@/models/_.js';
import { SignupService } from '@/core/SignupService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
-import { DI } from '@/di-symbols.js';
import { Packed } from '@/misc/json-schema.js';
+import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['admin'],
@@ -28,6 +28,35 @@ export const meta = {
},
},
},
+
+ errors: {
+ // From ApiCallService.ts
+ noCredential: {
+ message: 'Credential required.',
+ code: 'CREDENTIAL_REQUIRED',
+ id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+ httpStatusCode: 401,
+ },
+ noAdmin: {
+ message: 'You are not assigned to an administrator role.',
+ code: 'ROLE_PERMISSION_DENIED',
+ kind: 'permission',
+ id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+ },
+ noPermission: {
+ message: 'Your app does not have the necessary permissions to use this endpoint.',
+ code: 'PERMISSION_DENIED',
+ kind: 'permission',
+ id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
+ },
+ },
+
+ // Required token permissions, but we need to check them manually.
+ // ApiCallService checks access in a way that would prevent creating the first account.
+ softPermissions: [
+ 'write:admin:account',
+ 'write:admin:approve-user',
+ ],
} as const;
export const paramDef = {
@@ -42,22 +71,19 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
+ private roleService: RoleService,
private userEntityService: UserEntityService,
private signupService: SignupService,
private instanceActorService: InstanceActorService,
) {
super(meta, paramDef, async (ps, _me, token) => {
- const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
- const realUsers = await this.instanceActorService.realLocalUsersPresent();
- if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
+ await this.ensurePermissions(_me, token);
const { account, secret } = await this.signupService.signup({
username: ps.username,
password: ps.password,
ignorePreservedUsernames: true,
+ approved: true,
});
const res = await this.userEntityService.pack(account, account, {
@@ -70,4 +96,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return res;
});
}
+
+ private async ensurePermissions(me: MiUser | null, token: MiAccessToken | null): Promise<void> {
+ // Tokens have scoped permissions which may be *less* than the user's official role, so we need to check.
+ if (token && !meta.softPermissions.every(p => token.permission.includes(p))) {
+ throw new ApiError(meta.errors.noPermission);
+ }
+
+ // Only administrators (including root) can create users.
+ if (me && !await this.roleService.isAdministrator(me)) {
+ throw new ApiError(meta.errors.noAdmin);
+ }
+
+ // Anonymous access is only allowed for initial instance setup.
+ if (!me && await this.instanceActorService.realLocalUsersPresent()) {
+ throw new ApiError(meta.errors.noCredential);
+ }
+ }
}
diff --git a/packages/backend/src/server/api/endpoints/admin/decline-user.ts b/packages/backend/src/server/api/endpoints/admin/decline-user.ts
new file mode 100644
index 0000000000..0a75dd977d
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/decline-user.ts
@@ -0,0 +1,75 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UsedUsernamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { DI } from '@/di-symbols.js';
+import { EmailService } from '@/core/EmailService.js';
+import { DeleteAccountService } from '@/core/DeleteAccountService.js';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+ kind: 'write:admin:decline-user',
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
+ @Inject(DI.usedUsernamesRepository)
+ private usedUsernamesRepository: UsedUsernamesRepository,
+
+ private moderationLogService: ModerationLogService,
+ private emailService: EmailService,
+ private deleteAccountService: DeleteAccountService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const user = await this.usersRepository.findOneBy({ id: ps.userId });
+
+ if (user == null || user.isDeleted) {
+ throw new Error('user not found or already deleted');
+ }
+
+ if (user.approved) {
+ throw new Error('user is already approved');
+ }
+
+ if (user.host) {
+ throw new Error('user is not local');
+ }
+
+ const profile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
+
+ if (profile?.email) {
+ this.emailService.sendEmail(profile.email, 'Account Declined',
+ 'Your Account has been declined!',
+ 'Your Account has been declined!');
+ }
+
+ await this.usedUsernamesRepository.delete({ username: user.username });
+
+ await this.deleteAccountService.deleteAccount(user);
+
+ this.moderationLogService.log(me, 'decline', {
+ userId: user.id,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index 9e93310746..601c898f52 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -31,15 +31,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- @Inject(DI.notesRepository)
+ @Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
- const followings = await this.followingsRepository.findBy({
- followerHost: ps.host,
- });
+ const followings = await this.followingsRepository.findBy([
+ {
+ followeeHost: ps.host,
+ },
+ {
+ followerHost: ps.host,
+ },
+ ]);
const pairs = await Promise.all(followings.map(f => Promise.all([
this.usersRepository.findOneByOrFail({ id: f.followerId }),
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
index 8b142910a6..daf19c4435 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -25,6 +25,7 @@ export const paramDef = {
host: { type: 'string' },
isSuspended: { type: 'boolean' },
isNSFW: { type: 'boolean' },
+ rejectReports: { type: 'boolean' },
moderationNote: { type: 'string' },
},
required: ['host'],
@@ -57,6 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.federatedInstanceService.update(instance.id, {
suspensionState,
isNSFW: ps.isNSFW,
+ rejectReports: ps.rejectReports,
moderationNote: ps.moderationNote,
});
@@ -74,6 +76,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
+ if (ps.isNSFW != null && instance.isNSFW !== ps.isNSFW) {
+ const message = ps.rejectReports ? 'setRemoteInstanceNSFW' : 'unsetRemoteInstanceNSFW';
+ this.moderationLogService.log(me, message, {
+ id: instance.id,
+ host: instance.host,
+ });
+ }
+
+ if (ps.rejectReports != null && instance.rejectReports !== ps.rejectReports) {
+ const message = ps.rejectReports ? 'rejectRemoteInstanceReports' : 'acceptRemoteInstanceReports';
+ this.moderationLogService.log(me, message, {
+ id: instance.id,
+ host: instance.host,
+ });
+ }
+
if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
id: instance.id,
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 063bb6751b..6e368eff43 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -73,6 +73,14 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ enableFC: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ fcSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
swPublickey: {
type: 'string',
optional: false, nullable: true,
@@ -110,6 +118,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ sidebarLogoUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
enableEmail: {
type: 'boolean',
optional: false, nullable: false,
@@ -124,7 +136,7 @@ export const meta = {
},
silencedHosts: {
type: 'array',
- optional: true,
+ optional: false,
nullable: false,
items: {
type: 'string',
@@ -215,6 +227,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ fcSecretKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
sensitiveMediaDetection: {
type: 'string',
optional: false, nullable: false,
@@ -396,6 +412,10 @@ export const meta = {
type: 'number',
optional: false, nullable: false,
},
+ enableReactionsBuffering: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
notesPerOneAd: {
type: 'number',
optional: false, nullable: false,
@@ -522,6 +542,26 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ trustedLinkUrlPatterns: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
+ federation: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ federationHosts: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
},
},
} as const;
@@ -572,6 +612,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
turnstileSiteKey: instance.turnstileSiteKey,
+ enableFC: instance.enableFC,
+ fcSiteKey: instance.fcSiteKey,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl,
@@ -582,6 +624,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
iconUrl: instance.iconUrl,
app192IconUrl: instance.app192IconUrl,
app512IconUrl: instance.app512IconUrl,
+ sidebarLogoUrl: instance.sidebarLogoUrl,
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,
defaultLightTheme: instance.defaultLightTheme,
@@ -605,6 +648,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
mcaptchaSecretKey: instance.mcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
+ fcSecretKey: instance.fcSecretKey,
sensitiveMediaDetection: instance.sensitiveMediaDetection,
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
@@ -656,6 +700,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
+ enableReactionsBuffering: instance.enableReactionsBuffering,
notesPerOneAd: instance.notesPerOneAd,
summalyProxy: instance.urlPreviewSummaryProxyUrl,
urlPreviewEnabled: instance.urlPreviewEnabled,
@@ -664,6 +709,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
urlPreviewUserAgent: instance.urlPreviewUserAgent,
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
+ trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns,
+ federation: instance.federation,
+ federationHosts: instance.federationHosts,
};
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
index 7a3410ffa7..f3e440b4cb 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -21,16 +21,15 @@ export const meta = {
items: {
type: 'array',
optional: false, nullable: false,
- items: {
- anyOf: [
- {
- type: 'string',
- },
- {
- type: 'number',
- },
- ],
- },
+ prefixItems: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'number',
+ },
+ ],
+ unevaluatedItems: false,
},
example: [[
'example.com',
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
index 305ae1af1d..e7589cba81 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -21,16 +21,15 @@ export const meta = {
items: {
type: 'array',
optional: false, nullable: false,
- items: {
- anyOf: [
- {
- type: 'string',
- },
- {
- type: 'number',
- },
- ],
- },
+ prefixItems: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'number',
+ },
+ ],
+ unevaluatedItems: false,
},
example: [[
'example.com',
diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
index 828dbae712..e4bb545f5d 100644
--- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
@@ -11,6 +11,7 @@ import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
export const meta = {
tags: ['admin'],
@@ -63,6 +64,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('cannot reset password of root');
}
+ if (isSystemAccount(user)) {
+ throw new Error('cannot reset password of system account');
+ }
+
const passwd = secureRndstr(8);
// Generate hash of password
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index a7ca7f9547..669bffe2dc 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -11,6 +11,7 @@ import { RoleService } from '@/core/RoleService.js';
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
import { IdService } from '@/core/IdService.js';
import { notificationRecieveConfig } from '@/models/json-schema/user.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
export const meta = {
tags: ['admin'],
@@ -31,6 +32,14 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ approved: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ followedMessage: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
autoAcceptFollowed: {
type: 'boolean',
optional: false, nullable: false,
@@ -111,6 +120,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ isSystem: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
isSilenced: {
type: 'boolean',
optional: false, nullable: false,
@@ -228,6 +241,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailVerified: profile.emailVerified,
approved: user.approved,
signupReason: user.signupReason,
+ followedMessage: profile.followedMessage,
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
preventAiLearning: profile.preventAiLearning,
@@ -240,6 +254,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
mutedInstances: profile.mutedInstances,
notificationRecieveConfig: profile.notificationRecieveConfig,
isModerator: isModerator,
+ isSystem: isSystemAccount(user),
isSilenced: isSilenced,
isSuspended: user.isSuspended,
isHibernated: user.isHibernated,
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts
new file mode 100644
index 0000000000..fb2ddf4b44
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts
@@ -0,0 +1,77 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { WebhookTestService } from '@/core/WebhookTestService.js';
+import { ApiError } from '@/server/api/error.js';
+import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
+
+export const meta = {
+ tags: ['webhooks'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'read:admin:system-webhook',
+
+ limit: {
+ duration: ms('15min'),
+ max: 60,
+ },
+
+ errors: {
+ noSuchWebhook: {
+ message: 'No such webhook.',
+ code: 'NO_SUCH_WEBHOOK',
+ id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ webhookId: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ type: {
+ type: 'string',
+ enum: systemWebhookEventTypes,
+ },
+ override: {
+ type: 'object',
+ properties: {
+ url: { type: 'string', nullable: false },
+ secret: { type: 'string', nullable: false },
+ },
+ },
+ },
+ required: ['webhookId', 'type'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private webhookTestService: WebhookTestService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ try {
+ await this.webhookTestService.testSystemWebhook({
+ webhookId: ps.webhookId,
+ type: ps.type,
+ override: ps.override,
+ });
+ } catch (e) {
+ if (e instanceof WebhookTestService.NoSuchWebhookError) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+ throw e;
+ }
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 6bda1ae6ad..98760bbcc3 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -55,6 +55,7 @@ export const paramDef = {
iconUrl: { type: 'string', nullable: true },
app192IconUrl: { type: 'string', nullable: true },
app512IconUrl: { type: 'string', nullable: true },
+ sidebarLogoUrl: { type: 'string', nullable: true },
backgroundImageUrl: { type: 'string', nullable: true },
logoImageUrl: { type: 'string', nullable: true },
name: { type: 'string', nullable: true },
@@ -80,6 +81,9 @@ export const paramDef = {
enableTurnstile: { type: 'boolean' },
turnstileSiteKey: { type: 'string', nullable: true },
turnstileSecretKey: { type: 'string', nullable: true },
+ enableFC: { type: 'boolean' },
+ fcSiteKey: { type: 'string', nullable: true },
+ fcSecretKey: { type: 'string', nullable: true },
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
setSensitiveFlagAutomatically: { type: 'boolean' },
@@ -150,6 +154,7 @@ export const paramDef = {
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
perUserHomeTimelineCacheMax: { type: 'integer' },
perUserListTimelineCacheMax: { type: 'integer' },
+ enableReactionsBuffering: { type: 'boolean' },
notesPerOneAd: { type: 'integer' },
silencedHosts: {
type: 'array',
@@ -175,6 +180,21 @@ export const paramDef = {
urlPreviewRequireContentLength: { type: 'boolean' },
urlPreviewUserAgent: { type: 'string', nullable: true },
urlPreviewSummaryProxyUrl: { type: 'string', nullable: true },
+ trustedLinkUrlPatterns: {
+ type: 'array', nullable: true, items: {
+ type: 'string',
+ },
+ },
+ federation: {
+ type: 'string',
+ enum: ['all', 'none', 'specified'],
+ },
+ federationHosts: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
},
required: [],
} as const;
@@ -250,6 +270,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.app512IconUrl = ps.app512IconUrl;
}
+ if (ps.sidebarLogoUrl !== undefined) {
+ set.sidebarLogoUrl = ps.sidebarLogoUrl;
+ }
+
if (ps.serverErrorImageUrl !== undefined) {
set.serverErrorImageUrl = ps.serverErrorImageUrl;
}
@@ -362,6 +386,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.turnstileSecretKey = ps.turnstileSecretKey;
}
+ if (ps.enableFC !== undefined) {
+ set.enableFC = ps.enableFC;
+ }
+
+ if (ps.fcSiteKey !== undefined) {
+ set.fcSiteKey = ps.fcSiteKey;
+ }
+
+ if (ps.fcSecretKey !== undefined) {
+ set.fcSecretKey = ps.fcSecretKey;
+ }
+
if (ps.enableBotTrending !== undefined) {
set.enableBotTrending = ps.enableBotTrending;
}
@@ -626,6 +662,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax;
}
+ if (ps.enableReactionsBuffering !== undefined) {
+ set.enableReactionsBuffering = ps.enableReactionsBuffering;
+ }
+
if (ps.notesPerOneAd !== undefined) {
set.notesPerOneAd = ps.notesPerOneAd;
}
@@ -660,6 +700,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.urlPreviewSummaryProxyUrl = value === '' ? null : value;
}
+ if (Array.isArray(ps.trustedLinkUrlPatterns)) {
+ set.trustedLinkUrlPatterns = ps.trustedLinkUrlPatterns.filter(Boolean);
+ }
+
+ if (ps.federation !== undefined) {
+ set.federation = ps.federation;
+ }
+
+ if (Array.isArray(ps.federationHosts)) {
+ set.blockedHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
+ }
+
const before = await this.metaService.fetch(true);
await this.metaService.update(set);