Skip to content

Commit

Permalink
Merge branch 'main' into fix-redaction-queue-on-bf-exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
H-Shay authored Jul 24, 2024
2 parents 9b2d21b + e776d82 commit 6473136
Show file tree
Hide file tree
Showing 23 changed files with 406 additions and 154 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/mjolnir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: Specifically use node 16 like in the readme.
- name: Specifically use node 18 like in the readme.
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '18'
- run: yarn install
- run: yarn build
- run: yarn lint
Expand All @@ -29,10 +29,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Specifically use node 16 like in the readme.
- name: Specifically use node 18 like in the readme.
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '18'
- run: yarn install
- run: yarn test
integration:
Expand All @@ -43,7 +43,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '18'
- name: Fetch and build mx-tester (cached across runs)
uses: baptiste0928/cargo-install@v1
with:
Expand All @@ -65,7 +65,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '18'
- name: Fetch and build mx-tester (cached across runs)
uses: baptiste0928/cargo-install@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# We can't use alpine anymore because crypto has rust deps.
FROM node:16-slim
FROM node:18-slim
COPY . /tmp/src
RUN cd /tmp/src \
&& yarn install \
Expand Down
2 changes: 1 addition & 1 deletion docs/setup_selfbuild.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
These instructions are to build and run mjolnir without using [Docker](./setup_docker.md).
To build mjolnir, you have to have installed [Node >=16](https://nodejs.org/en/download/), [npm](https://docs.npmjs.com/cli/v7/configuring-npm/install) and [yarn >1.x](https://classic.yarnpkg.com/en/docs/install).
To build mjolnir, you have to have installed [Node >=18](https://nodejs.org/en/download/), [npm](https://docs.npmjs.com/cli/v7/configuring-npm/install) and [yarn >1.x](https://classic.yarnpkg.com/en/docs/install).

Copy the latest release tag from https://github.com/matrix-org/mjolnir/releases/latest/ to use when cloning. For example `v1.6.1` - please check the link for the current version as this mentioned example might be outdated.

Expand Down
5 changes: 5 additions & 0 deletions mx-tester.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ modules:


homeserver:
docker: element-hq/synapse:latest
# Basic configuration.
server_name: localhost:9999
public_baseurl: http://localhost:9999
Expand Down Expand Up @@ -76,6 +77,10 @@ homeserver:
per_second: 10000
burst_count: 10000

experimental_features:
msc3823_account_suspension: true


# Creating a few users simplifies testing.
users:
- localname: admin
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mjolnir",
"version": "1.6.3",
"version": "1.6.5",
"description": "A moderation tool for Matrix",
"main": "lib/index.js",
"repository": "[email protected]:matrix-org/mjolnir.git",
Expand Down Expand Up @@ -30,11 +30,11 @@
"@types/jsdom": "^16.2.11",
"@types/mocha": "^9.0.0",
"@types/nedb": "^1.8.12",
"@types/node": "^16.7.10",
"@types/node": "^18.0.0",
"@types/pg": "^8.6.5",
"@types/request": "^2.48.8",
"@types/shell-quote": "1.7.1",
"crypto-js": "^4.1.1",
"crypto-js": "^4.2.0",
"eslint": "^7.32",
"expect": "^27.0.6",
"mocha": "^9.0.1",
Expand All @@ -49,21 +49,21 @@
"await-lock": "^2.2.2",
"body-parser": "^1.20.1",
"config": "^3.3.8",
"express": "^4.17",
"express": "^4.19",
"html-to-text": "^8.0.0",
"humanize-duration": "^3.27.1",
"humanize-duration-ts": "^2.1.1",
"js-yaml": "^4.1.0",
"jsdom": "^16.6.0",
"matrix-appservice-bridge": "8.0.0",
"matrix-appservice-bridge": "8.1.2",
"parse-duration": "^1.0.2",
"pg": "^8.8.0",
"prom-client": "^14.1.0",
"shell-quote": "^1.7.3",
"ulidx": "^0.3.0",
"yaml": "^2.1.1"
"yaml": "^2.2.2"
},
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
}
}
11 changes: 11 additions & 0 deletions releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Releasing mjolnir

1. Make sure all the things you want have landed on the main branch.
2. Run yarn version --patch (or --minor or --major, see documentation) to create the tag and update the versioning.
3. Push main and the tag to the repo (e.g. git push --atomic origin main v1.3.0).
4. Docker Hub will automatically start building the images required.
5. Create a new github release, freehanding the changelog from a prior release (copy/paste, edit as needed). Github's
auto-changelog is good to press at the beginning to make sure you don't miss anything. Remember to check recently
closed issues and thank the reporters.
6. Update the room topic of #mjolnir:matrix.org to mention the latest version.

13 changes: 13 additions & 0 deletions src/Mjolnir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,19 @@ export class Mjolnir {
return await this.client.doRequest("POST", endpoint);
}

