Skip to content

Commit

Permalink
Add native encryption support to Mjolnir (#528)
Browse files Browse the repository at this point in the history
* support native encryption

* update tests to remove use of panataliamon

* update docs

* lint

* add more time in tests
  • Loading branch information
H-Shay authored Oct 1, 2024
1 parent 5ecdb8d commit 37a2168
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 93 deletions.
16 changes: 15 additions & 1 deletion config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@ homeserverUrl: "https://matrix.org"
# only set this to the public-internet homeserver client API URL, do NOT set this to the pantalaimon URL.
rawHomeserverUrl: "https://matrix.org"

# Matrix Access Token to use, Mjolnir will only use this if pantalaimon.use is false.
# Matrix Access Token to use
accessToken: "YOUR_TOKEN_HERE"

# Options related to native encryption
encryption:
# whether to use native encryption in mjolnir, rather than using pantalaimon as a proxy
# note that if encryption is enabled here, pantaliamon must be disabled, and vice versa
use: true

# the username to log in with
username: "mjolnir"

# the password to log in with
password: "password"

# Options related to Pantalaimon (https://github.com/matrix-org/pantalaimon)
# Note that this option is now deprecated as native encryption is now supported in mjolnir,
# and will be removed at a later date.
pantalaimon:
# Whether or not Mjolnir will use pantalaimon to access the matrix homeserver,
# set to `true` if you're using pantalaimon.
Expand Down
7 changes: 6 additions & 1 deletion config/harness.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ rawHomeserverUrl: "http://localhost:8081"
pantalaimon:
# If true, accessToken above is ignored and the username/password below will be
# used instead. The access token of the bot will be stored in the dataPath.
use: true
use: false

# The username to login with.
username: mjolnir
Expand All @@ -25,6 +25,11 @@ pantalaimon:
# stored the access token.
password: mjolnir

encryption:
use: true
username: test
password: testPassword

# The directory the bot should store various bits of information in
dataPath: "./test/harness/mjolnir-data/"

Expand Down
4 changes: 2 additions & 2 deletions docs/moderators.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ the ACL format while membership bans are applied on sight. Within Matrix it is n
ban a set of users by glob/regex, so Mjolnir monitors the rooms it protects for membership changes and
bans people who match rules when they join/are invited.

Mjolnir can run through Pantalaimon if your coordination room is encrypted (this is recommended). Your
coordination/management room is where you and all of your moderators can speak to Mjolnir and update the
It is recommended that your management room be encrypted. Ensure that you have enabled encryption in the configuration
Your coordination/management room is where you and all of your moderators can speak to Mjolnir and update the
rules it uses. Be sure to keep this room private to avoid unauthorized access to the bot.

Note that Mjolnir performs all its moderation actions as itself rather than encouraging you to use your
Expand Down
7 changes: 4 additions & 3 deletions docs/setup.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Setting up Mjolnir

It is recommended to use [Pantalaimon](https://github.com/matrix-org/pantalaimon) so your management
room can be encrypted. This also applies if you are looking to moderate an encrypted room.
It is recommended that your management room be encrypted. To ensure that your bot can communicate with encrypted rooms,
ensure that you have encryption enabled in the config.

If you aren't using encrypted rooms anywhere, get an access token by opening Element in a
seperate browser profile or incognito tab, and log in as the bot. Then, go to "All Settings", "Help & About", and
Expand Down Expand Up @@ -39,7 +39,8 @@ See the below links for corresponding installation documentation;

After installation, create a room, and ensure the mjolnir has joined. This will be your "management room".

If you're using pantalaimon, this room can be encrypted. If you're not using pantalaimon, this room **can not** be encrypted.
It is highly recommended that this room be encrypted. Ensure that you have properly configured your mjolnir to use
encryption in the configuration.

Acquire the room ID of this room, in Element Web you can find this via `(Room Name) -> Settings -> Advanced -> "Internal Room ID"`.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"typescript-formatter": "^7.2"
},
"dependencies": {
"axios": "^1.7.6",
"@sentry/node": "^7.17.2",
"@sentry/tracing": "^7.17.2",
"@tensorflow/tfjs-node": "^4.21.0",
Expand Down
10 changes: 10 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export interface IConfig {
homeserverUrl: string;
rawHomeserverUrl: string;
accessToken: string;
encryption: {
use: boolean;
username: string;
password: string;
}
pantalaimon: {
use: boolean;
username: string;
Expand Down Expand Up @@ -189,6 +194,11 @@ const defaultConfig: IConfig = {
homeserverUrl: "http://localhost:8008",
rawHomeserverUrl: "http://localhost:8008",
accessToken: "NONE_PROVIDED",
encryption: {
use: true,
username: "name",
password: "pass",
},
pantalaimon: {
use: false,
username: "",
Expand Down
28 changes: 25 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import { Healthz } from "./health/healthz";

import {
LogLevel,
LogService,
LogService, MatrixAuth,
MatrixClient,
PantalaimonClient,
RichConsoleLogger,
RichConsoleLogger, RustSdkCryptoStorageProvider,
SimpleFsStorageProvider
} from "@vector-im/matrix-bot-sdk";

Expand Down Expand Up @@ -59,13 +59,35 @@ import { initializeSentry, initializeGlobalPerformanceMetrics, patchMatrixClient
try {
const storagePath = path.isAbsolute(config.dataPath) ? config.dataPath : path.join(__dirname, '../', config.dataPath);
const storage = new SimpleFsStorageProvider(path.join(storagePath, "bot.json"));
const cryptoStorage = new RustSdkCryptoStorageProvider(storagePath, 0)

if (config.encryption.use && config.pantalaimon.use) {
throw Error('Cannot enable both pantalaimon and encryption at the same time. Remove one from the config.');
}

let client: MatrixClient;
if (config.pantalaimon.use) {
const pantalaimon = new PantalaimonClient(config.homeserverUrl, storage);
client = await pantalaimon.createClientWithCredentials(config.pantalaimon.username, config.pantalaimon.password);
} else {
client = new MatrixClient(config.homeserverUrl, config.accessToken, storage);
const accessToken = await Promise.resolve(storage.readValue("access_token"));
if (accessToken) {
client = new MatrixClient(config.homeserverUrl, accessToken, storage, cryptoStorage);
} else {
const auth = new MatrixAuth(config.homeserverUrl)
const tempClient = await auth.passwordLogin(config.encryption.username, config.encryption.password)
client = new MatrixClient(config.homeserverUrl, tempClient.accessToken, storage, cryptoStorage);
}

try {
LogService.info("index", "Preparing encrypted client...")
await client.crypto.prepare();
} catch (e) {
LogService.error("Index", `Error preparing encrypted client ${e}`)
throw e
}


}
patchMatrixClient();
config.RUNTIME.client = client;
Expand Down
17 changes: 9 additions & 8 deletions test/integration/abuseReportTest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { strict as assert } from "assert";

import { matrixClient } from "./mjolnirSetupUtils";
import { newTestUser } from "./clientHelper";
import { ReportManager, ABUSE_ACTION_CONFIRMATION_KEY, ABUSE_REPORT_KEY } from "../../src/report/ReportManager";
import { ABUSE_REPORT_KEY } from "../../src/report/ReportManager";

/**
* Test the ability to turn abuse reports into room messages.
Expand Down Expand Up @@ -30,8 +29,9 @@ describe("Test: Reporting abuse", async () => {
this.timeout(90000);

// Listen for any notices that show up.
await new Promise(resolve => setTimeout(resolve, 1000));
let notices: any[] = [];
this.mjolnir.client.on("room.event", (roomId, event) => {
this.mjolnir.client.on("room.event", (roomId: string, event: any) => {
if (roomId = this.mjolnir.managementRoomId) {
notices.push(event);
}
Expand Down Expand Up @@ -225,8 +225,9 @@ describe("Test: Reporting abuse", async () => {
this.timeout(60000);

// Listen for any notices that show up.
await new Promise(resolve => setTimeout(resolve, 1000));
let notices: any[] = [];
this.mjolnir.client.on("room.event", (roomId, event) => {
this.mjolnir.client.on("room.event", (roomId: string, event: any) => {
if (roomId = this.mjolnir.managementRoomId) {
notices.push(event);
}
Expand All @@ -244,8 +245,8 @@ describe("Test: Reporting abuse", async () => {
let badUserId = await badUser.getUserId();

let roomId = await moderatorUser.createRoom({ invite: [await badUser.getUserId()] });
await moderatorUser.inviteUser(await goodUser.getUserId(), roomId);
await moderatorUser.inviteUser(await badUser.getUserId(), roomId);
await moderatorUser.inviteUser(goodUserId, roomId);
await moderatorUser.inviteUser(badUserId, roomId);
await badUser.joinRoom(roomId);
await goodUser.joinRoom(roomId);

Expand All @@ -257,9 +258,9 @@ describe("Test: Reporting abuse", async () => {
// Exchange a few messages.
let goodText = `GOOD: ${Math.random()}`; // Will NOT be reported.
let badText = `BAD: ${Math.random()}`; // Will be reported as abuse.
let goodEventId = await goodUser.sendText(roomId, goodText);
await goodUser.sendText(roomId, goodText);
let badEventId = await badUser.sendText(roomId, badText);
let goodEventId2 = await goodUser.sendText(roomId, goodText);
await goodUser.sendText(roomId, goodText);

console.log("Test: Reporting abuse - send reports");

Expand Down
4 changes: 2 additions & 2 deletions test/integration/banListTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ describe('Test: ACL updates will batch when rules are added in succession.', fun
// If there's less than two then it means the ACL was updated by this test calling `this.mjolnir!.syncLists()`
// and not the listener that detects changes to ban lists (that we want to test!).
// It used to be 10, but it was too low, 30 seems better for CI.
assert.equal(aclEventCount < 30 && aclEventCount > 2, true, 'We should have sent less than 30 ACL events to each room because they should be batched')
assert.equal(aclEventCount < 50 && aclEventCount > 2, true, 'We should have sent less than 50 ACL events to each room because they should be batched')
}));
})
})
Expand Down Expand Up @@ -414,7 +414,7 @@ describe('Test: should apply bans to the most recently active rooms first', func
// create some activity in the same order.
for (const roomId of protectedRooms.slice().reverse()) {
await moderator.sendMessage(roomId, { body: `activity`, msgtype: 'm.text' });
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise(resolve => setTimeout(resolve, 500));
}

// check the rooms are in the expected order
Expand Down
Loading

0 comments on commit 37a2168

Please sign in to comment.