From da033e346a84a47efd7308f3beb830b1f424172e Mon Sep 17 00:00:00 2001 From: gnuxie Date: Sat, 8 Jun 2024 17:54:13 +0100 Subject: [PATCH] TypeBox Schema for the Config file... This is the most boring and demotivating thing I have done in awhile. Jesus christ, I hated this and it took me so long just to convince myself to start copy and pasting things. And I don't know why --- config/default.yaml | 32 +---- src/ConfigSchema.ts | 312 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 315 insertions(+), 29 deletions(-) create mode 100644 src/ConfigSchema.ts diff --git a/config/default.yaml b/config/default.yaml index e9c001bf..2e97c06e 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -62,25 +62,14 @@ recordIgnoredInvites: false # (see verboseLogging to adjust this a bit.) managementRoom: "#moderators:example.org" -# Deprecated and will be removed in a future version. -# Running with verboseLogging is unsupported. -# Whether Draupnir should log a lot more messages in the room, -# mainly involves "all-OK" messages, and debugging messages for when draupnir checks bans in a room. -verboseLogging: false - # The log level of terminal (or container) output, # can be one of DEBUG, INFO, WARN and ERROR, in increasing order of importance and severity. # -# This should be at INFO or DEBUG in order to get support for Draupnir problems. +# This should set to INFO or DEBUG in order to get support for Draupnir issues logLevel: "INFO" -# Whether or not Draupnir should synchronize policy lists immediately after startup. -# Equivalent to running '!draupnir sync'. -syncOnStartup: true - -# Whether or not Draupnir should check moderation permissions in all protected rooms on startup. -# Equivalent to running `!draupnir verify`. -verifyPermissionsOnStartup: true +# FIXME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# WARN for these options that have been delted. # Whether or not Draupnir should actually apply bans and policy lists, # turn on to trial some untrusted configuration or lists. @@ -90,12 +79,6 @@ noop: false # DO NOT change this to `true` unless you are very confident that you know what you are doing. disableServerACL: false -# Whether Draupnir should check member lists quicker (by using a different endpoint), -# keep in mind that enabling this will miss invited (but not joined) users. -# -# Turn on if your bot is in (very) large rooms, or in large amounts of rooms. -fasterMembershipChecks: false - # A case-insensitive list of ban reasons to have the bot also automatically redact the user's messages for. # # If the bot sees you ban a user with a reason that is an (exact case-insensitive) match to this list, @@ -111,15 +94,6 @@ automaticallyRedactForReasons: - "spam" - "advertising" -# A list of rooms to protect. Draupnir will add this to the list it knows from its account data. -# -# It won't, however, add it to the account data. -# Manually add the room via '!draupnir rooms add' to have it stay protected regardless if this config value changes. -# -# Note: These must be matrix.to URLs -protectedRooms: - - "https://matrix.to/#/#yourroom:example.org" - # Whether or not to add all joined rooms to the "protected rooms" list # (excluding the management room and watched policy list rooms, see below). # diff --git a/src/ConfigSchema.ts b/src/ConfigSchema.ts new file mode 100644 index 00000000..ed15472a --- /dev/null +++ b/src/ConfigSchema.ts @@ -0,0 +1,312 @@ +// SPDX-FileCopyrightText: 2024 Gnuxie +// SPDX-FileCopyrightText: 2019 2021 The Matrix.org Foundation C.I.C. +// +// SPDX-License-Identifier: AFL-3.0 AND Apache-2.0 +// +// SPDX-FileAttributionText: +// This modified file incorporates work from mjolnir +// https://github.com/matrix-org/mjolnir +// + +import { Type } from "@sinclair/typebox"; +import { EDStatic, Permalink, StringRoomAlias, StringRoomID } from "matrix-protection-suite"; + +const PantalaimonConfig = Type.Object({ + use: Type.Boolean({ + description: "Whether or not Draupnir will use pantalaimon to access the matrix homeserver,\ + set to `true` if you're using pantalaimon.\ + Be sure to point homeserverUrl to the pantalaimon instance.\ + Draupnir will log in using the given username and password once,\ + then store the resulting access token in a file under dataPath.", + }), + username: Type.String({ + description: "The username for Draupnir to log into Pantalaimon with." + }), + password: Type.String({ + description: "The password Draupnir will log into Pantalaimon with.\ + After successfully logging in once, this will be ignored, so this value can be blanked after the first startup.\ + This option can be loaded from a file by passing \"--pantalaimon-password-path \" at the command line,\ + which would allow using secret management systems such as systemd's service credentials." + }), +}, { + description: "Options related to Pantalaimon (https://github.com/matrix-org/pantalaimon)" +}); + +const AdminConfig = Type.Object({ + enableMakeRoomAdminCommand: Type.Boolean({ + description: `Whether or not Draupnir can temporarily take control of any eligible account from the local homeserver who's in the room + (with enough permissions) to "make" a user an admin. + + This only works if a local user with enough admin permissions is present in the room.` + }) +}, { + description: `Server administration commands, these commands will only work if Draupnir is + a global server administrator, and the bot's server is a Synapse instance.` +}); + +const BanCommandConfig = Type.Object({ + defaultReasons: Type.Array(Type.String(), { + description: "The default reasons to be prompted with if the reason is missing from a ban command." + }) +}, { + description: "Options for the ban command." +}); + +const WordlistProtectionConfig = Type.Object({ + words: Type.Array(Type.String(), { + description: `A list of case-insensitive keywords that the WordList protection will watch for from new users. + + WordList will ban users who use these words when first joining a room, so take caution when selecting them. + + For advanced usage, regex can also be used, see the following links for more information; + - https://www.digitalocean.com/community/tutorials/an-introduction-to-regular-expressions + - https://regexr.com/ + - https://regexone.com/` + }), + minutesBeforeTrusting: Type.Number({ + description: `For how long (in minutes) the user is "new" to the WordList plugin. + + After this time, the user will no longer be banned for using a word in the above wordlist. + + Set to zero to disable the timeout and make users *always* appear "new". + (users will always be banned if they say a bad word)` + }) +}, { + description: `Configuration for the wordlist plugin, which can ban users based if they say certain + blocked words shortly after joining.` +}) + +const ProtectionsConfig = Type.Object({ + wordlist: WordlistProtectionConfig +}) + +const CommandsConfig = Type.Object({ + // FIXME: Does this even do anything anymore? + allowNoPrefix: Type.Boolean({ + description: `Whether or not the \`!draupnir\` prefix is necessary to submit commands. + + If \`true\`, will allow commands like \`!ban\`, \`!help\`, etc. + + Note: Draupnir can also be pinged by display name instead of having to use + the !draupnir prefix. For example, "my_moderator_bot: ban @spammer:example.org" + will address only my_moderator_bot.` + }), + additionalPrefixes: Type.Array(Type.String(), { + description: "Any additional bot prefixes that Draupnir will listen to. i.e. adding `mod` will allow `!mod help`." + }), + confirmWildcardBan: Type.Boolean({ + description: `Whether or not commands with a wildcard (*) will require an additional \`--force\` argument + in the command to be able to be submitted.` + }), + ban: BanCommandConfig, +}, { + description: "Misc options for command handling and commands" +}); + +const RoomStateBackingStoreConfig = Type.Object({ + enabled: Type.Boolean(), +}, +{ + description: `The room state backing store writes a copy of the room state for all protected + rooms to the data directory. + It is recommended to enable this option unless you deploy Draupnir close to the + homeserver and know that Draupnir is starting up quickly. If your homeserver can + respond quickly to Draupnir's requests for \`/state\` then you might not need this option.` +}); + +const HealthzConfig = Type.Object({ + enabled: Type.Boolean({ + description: "Whether the healthz integration should be enabled (default false)", + }), + port: Type.Integer({ + description: "The port to expose the webserver on. Defaults to 8080.", + }), + address: Type.String({ + description: "The address to listen for requests on. Defaults to all addresses." + }), + endpoint: Type.String({ + description: "The path to expose the monitoring endpoint at. Defaults to `/healthz`" + }), + healthyStatus: Type.Integer({ + description: `The HTTP status code which reports that the bot is healthy/ready to + process requests. Typically this should not be changed. Defaults to 200.` + }), + unhealthyStatus: Type.Integer({ + description: `The HTTP status code which reports that the bot is not healthy/ready. + Defaults to 418.` + }), +}, { + description: `healthz options. These options are best for use in container environments + like Kubernetes to detect how healthy the service is. The bot will report + that it is unhealthy until it is able to process user requests. Typically + this means that it'll flag itself as unhealthy for a number of minutes + before saying "Now monitoring rooms" and flagging itself healthy.".` +}); + +const SentryConfig = Type.Object({ + dsn: Type.String({ + description: `The key used to upload Sentry data to the server. + dsn: "https://XXXXXXXXX@example.com/YYY` + }), + tracesSampleRate: Type.Number({ + description: `Frequency of performance monitoring. + A number in [0.0, 1.0], where 0.0 means "don't bother with tracing" + and 1.0 means "trace performance at every opportunity".` + }) +}, { + description: `Sentry options. Sentry is a tool used to receive/collate/triage runtime + errors and performance issues. Skip this section if you do not wish to use Sentry.`, +}) + +const HealthConfig = Type.Object({ + healthz: HealthzConfig, + sentry: Type.Optional(SentryConfig), +}, { + description: "Options for advanced monitoring of the health of the bot." +}) + +const AbuseReportingConfig = Type.Object({ + enabled: Type.Boolean({ + description: "Whether to enable the abuse reporting feature." + }) +}, { + description: `A web API designed to intercept Matrix API + POST /_matrix/client/r0/rooms/{roomId}/report/{eventId} + and display readable abuse reports in the moderation room. + + If you wish to take advantage of this feature, you will need + to configure a reverse proxy, see e.g. test/nginx.conf` +}) + +const WebConfig = Type.Object({ + enabled: Type.Boolean({ + description: "Whether to enable web APIs.", + }), + port: Type.Integer({ + description: "The port to expose the webserver on. Defaults to 8080." + }), + address: Type.String({ + description: "The address to listen for requests on. Defaults to only the current computer." + }), + abuseReporting: AbuseReportingConfig, + +}, { + description: "Options for exposing web APIs." +}) + +// FIXME: Would be nice if MPS had a transform for MatrixRoomReferences +export const ConfigSchema = Type.Object({ + homeserverUrl: Type.String({ + // I hate this? does matrix-bot-sdk know to use well knoen? what's going on here + description: "The URL to connect to the client server API with, this could be Pantalaimon." + }), + rawHomeserverUrl: Type.String({ + description: "The publical facing URL client server API and /_synapse/ are served under, note that this must not be the URL for Pantalaimon" + }), + accessToken: Type.String({ + description: ` + A Matrix access token for the Matrix user to authenticate with. + Draupnir will only use this if pantalaimon.use is false. + This option can be loaded from a file by passing "--access-token-path " at the command line, + which would allow using secret management systems such as systemd's service credentials.`, + }), + pantalaimon: PantalaimonConfig, + experimentalRustCrypto: Type.Boolean({ + description: "\ + Experimental usage of the matrix-bot-sdk rust crypto.\ + This can not be used with Pantalaimon.\ + Make sure to setup the bot as if you are NOT using pantalaimon for this.\ + Warning: At this time this is not considered production safe." + }), + dataPath: Type.String({ + description: 'The path Draupnir will store its state/data in, leave default ("/data/storage") when using containers.' + }), + autojoinOnlyIfManager: Type.Boolean({ + description: "If true (the default), Draupnir will only accept invites from users present in managementRoom." + }), + acceptInvitesFromSpace: Type.Union([Permalink, StringRoomID, StringRoomAlias], { + description: "If `autojoinOnlyIfManager` is false, only the members in this space can invite the bot to protect new rooms." + }), + recordIgnoredInvites: Type.Boolean({ + description: "Whether Draupnir should report ignored invites to the management room (if autojoinOnlyIfManager is true)." + }), + managementRoom: Type.Union([Permalink, StringRoomID, StringRoomAlias], { + description: "The room ID (or room alias) of the management room, anyone in this room can issue commands to Draupnir.\ + Draupnir has no more granular access controls other than this, be sure you trust everyone in this room - secure it!\ + This should be a room alias or room ID - not a matrix.to URL.\ + Note: By default, Draupnir is fairly verbose - expect a lot of messages in this room.\ + (see verboseLogging to adjust this a bit.)" + }), + logLevel: Type.Union([ + Type.Literal("DEBUG"), + Type.Literal("INFO"), + Type.Literal("WARN"), + Type.Literal("ERROR") + ], + { + description: "The log level of terminal (or container) output,\ + can be one of DEBUG, INFO, WARN and ERROR, in increasing order of importance and severity.\ + This should set to INFO or DEBUG in order to get support for Draupnir issues." + }), + logMutedModules: Type.Array(Type.String(), { + description: "Modules to mute in the log output" + }), + noop: Type.Boolean({ + description: "Whether or not Draupnir should actually apply bans and policy lists,\ + turn on to trial some untrusted configuration or lists." + }), + disableServerACL: Type.Boolean({ + description: "# Whether or not Draupnir should apply `m.room.server_acl` events.\ + DO NOT change this to `true` unless you are very confident that you know what you are doing." + }), + automaticallyRedactForReasons: Type.Array(Type.String(), { + description: `A case-insensitive list of ban reasons to have the bot also automatically redact the user's messages for. + + If the bot sees you ban a user with a reason that is an (exact case-insensitive) match to this list, + it will also remove the user's messages automatically. + # + Typically this is useful to avoid having to give two commands to the bot. + Advanced: Use asterisks to have the reason match using "globs" + (f.e. "spam*testing" would match "spam for testing" as well as "spamtesting"). + + See here for more info: https://www.digitalocean.com/community/tools/glob + Note: Keep in mind that glob is NOT regex!` + }), + protectAllJoinedRooms: Type.Boolean({ + description: `Whether or not to add all joined rooms to the "protected rooms" list + (excluding the management room and watched policy list rooms, see below). + + Note that this effectively makes the protectedRooms and associated commands useless + for regular rooms. + + Note: the management room is *excluded* from this condition. + Explicitly add it as a protected room to protect it. + + Note: Ban list rooms the bot is watching but didn't create will not be protected. + Explicitly add these rooms as a protected room list if you want them protected.` + }), + backgroundDelayMS: Type.Integer({ + description: `Increase this delay to have Draupnir wait longer between two consecutive backgrounded + operations. The total duration of operations will be longer, but the homeserver won't + be affected as much. Conversely, decrease this delay to have Draupnir chain operations + faster. The total duration of operations will generally be shorter, but the performance + of the homeserver may be more impacted.` + }), + pollReports: Type.Boolean({ + description: `Whether or not to actively poll synapse for abuse reports, to be used + instead of intercepting client calls to synapse's abuse endpoint, when that + isn't possible/practical.` + }), + displayReports: Type.Boolean({ + description: `Whether or not new reports, received either by webapi or polling, + should be printed to our managementRoom.` + }), + admin: Type.Optional(AdminConfig), + commands: CommandsConfig, + protections: ProtectionsConfig, + roomStateBackingStore: RoomStateBackingStoreConfig, + health: HealthConfig, + web: WebConfig, +}); +// eslint-disable-next-line no-redeclare +export type ConfigSchema = EDStatic;