Skip to content

Commit 21aabc8

Browse files
authored
Stop the config being global (in almost all contexts). (#334)
* Stop the config being global (in almost all contexts). * make sure unit test has a config * Make failing word list more visible * Only use Healthz from index.ts Not really sure how useful it is anyways?
1 parent 121d4cf commit 21aabc8

26 files changed

+179
-188
lines changed

src/Mjolnir.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,12 @@ import { applyServerAcls } from "./actions/ApplyAcl";
3232
import { RoomUpdateError } from "./models/RoomUpdateError";
3333
import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
3434
import { applyUserBans } from "./actions/ApplyBan";
35-
import config from "./config";
3635
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
3736
import { Protection } from "./protections/IProtection";
3837
import { PROTECTIONS } from "./protections/protections";
3938
import { ConsequenceType, Consequence } from "./protections/consequence";
4039
import { ProtectionSettingValidationError } from "./protections/ProtectionSettings";
4140
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue";
42-
import { Healthz } from "./health/healthz";
4341
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
4442
import { htmlEscape } from "./utils";
4543
import { ReportManager } from "./report/ReportManager";
@@ -50,6 +48,7 @@ import RuleServer from "./models/RuleServer";
5048
import { RoomMemberManager } from "./RoomMembers";
5149
import { ProtectedRoomActivityTracker } from "./queues/ProtectedRoomActivityTracker";
5250
import { ThrottlingQueue } from "./queues/ThrottlingQueue";
51+
import { IConfig } from "./config";
5352
import PolicyList, { ListRuleChange } from "./models/PolicyList";
5453

5554
const levelToFn = {
@@ -162,7 +161,7 @@ export class Mjolnir {
162161
* @param {MatrixClient} client The client for Mjolnir to use.
163162
* @returns A new Mjolnir instance that can be started without further setup.
164163
*/
165-
static async setupMjolnirFromConfig(client: MatrixClient): Promise<Mjolnir> {
164+
static async setupMjolnirFromConfig(client: MatrixClient, config: IConfig): Promise<Mjolnir> {
166165
const policyLists: PolicyList[] = [];
167166
const protectedRooms: { [roomId: string]: string } = {};
168167
const joinedRooms = await client.getJoinedRooms();
@@ -188,7 +187,7 @@ export class Mjolnir {
188187
}
189188

190189
const ruleServer = config.web.ruleServer ? new RuleServer() : null;
191-
const mjolnir = new Mjolnir(client, managementRoomId, protectedRooms, policyLists, ruleServer);
190+
const mjolnir = new Mjolnir(client, managementRoomId, config, protectedRooms, policyLists, ruleServer);
192191
await mjolnir.logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
193192
Mjolnir.addJoinOnInviteListener(mjolnir, client, config);
194193
return mjolnir;
@@ -197,6 +196,7 @@ export class Mjolnir {
197196
constructor(
198197
public readonly client: MatrixClient,
199198
public readonly managementRoomId: string,
199+
public readonly config: IConfig,
200200
/*
201201
* All the rooms that Mjolnir is protecting and their permalinks.
202202
* If `config.protectAllJoinedRooms` is specified, then `protectedRooms` will be all joined rooms except watched banlists that we can't protect (because they aren't curated by us).
@@ -208,7 +208,7 @@ export class Mjolnir {
208208
) {
209209
this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms);
210210

211-
for (const reason of config.automaticallyRedactForReasons) {
211+
for (const reason of this.config.automaticallyRedactForReasons) {
212212
this.automaticRedactionReasons.push(new MatrixGlob(reason.toLowerCase()));
213213
}
214214

@@ -276,7 +276,7 @@ export class Mjolnir {
276276
console.log("Creating Web APIs");
277277
const reportManager = new ReportManager(this);
278278
reportManager.on("report.new", this.handleReport.bind(this));
279-
this.webapis = new WebAPIs(reportManager, this.ruleServer);
279+
this.webapis = new WebAPIs(reportManager, this.config, this.ruleServer);
280280
if (config.pollReports) {
281281
this.reportPoller = new ReportPoller(this, reportManager);
282282
}
@@ -356,20 +356,19 @@ export class Mjolnir {
356356
await this.buildWatchedPolicyLists();
357357
this.applyUnprotectedRooms();
358358

359-
if (config.verifyPermissionsOnStartup) {
359+
if (this.config.verifyPermissionsOnStartup) {
360360
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
361-
await this.verifyPermissions(config.verboseLogging);
361+
await this.verifyPermissions(this.config.verboseLogging);
362362
}
363363

364364
this.currentState = STATE_SYNCING;
365-
if (config.syncOnStartup) {
365+
if (this.config.syncOnStartup) {
366366
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists...");
367-
await this.syncLists(config.verboseLogging);
367+
await this.syncLists(this.config.verboseLogging);
368368
await this.registerProtections();
369369
}
370370

371371
this.currentState = STATE_RUNNING;
372-
Healthz.isHealthy = true;
373372
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Startup complete. Now monitoring rooms.");
374373
} catch (err) {
375374
try {
@@ -399,14 +398,13 @@ export class Mjolnir {
399398
if (!additionalRoomIds) additionalRoomIds = [];
400399
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
401400

402-
if (config.RUNTIME.client && (config.verboseLogging || LogLevel.INFO.includes(level))) {
401+
if (this.config.verboseLogging || LogLevel.INFO.includes(level)) {
403402
let clientMessage = message;
404403
if (level === LogLevel.WARN) clientMessage = `⚠ | ${message}`;
405404
if (level === LogLevel.ERROR) clientMessage = `‼ | ${message}`;
406405

407-
const client = config.RUNTIME.client;
408-
const managementRoomId = await client.resolveRoom(config.managementRoom);
409-
const roomIds = [managementRoomId, ...additionalRoomIds];
406+
const client = this.client;
407+
const roomIds = [this.managementRoomId, ...additionalRoomIds];
410408

411409
let evContent: TextualMessageEventContent = {
412410
body: message,
@@ -418,7 +416,7 @@ export class Mjolnir {
418416
evContent = await replaceRoomIdsWithPills(this, clientMessage, new Set(roomIds), "m.notice");
419417
}
420418

421-
await client.sendMessage(managementRoomId, evContent);
419+
await client.sendMessage(this.managementRoomId, evContent);
422420
}
423421

424422
levelToFn[level.toString()](module, message);
@@ -443,7 +441,7 @@ export class Mjolnir {
443441
const rooms = (additionalProtectedRooms?.rooms ?? []);
444442
rooms.push(roomId);
445443
await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, { rooms: rooms });
446-
await this.syncLists(config.verboseLogging);
444+
await this.syncLists(this.config.verboseLogging);
447445
}
448446

449447
public async removeProtectedRoom(roomId: string) {
@@ -465,7 +463,7 @@ export class Mjolnir {
465463
}
466464

467465
private async resyncJoinedRooms(withSync = true) {
468-
if (!config.protectAllJoinedRooms) return;
466+
if (!this.config.protectAllJoinedRooms) return;
469467

470468
const joinedRoomIds = (await this.client.getJoinedRooms()).filter(r => r !== this.managementRoomId);
471469
const oldRoomIdsSet = new Set(this.protectedJoinedRoomIds);
@@ -491,7 +489,7 @@ export class Mjolnir {
491489
this.applyUnprotectedRooms();
492490

493491
if (withSync) {
494-
await this.syncLists(config.verboseLogging);
492+
await this.syncLists(this.config.verboseLogging);
495493
}
496494
}
497495

@@ -718,7 +716,7 @@ export class Mjolnir {
718716
}
719717

720718
public async warnAboutUnprotectedPolicyListRoom(roomId: string) {
721-
if (!config.protectAllJoinedRooms) return; // doesn't matter
719+
if (!this.config.protectAllJoinedRooms) return; // doesn't matter
722720
if (this.explicitlyProtectedRoomIds.includes(roomId)) return; // explicitly protected
723721

724722
const createEvent = new CreateEvent(await this.client.getRoomStateEvent(roomId, "m.room.create", ""));

src/actions/ApplyAcl.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import PolicyList from "../models/PolicyList";
1818
import { ServerAcl } from "../models/ServerAcl";
1919
import { RoomUpdateError } from "../models/RoomUpdateError";
2020
import { Mjolnir } from "../Mjolnir";
21-
import config from "../config";
2221
import { LogLevel, UserID } from "matrix-bot-sdk";
2322
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
2423

@@ -57,7 +56,7 @@ async function _applyServerAcls(lists: PolicyList[], roomIds: string[], mjolnir:
5756
mjolnir.logMessage(LogLevel.WARN, "ApplyAcl", `Mjölnir has detected and removed an ACL that would exclude itself. Please check the ACL lists.`);
5857
}
5958

60-
if (config.verboseLogging) {
59+
if (mjolnir.config.verboseLogging) {
6160
// We specifically use sendNotice to avoid having to escape HTML
6261
await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Constructed server ACL:\n${JSON.stringify(finalAcl, null, 2)}`);
6362
}
@@ -80,7 +79,7 @@ async function _applyServerAcls(lists: PolicyList[], roomIds: string[], mjolnir:
8079
// We specifically use sendNotice to avoid having to escape HTML
8180
await mjolnir.logMessage(LogLevel.DEBUG, "ApplyAcl", `Applying ACL in ${roomId}`, roomId);
8281

83-
if (!config.noop) {
82+
if (!mjolnir.config.noop) {
8483
await mjolnir.client.sendStateEvent(roomId, "m.room.server_acl", "", finalAcl);
8584
} else {
8685
await mjolnir.logMessage(LogLevel.WARN, "ApplyAcl", `Tried to apply ACL in ${roomId} but Mjolnir is running in no-op mode`, roomId);

src/actions/ApplyBan.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
import PolicyList from "../models/PolicyList";
1818
import { RoomUpdateError } from "../models/RoomUpdateError";
1919
import { Mjolnir } from "../Mjolnir";
20-
import config from "../config";
2120
import { LogLevel } from "matrix-bot-sdk";
2221
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
2322

@@ -38,7 +37,7 @@ export async function applyUserBans(lists: PolicyList[], roomIds: string[], mjol
3837

3938
let members: { userId: string, membership: string }[];
4039

41-
if (config.fasterMembershipChecks) {
40+
if (mjolnir.config.fasterMembershipChecks) {
4241
const memberIds = await mjolnir.client.getJoinedRoomMembers(roomId);
4342
members = memberIds.map(u => {
4443
return { userId: u, membership: "join" };
@@ -64,7 +63,7 @@ export async function applyUserBans(lists: PolicyList[], roomIds: string[], mjol
6463
// We specifically use sendNotice to avoid having to escape HTML
6564
await mjolnir.logMessage(LogLevel.INFO, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`, roomId);
6665

67-
if (!config.noop) {
66+
if (!mjolnir.config.noop) {
6867
await mjolnir.client.banUser(member.userId, roomId, userRule.reason);
6968
if (mjolnir.automaticRedactGlobs.find(g => g.test(userRule.reason.toLowerCase()))) {
7069
mjolnir.queueRedactUserMessagesIn(member.userId, roomId);

src/commands/KickCommand.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ limitations under the License.
1616

1717
import { Mjolnir } from "../Mjolnir";
1818
import { LogLevel, MatrixGlob, RichReply } from "matrix-bot-sdk";
19-
import config from "../config";
2019

2120
// !mjolnir kick <user|filter> [room] [reason]
2221
export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
@@ -30,7 +29,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln
3029
parts.pop();
3130
}
3231

33-
if (config.commands.confirmWildcardBan && /[*?]/.test(glob) && !force) {
32+
if (mjolnir.config.commands.confirmWildcardBan && /[*?]/.test(glob) && !force) {
3433
let replyMessage = "Wildcard bans require an addition `--force` argument to confirm";
3534
const reply = RichReply.createFor(roomId, event, replyMessage, replyMessage);
3635
reply["msgtype"] = "m.notice";
@@ -60,7 +59,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln
6059
if (kickRule.test(victim)) {
6160
await mjolnir.logMessage(LogLevel.DEBUG, "KickCommand", `Removing ${victim} in ${protectedRoomId}`, protectedRoomId);
6261

63-
if (!config.noop) {
62+
if (!mjolnir.config.noop) {
6463
try {
6564
await mjolnir.taskQueue.push(async () => {
6665
return mjolnir.client.kickUser(victim, protectedRoomId, reason);

src/commands/MakeRoomAdminCommand.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import config from "../config";
1817
import { Mjolnir } from "../Mjolnir";
1918
import { RichReply } from "matrix-bot-sdk";
2019

2120
// !mjolnir make admin <room> [<user ID>]
2221
export async function execMakeRoomAdminCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
2322
const isAdmin = await mjolnir.isSynapseAdmin();
24-
if (!config.admin?.enableMakeRoomAdminCommand || !isAdmin) {
23+
if (!mjolnir.config.admin?.enableMakeRoomAdminCommand || !isAdmin) {
2524
const message = "Either the command is disabled or I am not running as homeserver administrator.";
2625
const reply = RichReply.createFor(roomId, event, message, message);
2726
reply['msgtype'] = "m.notice";

src/commands/UnbanBanCommand.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { Mjolnir } from "../Mjolnir";
1818
import PolicyList from "../models/PolicyList";
1919
import { extractRequestError, LogLevel, LogService, MatrixGlob, RichReply } from "matrix-bot-sdk";
2020
import { Recommendation, RULE_ROOM, RULE_SERVER, RULE_USER, USER_RULE_TYPES } from "../models/ListRule";
21-
import config from "../config";
2221
import { DEFAULT_LIST_EVENT_TYPE } from "./SetDefaultBanListCommand";
2322

2423
interface Arguments {
@@ -95,7 +94,7 @@ export async function parseArguments(roomId: string, event: any, mjolnir: Mjolni
9594
else if (!ruleType) replyMessage = "Please specify the type as either 'user', 'room', or 'server'";
9695
else if (!entity) replyMessage = "No entity found";
9796

98-
if (config.commands.confirmWildcardBan && /[*?]/.test(entity) && !force) {
97+
if (mjolnir.config.commands.confirmWildcardBan && /[*?]/.test(entity) && !force) {
9998
replyMessage = "Wildcard bans require an additional `--force` argument to confirm";
10099
}
101100

@@ -150,7 +149,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol
150149
if (rule.test(victim)) {
151150
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`, protectedRoomId);
152151

153-
if (!config.noop) {
152+
if (!mjolnir.config.noop) {
154153
await mjolnir.client.unbanUser(victim, protectedRoomId);
155154
} else {
156155
await mjolnir.logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId);
@@ -163,7 +162,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol
163162

164163
if (unbannedSomeone) {
165164
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Syncing lists to ensure no users were accidentally unbanned`);
166-
await mjolnir.syncLists(config.verboseLogging);
165+
await mjolnir.syncLists(mjolnir.config.verboseLogging);
167166
}
168167
}
169168

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { MatrixClient } from "matrix-bot-sdk";
2525
// The object is magically generated by external lib `config`
2626
// from the file specified by `NODE_ENV`, e.g. production.yaml
2727
// or harness.yaml.
28-
interface IConfig {
28+
export interface IConfig {
2929
homeserverUrl: string;
3030
rawHomeserverUrl: string;
3131
accessToken: string;

src/health/healthz.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import config from "../config";
1817
import * as http from "http";
1918
import { LogService } from "matrix-bot-sdk";
19+
// allowed to use the global configuration since this is only intended to be used by `src/index.ts`.
20+
import config from '../config';
2021

2122
export class Healthz {
2223
private static healthCode: number;

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,14 @@ if (config.health.healthz.enabled) {
5656
patchMatrixClient();
5757
config.RUNTIME.client = client;
5858

59-
bot = await Mjolnir.setupMjolnirFromConfig(client);
59+
bot = await Mjolnir.setupMjolnirFromConfig(client, config);
6060
} catch (err) {
6161
console.error(`Failed to setup mjolnir from the config ${config.dataPath}: ${err}`);
6262
throw err;
6363
}
6464
try {
6565
await bot.start();
66+
Healthz.isHealthy = true;
6667
} catch (err) {
6768
console.error(`Mjolnir failed to start: ${err}`);
6869
throw err;

src/protections/BasicFlooding.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { Protection } from "./IProtection";
1818
import { NumberProtectionSetting } from "./ProtectionSettings";
1919
import { Mjolnir } from "../Mjolnir";
2020
import { LogLevel, LogService } from "matrix-bot-sdk";
21-
import config from "../config";
2221

2322
// if this is exceeded, we'll ban the user for spam and redact their messages
2423
export const DEFAULT_MAX_PER_MINUTE = 10;
@@ -64,7 +63,7 @@ export class BasicFlooding extends Protection {
6463

6564
if (messageCount >= this.settings.maxPerMinute.value) {
6665
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`, roomId);
67-
if (!config.noop) {
66+
if (!mjolnir.config.noop) {
6867
await mjolnir.client.banUser(event['sender'], roomId, "spam");
6968
} else {
7069
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
@@ -75,7 +74,7 @@ export class BasicFlooding extends Protection {
7574
this.recentlyBanned.push(event['sender']); // flag to reduce spam
7675

7776
// Redact all the things the user said too
78-
if (!config.noop) {
77+
if (!mjolnir.config.noop) {
7978
for (const eventId of forUser.map(e => e.eventId)) {
8079
await mjolnir.client.redactEvent(roomId, eventId, "spam");
8180
}

0 commit comments

Comments
 (0)