public async suspendSynapseUser(userId: string): Promise<any> {
const endpoint = `/_synapse/admin/v1/suspend/${userId}`;
const body = {"suspend": true}
return await this.client.doRequest("PUT", endpoint, null, body);
}

public async unsuspendSynapseUser(userId: string): Promise<any> {
const endpoint = `/_synapse/admin/v1/suspend/${userId}`;
const body = {"suspend": false}
return await this.client.doRequest("PUT", endpoint, null, body);
}


public async shutdownSynapseRoom(roomId: string, message?: string): Promise<any> {
const endpoint = `/_synapse/admin/v1/rooms/${roomId}`;
return await this.client.doRequest("DELETE", endpoint, null, {
Expand Down
10 changes: 9 additions & 1 deletion src/commands/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import { execMakeRoomAdminCommand } from "./MakeRoomAdminCommand";
import { parse as tokenize } from "shell-quote";
import { execSinceCommand } from "./SinceCommand";
import { execSetupProtectedRoom } from "./SetupDecentralizedReportingCommand";
import {execSuspendCommand} from "./SuspendCommand";
import {execUnsuspendCommand} from "./UnsuspendCommand";


export const COMMAND_PREFIX = "!mjolnir";
Expand Down Expand Up @@ -128,6 +130,10 @@ export async function handleCommand(roomId: string, event: { content: { body: st
return await execKickCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'make' && parts[2] === 'admin' && parts.length > 3) {
return await execMakeRoomAdminCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'suspend' && parts.length > 2) {
return await execSuspendCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'unsuspend' && parts.length > 2) {
return await execUnsuspendCommand(roomId, event, mjolnir, parts)
} else {
// Help menu
const menu = "" +
Expand Down Expand Up @@ -170,7 +176,9 @@ export async function handleCommand(roomId: string, event: { content: { body: st
"!mjolnir shutdown room <room alias/ID> [message] - Uses the bot's account to shut down a room, preventing access to the room on this server\n" +
"!mjolnir powerlevel <user ID> <power level> [room alias/ID] - Sets the power level of the user in the specified room (or all protected rooms)\n" +
"!mjolnir make admin <room alias> [user alias/ID] - Make the specified user or the bot itself admin of the room\n" +
"!mjolnir help - This menu\n";
"!mjolnir suspend <user ID> - Suspend the specified user" +
"!mjolnir unsuspend <user ID> - Unsuspend the specified user" +
"!mjolnir help - This menu\n"
const html = `<b>Mjolnir help:</b><br><pre><code>${htmlEscape(menu)}</code></pre>`;
const text = `Mjolnir help:\n${menu}`;
const reply = RichReply.createFor(roomId, event, text, html);
Expand Down
4 changes: 2 additions & 2 deletions src/commands/DeactivateCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { RichReply } from "matrix-bot-sdk";

// !mjolnir deactivate <user ID>
export async function execDeactivateCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const victim = parts[2];
const target = parts[2];

const isAdmin = await mjolnir.isSynapseAdmin();
if (!isAdmin) {
Expand All @@ -30,6 +30,6 @@ export async function execDeactivateCommand(roomId: string, event: any, mjolnir:
return;
}

await mjolnir.deactivateSynapseUser(victim);
await mjolnir.deactivateSynapseUser(target);
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}
12 changes: 6 additions & 6 deletions src/commands/KickCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,21 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln
const members = await mjolnir.client.getRoomMembers(protectedRoomId, undefined, ["join"], ["ban", "leave"]);

for (const member of members) {
const victim = member.membershipFor;
const target = member.membershipFor;

if (kickRule.test(victim)) {
await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "KickCommand", `Removing ${victim} in ${protectedRoomId}`, protectedRoomId);
if (kickRule.test(target)) {
await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "KickCommand", `Removing ${target} in ${protectedRoomId}`, protectedRoomId);

if (!mjolnir.config.noop) {
try {
await mjolnir.taskQueue.push(async () => {
return mjolnir.client.kickUser(victim, protectedRoomId, reason);
return mjolnir.client.kickUser(target, protectedRoomId, reason);
});
} catch (e) {
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "KickCommand", `An error happened while trying to kick ${victim}: ${e}`);
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "KickCommand", `An error happened while trying to kick ${target}: ${e}`);
}
} else {
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "KickCommand", `Tried to kick ${victim} in ${protectedRoomId} but the bot is running in no-op mode.`, protectedRoomId);
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "KickCommand", `Tried to kick ${target} in ${protectedRoomId} but the bot is running in no-op mode.`, protectedRoomId);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/commands/SetPowerLevelCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ import { extractRequestError, LogLevel, LogService } from "matrix-bot-sdk";

// !mjolnir powerlevel <user ID> <level> [room]
export async function execSetPowerLevelCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const victim = parts[2];
const target = parts[2];
const level = Math.round(Number(parts[3]));
const inRoom = parts[4];

let targetRooms = inRoom ? [await mjolnir.client.resolveRoom(inRoom)] : mjolnir.protectedRoomsTracker.getProtectedRooms();

for (const targetRoomId of targetRooms) {
try {
await mjolnir.client.setUserPowerLevel(victim, targetRoomId, level);
await mjolnir.client.setUserPowerLevel(target, targetRoomId, level);
} catch (e) {
const message = e.message || (e.body ? e.body.error : '<no message>');
await mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "SetPowerLevelCommand", `Failed to set power level of ${victim} to ${level} in ${targetRoomId}: ${message}`, targetRoomId);
await mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "SetPowerLevelCommand", `Failed to set power level of ${target} to ${level} in ${targetRoomId}: ${message}`, targetRoomId);
LogService.error("SetPowerLevelCommand", extractRequestError(e));
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/ShutdownRoomCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { RichReply } from "matrix-bot-sdk";

// !mjolnir shutdown room <room> [<message>]
export async function execShutdownRoomCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const victim = parts[3];
const target = parts[3];
const reason = parts.slice(4).join(" ") || undefined;

const isAdmin = await mjolnir.isSynapseAdmin();
Expand All @@ -31,6 +31,6 @@ export async function execShutdownRoomCommand(roomId: string, event: any, mjolni
return;
}

await mjolnir.shutdownSynapseRoom(await mjolnir.client.resolveRoom(victim), reason);
await mjolnir.shutdownSynapseRoom(await mjolnir.client.resolveRoom(target), reason);
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}
38 changes: 38 additions & 0 deletions src/commands/SuspendCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {Mjolnir} from "../Mjolnir";
import {RichReply} from "matrix-bot-sdk";

export async function execSuspendCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const target = parts[2];

const isAdmin = await mjolnir.isSynapseAdmin();
if (!isAdmin) {
const message = "I am not a Synapse administrator, or the endpoint is blocked";
const reply = RichReply.createFor(roomId, event, message, message);
reply['msgtype'] = "m.notice";
await mjolnir.client.sendMessage(roomId, reply);
return;
}

await mjolnir.suspendSynapseUser(target);
const msg = `User ${target} has been suspended.`
const confirmation = RichReply.createFor(roomId, event, msg, msg);
confirmation['msgtype'] = "m.notice";
await mjolnir.client.sendMessage(roomId, confirmation)
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}
10 changes: 5 additions & 5 deletions src/commands/UnbanBanCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol
const members = await mjolnir.client.getRoomMembers(protectedRoomId, undefined, ['ban'], undefined);
await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Found ${members.length} banned user(s)`);
for (const member of members) {
const victim = member.membershipFor;
const target = member.membershipFor;
if (member.membership !== 'ban') continue;
if (rule.test(victim)) {
await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`, protectedRoomId);
if (rule.test(target)) {
await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${target} in ${protectedRoomId}`, protectedRoomId);

if (!mjolnir.config.noop) {
await mjolnir.client.unbanUser(victim, protectedRoomId);
await mjolnir.client.unbanUser(target, protectedRoomId);
} else {
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId);
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${target} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId);
}

unbannedSomeone = true;
Expand Down
38 changes: 38 additions & 0 deletions src/commands/UnsuspendCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {Mjolnir} from "../Mjolnir";
import {RichReply} from "matrix-bot-sdk";

export async function execUnsuspendCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const target = parts[2];

const isAdmin = await mjolnir.isSynapseAdmin();
if (!isAdmin) {
const message = "I am not a Synapse administrator, or the endpoint is blocked";
const reply = RichReply.createFor(roomId, event, message, message);
reply['msgtype'] = "m.notice";
mjolnir.client.sendMessage(roomId, reply);
return;
}

await mjolnir.unsuspendSynapseUser(target);
const msg = `User ${target}'s suspension has been reversed.`
const confirmation = RichReply.createFor(roomId, event, msg, msg);
confirmation['msgtype'] = "m.notice";
mjolnir.client.sendMessage(roomId, confirmation)
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}
14 changes: 12 additions & 2 deletions src/models/PolicyList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,9 +658,19 @@ export class PolicyListManager {
const permalink = Permalinks.parseUrl(roomRef);
if (!permalink.roomIdOrAlias) return null;

const roomId = await this.mjolnir.client.resolveRoom(permalink.roomIdOrAlias);
let roomId: string;
let viaServers;
if (permalink.roomIdOrAlias.startsWith("!")) {
roomId = permalink.roomIdOrAlias
viaServers = permalink.viaServers
} else {
const roomInfo = await this.mjolnir.client.lookupRoomAlias(permalink.roomIdOrAlias)
roomId = roomInfo.roomId
viaServers = roomInfo.residentServers
}

if (!joinedRooms.includes(roomId)) {
await this.mjolnir.client.joinRoom(roomId, permalink.viaServers);
await this.mjolnir.client.joinRoom(roomId, viaServers);
}

if (this.policyLists.find(b => b.roomId === roomId)) {
Expand Down
Loading

0 comments on commit 6473136

Please sign in to comment.