diff --git a/.config/docker_example.yml b/.config/docker_example.yml index b3a6d7852..c6c83a98b 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -167,6 +167,9 @@ id: 'aidx' # IP address family used for outgoing request (ipv4, ipv6 or dual) #outgoingAddressFamily: ipv4 +# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1) +maxNoteLength: 3000 + # Proxy for HTTP/HTTPS #proxy: http://127.0.0.1:3128 @@ -197,6 +200,8 @@ proxyRemoteFiles: true # Sign to ActivityPub GET request (default: true) signToActivityPubGet: true +# check that inbound ActivityPub GET requests are signed ("authorized fetch") +checkActivityPubGetSignature: false # For security reasons, uploading attachments from the intranet is prohibited, # but exceptions can be made from the following settings. Default value is "undefined". diff --git a/.config/example.yml b/.config/example.yml index 28fe5b359..4aa7757c6 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -179,6 +179,9 @@ id: 'aidx' # IP address family used for outgoing request (ipv4, ipv6 or dual) #outgoingAddressFamily: ipv4 +# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1) +maxNoteLength: 3000 + # Proxy for HTTP/HTTPS #proxy: http://127.0.0.1:3128 diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 24a0296aa..dfaf186e0 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -7,8 +7,8 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; -import type { RedisOptions } from 'ioredis'; import { globSync } from 'glob'; +import type { RedisOptions } from 'ioredis'; type RedisOptionsSource = Partial & { host: string; @@ -65,6 +65,7 @@ type Source = { allowedPrivateNetworks?: string[]; maxFileSize?: number; + maxNoteLength?: number; clusterLimit?: number; @@ -133,6 +134,7 @@ export type Config = { proxyBypassHosts: string[] | undefined; allowedPrivateNetworks: string[] | undefined; maxFileSize: number | undefined; + maxNoteLength: number; clusterLimit: number | undefined; id: string; outgoingAddress: string | undefined; @@ -249,6 +251,7 @@ export function loadConfig(): Config { proxyBypassHosts: config.proxyBypassHosts, allowedPrivateNetworks: config.allowedPrivateNetworks, maxFileSize: config.maxFileSize, + maxNoteLength: config.maxNoteLength ?? 3000, clusterLimit: config.clusterLimit, outgoingAddress: config.outgoingAddress, outgoingAddressFamily: config.outgoingAddressFamily, diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 37a120a6f..31479269b 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -7,7 +7,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { MemorySingleCache } from '@/misc/cache.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -118,7 +117,7 @@ export class NodeinfoServerService { emailRequiredForSignup: meta.emailRequiredForSignup, enableHcaptcha: meta.enableHcaptcha, enableRecaptcha: meta.enableRecaptcha, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, + maxNoteTextLength: this.config.maxNoteLength, enableEmail: meta.enableEmail, enableServiceWorker: meta.enableServiceWorker, proxyAccountName: proxyAccount ? proxyAccount.username : null, diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 9ba22f89b..1d0c102c9 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -7,7 +7,6 @@ import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import JSON5 from 'json5'; import type { AdsRepository, UsersRepository } from '@/models/_.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MetaService } from '@/core/MetaService.js'; @@ -375,7 +374,7 @@ export default class extends Endpoint { // eslint- iconUrl: instance.iconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, + maxNoteTextLength: this.config.maxNoteLength, // クライアントの手間を減らすためあらかじめJSONに変換しておく defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null, defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 27743dfff..ac0a7f3b5 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -11,7 +11,7 @@ import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesR import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiNote } from '@/models/Note.js'; import type { MiChannel } from '@/models/Channel.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import type { Config } from '@/config.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; @@ -82,6 +82,12 @@ export const meta = { id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', }, + maxLength: { + message: 'You tried posting a note which is too long.', + code: 'MAX_LENGTH', + id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16', + }, + cannotCreateAlreadyExpiredPoll: { message: 'Poll is already expired.', code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', @@ -136,7 +142,6 @@ export const paramDef = { text: { type: 'string', minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true, }, fileIds: { @@ -184,6 +189,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -203,6 +211,10 @@ export default class extends Endpoint { // eslint- private noteCreateService: NoteCreateService, ) { super(meta, paramDef, async (ps, me) => { + if (ps.text && (ps.text.length > this.config.maxNoteLength)) { + throw new ApiError(meta.errors.maxLength); + } + let visibleUsers: MiUser[] = []; if (ps.visibleUserIds) { visibleUsers = await this.usersRepository.findBy({ diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts index cfbc20785..0c9c0d3ba 100644 --- a/packages/backend/src/server/api/endpoints/notes/edit.ts +++ b/packages/backend/src/server/api/endpoints/notes/edit.ts @@ -6,7 +6,7 @@ import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesR import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiNote } from '@/models/Note.js'; import type { MiChannel } from '@/models/Channel.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import type { Config } from '@/config.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteEditService } from '@/core/NoteEditService.js'; @@ -135,6 +135,12 @@ export const meta = { code: 'CANNOT_QUOTE_THE_CURRENT_NOTE', id: '33510210-8452-094c-6227-4a6c05d99f02', }, + + maxLength: { + message: 'You tried posting a note which is too long.', + code: 'MAX_LENGTH', + id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16', + }, }, } as const; @@ -163,7 +169,6 @@ export const paramDef = { text: { type: 'string', minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true, }, fileIds: { @@ -205,7 +210,6 @@ export const paramDef = { text: { type: 'string', minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false, }, }, @@ -236,6 +240,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -255,6 +262,9 @@ export default class extends Endpoint { // eslint- private noteEditService: NoteEditService, ) { super(meta, paramDef, async (ps, me) => { + if (ps.text && (ps.text.length > this.config.maxNoteLength)) { + throw new ApiError(meta.errors.maxLength); + } let visibleUsers: MiUser[] = []; if (ps.visibleUserIds) { visibleUsers = await this.usersRepository.findBy({ diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 61713b341..efb39ef93 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -1,5 +1,5 @@ import { Entity } from 'megalodon'; -import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import type { Config } from '@/config.js'; import type { MiMeta } from '@/models/Meta.js'; @@ -35,7 +35,7 @@ export async function getInstance( max_featured_tags: 20, }, statuses: { - max_characters: MAX_NOTE_TEXT_LENGTH, + max_characters: config.maxNoteLength, max_media_attachments: 16, characters_reserved_per_url: response.uri.length, },