From fdc4fd11a1ce463021ed1f6a8219fbd1f3b45695 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Sat, 29 Mar 2025 19:23:48 +0000 Subject: [PATCH 1/3] Move system admin commands to their own folder in commands directory. --- src/commands/DraupnirCommands.ts | 8 ++++---- src/commands/{ => server-admin}/DeactivateCommand.ts | 4 ++-- src/commands/{ => server-admin}/HijackRoomCommand.ts | 4 ++-- src/commands/{ => server-admin}/ShutdownRoomCommand.ts | 4 ++-- src/commands/{ => server-admin}/Takedown.tsx | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) rename src/commands/{ => server-admin}/DeactivateCommand.ts (92%) rename src/commands/{ => server-admin}/HijackRoomCommand.ts (94%) rename src/commands/{ => server-admin}/ShutdownRoomCommand.ts (94%) rename src/commands/{ => server-admin}/Takedown.tsx (95%) diff --git a/src/commands/DraupnirCommands.ts b/src/commands/DraupnirCommands.ts index f1bad558..07cd6eb2 100644 --- a/src/commands/DraupnirCommands.ts +++ b/src/commands/DraupnirCommands.ts @@ -15,9 +15,9 @@ import { } from "./AliasCommands"; import { DraupnirBanCommand } from "./Ban"; import { DraupnirListCreateCommand } from "./CreateBanListCommand"; -import { SynapseAdminDeactivateCommand } from "./DeactivateCommand"; +import { SynapseAdminDeactivateCommand } from "./server-admin/DeactivateCommand"; import { DraupnirHelpCommand } from "./Help"; -import { SynapseAdminHijackRoomCommand } from "./HijackRoomCommand"; +import { SynapseAdminHijackRoomCommand } from "./server-admin/HijackRoomCommand"; import { DraupnirImportCommand } from "./ImportCommand"; import { DraupnirKickCommand } from "./KickCommand"; import { @@ -43,7 +43,7 @@ import { } from "./Rules"; import { DraupnirDisplaynameCommand } from "./SetDisplayNameCommand"; import { DraupnirSetPowerLevelCommand } from "./SetPowerLevelCommand"; -import { SynapseAdminShutdownRoomCommand } from "./ShutdownRoomCommand"; +import { SynapseAdminShutdownRoomCommand } from "./server-admin/ShutdownRoomCommand"; import { DraupnirStatusCommand } from "./StatusCommand"; import { DraupnirUnbanCommand } from "./unban/Unban"; import { @@ -55,7 +55,7 @@ import { DraupnirSafeModeCommand } from "./SafeModeCommand"; import { DraupnirProtectionsShowCommand } from "./ProtectionsShowCommand"; import { DraupnirProtectionsCapabilityCommand } from "./ProtectionsCapabilitiesCommand"; import { JoinWaveCommandTable } from "../protections/JoinWaveShortCircuit"; -import { DraupnirTakedownCommand } from "./Takedown"; +import { DraupnirTakedownCommand } from "./server-admin/Takedown"; // TODO: These commands should all be moved to subdirectories tbh and this // should be split like an index file for each subdirectory. diff --git a/src/commands/DeactivateCommand.ts b/src/commands/server-admin/DeactivateCommand.ts similarity index 92% rename from src/commands/DeactivateCommand.ts rename to src/commands/server-admin/DeactivateCommand.ts index aec3e7e6..fa54f031 100644 --- a/src/commands/DeactivateCommand.ts +++ b/src/commands/server-admin/DeactivateCommand.ts @@ -15,8 +15,8 @@ import { tuple, } from "@the-draupnir-project/interface-manager"; import { ActionError } from "matrix-protection-suite"; -import { Draupnir } from "../Draupnir"; -import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites"; +import { Draupnir } from "../../Draupnir"; +import { DraupnirInterfaceAdaptor } from "../DraupnirCommandPrerequisites"; export const SynapseAdminDeactivateCommand = describeCommand({ summary: "Deactivate a user on the homeserver.", diff --git a/src/commands/HijackRoomCommand.ts b/src/commands/server-admin/HijackRoomCommand.ts similarity index 94% rename from src/commands/HijackRoomCommand.ts rename to src/commands/server-admin/HijackRoomCommand.ts index cef15c0c..e15996a6 100644 --- a/src/commands/HijackRoomCommand.ts +++ b/src/commands/server-admin/HijackRoomCommand.ts @@ -21,8 +21,8 @@ import { describeCommand, tuple, } from "@the-draupnir-project/interface-manager"; -import { Draupnir } from "../Draupnir"; -import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites"; +import { Draupnir } from "../../Draupnir"; +import { DraupnirInterfaceAdaptor } from "../DraupnirCommandPrerequisites"; export const SynapseAdminHijackRoomCommand = describeCommand({ summary: diff --git a/src/commands/ShutdownRoomCommand.ts b/src/commands/server-admin/ShutdownRoomCommand.ts similarity index 94% rename from src/commands/ShutdownRoomCommand.ts rename to src/commands/server-admin/ShutdownRoomCommand.ts index 2927b10b..bc01fb15 100644 --- a/src/commands/ShutdownRoomCommand.ts +++ b/src/commands/server-admin/ShutdownRoomCommand.ts @@ -15,9 +15,9 @@ import { describeCommand, tuple, } from "@the-draupnir-project/interface-manager"; -import { Draupnir } from "../Draupnir"; +import { Draupnir } from "../../Draupnir"; import { ActionError } from "matrix-protection-suite"; -import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites"; +import { DraupnirInterfaceAdaptor } from "../DraupnirCommandPrerequisites"; export const SynapseAdminShutdownRoomCommand = describeCommand({ summary: diff --git a/src/commands/Takedown.tsx b/src/commands/server-admin/Takedown.tsx similarity index 95% rename from src/commands/Takedown.tsx rename to src/commands/server-admin/Takedown.tsx index eada0010..81187b6c 100644 --- a/src/commands/Takedown.tsx +++ b/src/commands/server-admin/Takedown.tsx @@ -14,7 +14,7 @@ import { tuple, union, } from "@the-draupnir-project/interface-manager"; -import { findPolicyRoomEditorFromRoomReference } from "./Ban"; +import { findPolicyRoomEditorFromRoomReference } from "../Ban"; import { isError, Logger, @@ -38,10 +38,10 @@ import { import { DraupnirContextToCommandContextTranslator, DraupnirInterfaceAdaptor, -} from "./DraupnirCommandPrerequisites"; -import { renderRoomPill } from "./interface-manager/MatrixHelpRenderer"; -import { RoomDetailsProvider } from "../capabilities/RoomTakedownCapability"; -import { SynapseAdminRoomDetailsProvider } from "../capabilities/SynapseAdminRoomTakedown/SynapseAdminRoomTakedown"; +} from "../DraupnirCommandPrerequisites"; +import { renderRoomPill } from "../interface-manager/MatrixHelpRenderer"; +import { RoomDetailsProvider } from "../../capabilities/RoomTakedownCapability"; +import { SynapseAdminRoomDetailsProvider } from "../../capabilities/SynapseAdminRoomTakedown/SynapseAdminRoomTakedown"; const log = new Logger("DraupnirTakedownCommand"); From a668d5637a128d4a8318cc4663b0b93f706d1c76 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Sat, 29 Mar 2025 19:44:34 +0000 Subject: [PATCH 2/3] Add suspend and unsuspend commands. --- src/commands/DraupnirCommands.ts | 8 +- src/commands/server-admin/SuspendCommand.tsx | 155 +++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/commands/server-admin/SuspendCommand.tsx diff --git a/src/commands/DraupnirCommands.ts b/src/commands/DraupnirCommands.ts index 07cd6eb2..b2fd4e77 100644 --- a/src/commands/DraupnirCommands.ts +++ b/src/commands/DraupnirCommands.ts @@ -56,13 +56,19 @@ import { DraupnirProtectionsShowCommand } from "./ProtectionsShowCommand"; import { DraupnirProtectionsCapabilityCommand } from "./ProtectionsCapabilitiesCommand"; import { JoinWaveCommandTable } from "../protections/JoinWaveShortCircuit"; import { DraupnirTakedownCommand } from "./server-admin/Takedown"; +import { + SynapseAdminSuspendUserCommand, + SynapseAdminUnsuspendUserCommand, +} from "./server-admin/SuspendCommand"; // TODO: These commands should all be moved to subdirectories tbh and this // should be split like an index file for each subdirectory. export const SynapseAdminCommands = new StandardCommandTable("synapse admin") .internCommand(SynapseAdminDeactivateCommand, ["deactivate"]) .internCommand(SynapseAdminHijackRoomCommand, ["hijack", "room"]) - .internCommand(SynapseAdminShutdownRoomCommand, ["shutdown", "room"]); + .internCommand(SynapseAdminShutdownRoomCommand, ["shutdown", "room"]) + .internCommand(SynapseAdminSuspendUserCommand, ["suspend"]) + .internCommand(SynapseAdminUnsuspendUserCommand, ["unsuspend"]); const DraupnirCommands = new StandardCommandTable("draupnir") .internCommand(DraupnirAliasMoveCommand, ["alias", "move"]) diff --git a/src/commands/server-admin/SuspendCommand.tsx b/src/commands/server-admin/SuspendCommand.tsx new file mode 100644 index 00000000..c741b658 --- /dev/null +++ b/src/commands/server-admin/SuspendCommand.tsx @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { + DeadDocumentJSX, + describeCommand, + MatrixUserIDPresentationType, + tuple, +} from "@the-draupnir-project/interface-manager"; +import { Draupnir } from "../../Draupnir"; +import { isError, Ok, Result, ResultError } from "@gnuxie/typescript-result"; +import { StringUserID } from "@the-draupnir-project/matrix-basic-types"; +import { DraupnirInterfaceAdaptor } from "../DraupnirCommandPrerequisites"; +import { renderMentionPill } from "../interface-manager/MatrixHelpRenderer"; + +type SuspensionPreview = { + userID: StringUserID; +}; + +export const SynapseAdminSuspendUserCommand = describeCommand({ + summary: + "Suspend a user on the homeserver, this allows them to login but not interact with anything", + parameters: tuple({ + name: "user", + description: "The user to suspend", + acceptor: MatrixUserIDPresentationType, + }), + keywords: { + keywordDescriptions: { + "no-confirm": { + isFlag: true, + description: + "Runs the command without the preview of the unban and the confirmation prompt.", + }, + }, + }, + async executor( + draupnir: Draupnir, + _info, + keywords, + _rest, + targetUser + ): Promise> { + const isAdmin = await draupnir.synapseAdminClient?.isSynapseAdmin(); + if ( + draupnir.synapseAdminClient === undefined || + isAdmin === undefined || + isError(isAdmin) || + !isAdmin.ok + ) { + return ResultError.Result( + "I am not a Synapse administrator, or the endpoint to deactivate a user is blocked" + ); + } + const preview = { + userID: targetUser.toString(), + }; + const isNoConfirm = keywords.getKeywordValue("no-confirm", false); + if (!isNoConfirm) { + return Ok(preview); + } + const suspensionResult = await draupnir.synapseAdminClient.suspendUser( + targetUser.toString() + ); + if (isError(suspensionResult)) { + return suspensionResult; + } else { + return Ok(preview); + } + }, +}); + +DraupnirInterfaceAdaptor.describeRenderer(SynapseAdminSuspendUserCommand, { + isAlwaysSupposedToUseDefaultRenderer: true, + confirmationPromptJSXRenderer(commandResult) { + if (isError(commandResult)) { + return Ok(undefined); + } + return Ok( + + You are about to suspend the user{" "} + {renderMentionPill(commandResult.ok.userID, commandResult.ok.userID)}{" "} + {commandResult.ok.userID}. Doing so will prevent further + activity from their account. However, they will still be able to login, + manage their account, and redact messages. See + https://spec.matrix.org/v1.14/client-server-api/#account-suspension for + a complete explanation. + + ); + }, + JSXRenderer(commandResult) { + if (isError(commandResult)) { + return Ok(undefined); + } + return Ok( + + The user {commandResult.ok.userID} has been suspended. + + ); + }, +}); + +export const SynapseAdminUnsuspendUserCommand = describeCommand({ + summary: "Unsuspend a user on the homeserver that was previously suspended", + parameters: tuple({ + name: "user", + description: "The user to unsuspend", + acceptor: MatrixUserIDPresentationType, + }), + async executor( + draupnir: Draupnir, + _info, + _keywords, + _rest, + targetUser + ): Promise> { + const isAdmin = await draupnir.synapseAdminClient?.isSynapseAdmin(); + if ( + draupnir.synapseAdminClient === undefined || + isAdmin === undefined || + isError(isAdmin) || + !isAdmin.ok + ) { + return ResultError.Result( + "I am not a Synapse administrator, or the endpoint to deactivate a user is blocked" + ); + } + const preview = { + userID: targetUser.toString(), + }; + const suspensionResult = await draupnir.synapseAdminClient.unsuspendUser( + targetUser.toString() + ); + if (isError(suspensionResult)) { + return suspensionResult; + } else { + return Ok(preview); + } + }, +}); + +DraupnirInterfaceAdaptor.describeRenderer(SynapseAdminUnsuspendUserCommand, { + isAlwaysSupposedToUseDefaultRenderer: true, + JSXRenderer(commandResult) { + if (isError(commandResult)) { + return Ok(undefined); + } + return Ok( + + The user {commandResult.ok.userID} has been unsuspended. + + ); + }, +}); From 44c93c3766203b262143819e2a1430f3648979c3 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Sat, 29 Mar 2025 20:14:22 +0000 Subject: [PATCH 3/3] Update matrix-protection-suite-for-matrix-bot-sdk v3.1.1. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 87afce94..e7fda8ca 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "matrix-appservice-bridge": "^10.3.1", "matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@^0.7.1-element.6", "matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@3.1.0", - "matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@3.1.0", + "matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@3.1.1", "pg": "^8.8.0", "yaml": "^2.3.2" }, diff --git a/yarn.lock b/yarn.lock index df6825e9..5e7a0668 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2593,10 +2593,10 @@ matrix-appservice@^2.0.0: request-promise "^4.2.6" sanitize-html "^2.11.0" -"matrix-protection-suite-for-matrix-bot-sdk@npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite-for-matrix-bot-sdk/-/matrix-protection-suite-for-matrix-bot-sdk-3.1.0.tgz#36ed640f6425f568e85c880b6e3da87019e06db5" - integrity sha512-dgagHqAupTj7L/MAdXxwXFxNNceGALuMUr05WfnfBUfkbeI0xiC2rPtwKzh3xO2crjSYx52UE+59V73koHzDpQ== +"matrix-protection-suite-for-matrix-bot-sdk@npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite-for-matrix-bot-sdk/-/matrix-protection-suite-for-matrix-bot-sdk-3.1.1.tgz#1bdfadd16a3e69677a6dd694d6288fd967764a2d" + integrity sha512-jzOFhKt24FiSmK5Ej0khaPfBder607yFX1vg5dvDSv6q/GrHrkFB5poq4TGtu+x+gRIDNAe2zfEOriK1p/qyoQ== dependencies: "@gnuxie/typescript-result" "^1.0.0" await-lock "^2.2.2"