From 4ae5e5f63abde5bfcedb6a31da2c0ccafafc499c Mon Sep 17 00:00:00 2001 From: gnuxie Date: Wed, 12 Mar 2025 18:30:43 +0000 Subject: [PATCH] Begin process of creating top level stores. We now need to move the roomStateBackingStore into this thingy. --- src/Draupnir.ts | 5 ++ src/DraupnirBotMode.ts | 5 +- src/appservice/AppService.ts | 6 +++ src/appservice/AppServiceDraupnirManager.ts | 7 ++- src/backingstore/DraupnirStores.ts | 46 +++++++++++++++++++ src/draupnirfactory/DraupnirFactory.ts | 8 +++- .../RoomTakedown/SqliteRoomAuditLog.ts | 12 +++++ test/integration/mjolnirSetupUtils.ts | 1 + 8 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/backingstore/DraupnirStores.ts diff --git a/src/Draupnir.ts b/src/Draupnir.ts index 92b96fa3..28bb782a 100644 --- a/src/Draupnir.ts +++ b/src/Draupnir.ts @@ -82,6 +82,7 @@ import { makeConfirmationPromptListener, } from "./commands/interface-manager/MatrixPromptForConfirmation"; import { SynapseHttpAntispam } from "./webapis/SynapseHTTPAntispam/SynapseHttpAntispam"; +import { DraupnirStores } from "./backingstore/DraupnirStores"; const log = new Logger("Draupnir"); // webAPIS should not be included on the Draupnir class. @@ -145,6 +146,7 @@ export class Draupnir implements Client, MatrixAdaptorContext { public readonly acceptInvitesFromRoom: MatrixRoomID, public readonly acceptInvitesFromRoomIssuer: RoomMembershipRevisionIssuer, public readonly safeModeToggle: SafeModeToggle, + public readonly stores: DraupnirStores, public readonly synapseAdminClient: SynapseAdminClient | undefined, public readonly synapseHTTPAntispam: SynapseHttpAntispam | undefined ) { @@ -212,6 +214,7 @@ export class Draupnir implements Client, MatrixAdaptorContext { config: IConfig, loggableConfigTracker: LoggableConfigTracker, safeModeToggle: SafeModeToggle, + stores: DraupnirStores, synapseHTTPAntispam: SynapseHttpAntispam | undefined ): Promise> { const acceptInvitesFromRoom = await (async () => { @@ -270,6 +273,7 @@ export class Draupnir implements Client, MatrixAdaptorContext { acceptInvitesFromRoom.ok, acceptInvitesFromRoomIssuer.ok, safeModeToggle, + stores, new SynapseAdminClient(client, clientUserID), synapseHTTPAntispam ); @@ -388,6 +392,7 @@ export class Draupnir implements Client, MatrixAdaptorContext { this.clientRooms.off("timeline", this.timelineEventListener); this.reportPoller?.stop(); this.protectedRoomsSet.unregisterListeners(); + this.stores.dispose(); } public createRoomReference(roomID: StringRoomID): MatrixRoomID { diff --git a/src/DraupnirBotMode.ts b/src/DraupnirBotMode.ts index a08c8583..f0822f4e 100644 --- a/src/DraupnirBotMode.ts +++ b/src/DraupnirBotMode.ts @@ -49,6 +49,7 @@ import { ResultError } from "@gnuxie/typescript-result"; import { SafeModeCause, SafeModeReason } from "./safemode/SafeModeCause"; import { SafeModeBootOption } from "./safemode/BootOption"; import { SynapseHttpAntispam } from "./webapis/SynapseHTTPAntispam/SynapseHttpAntispam"; +import { TopLevelStores } from "./backingstore/DraupnirStores"; const log = new Logger("DraupnirBotMode"); @@ -113,6 +114,7 @@ export class DraupnirBotModeToggle implements BotModeTogle { client: MatrixSendClient, matrixEmitter: SafeMatrixEmitter, config: IConfig, + stores: TopLevelStores, backingStore?: RoomStateBackingStore ): Promise { const clientUserID = await client.getUserId(); @@ -169,7 +171,8 @@ export class DraupnirBotModeToggle implements BotModeTogle { clientsInRoomMap, clientCapabilityFactory, clientProvider, - roomStateManagerFactory + roomStateManagerFactory, + stores ); return new DraupnirBotModeToggle( clientUserID, diff --git a/src/appservice/AppService.ts b/src/appservice/AppService.ts index b10fe7d4..fe21cb6c 100644 --- a/src/appservice/AppService.ts +++ b/src/appservice/AppService.ts @@ -49,6 +49,9 @@ import { StringUserID, } from "@the-draupnir-project/matrix-basic-types"; import { SqliteRoomStateBackingStore } from "../backingstore/better-sqlite3/SqliteRoomStateBackingStore"; +import { TopLevelStores } from "../backingstore/DraupnirStores"; + +// FIXME: Move roomStateBacking store into top level stores. const log = new Logger("AppService"); /** @@ -100,6 +103,7 @@ export class MjolnirAppService { dataStore: DataStore, eventDecoder: EventDecoder, registrationFilePath: string, + stores: TopLevelStores, backingStore?: RoomStateBackingStore ) { const bridge = new Bridge({ @@ -190,6 +194,7 @@ export class MjolnirAppService { bridge, accessControl, roomStateManagerFactory, + stores, clientCapabilityFactory, clientProvider, instanceCountGauge @@ -239,6 +244,7 @@ export class MjolnirAppService { dataStore, DefaultEventDecoder, registrationFilePath, + {}, // we don't support any stores in appservice atm except backing store. backingStore ); // The call to `start` MUST happen last. As it needs the datastore, and the mjolnir manager to be initialized before it can process events from the homeserver. diff --git a/src/appservice/AppServiceDraupnirManager.ts b/src/appservice/AppServiceDraupnirManager.ts index 2ebe88b5..ad13d892 100644 --- a/src/appservice/AppServiceDraupnirManager.ts +++ b/src/appservice/AppServiceDraupnirManager.ts @@ -45,6 +45,7 @@ import { userLocalpart, isStringRoomID, } from "@the-draupnir-project/matrix-basic-types"; +import { TopLevelStores } from "../backingstore/DraupnirStores"; const log = new Logger("AppServiceDraupnirManager"); @@ -63,6 +64,7 @@ export class AppServiceDraupnirManager { private readonly bridge: Bridge, private readonly accessControl: AccessControl, private readonly roomStateManagerFactory: RoomStateManagerFactory, + stores: TopLevelStores, private readonly clientCapabilityFactory: ClientCapabilityFactory, clientProvider: ClientForUserID, private readonly instanceCountGauge: Gauge<"status" | "uuid"> @@ -71,7 +73,8 @@ export class AppServiceDraupnirManager { this.roomStateManagerFactory.clientsInRoomMap, this.clientCapabilityFactory, clientProvider, - this.roomStateManagerFactory + this.roomStateManagerFactory, + stores ); this.baseManager = new StandardDraupnirManager(draupnirFactory); } @@ -93,6 +96,7 @@ export class AppServiceDraupnirManager { bridge: Bridge, accessControl: AccessControl, roomStateManagerFactory: RoomStateManagerFactory, + stores: TopLevelStores, clientCapabilityFactory: ClientCapabilityFactory, clientProvider: ClientForUserID, instanceCountGauge: Gauge<"status" | "uuid"> @@ -103,6 +107,7 @@ export class AppServiceDraupnirManager { bridge, accessControl, roomStateManagerFactory, + stores, clientCapabilityFactory, clientProvider, instanceCountGauge diff --git a/src/backingstore/DraupnirStores.ts b/src/backingstore/DraupnirStores.ts new file mode 100644 index 00000000..8d5ef4fe --- /dev/null +++ b/src/backingstore/DraupnirStores.ts @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: Apache-2.0 + +import { + SHA256RoomHashStore, + StandardSHA256RoomHashStore, +} from "matrix-protection-suite"; +import { RoomAuditLog } from "../protections/RoomTakedown/RoomAuditLog"; + +export type TopLevelStores = { + hashStore?: Omit; + roomAuditLog?: RoomAuditLog; +}; + +/** + * These stores will usually be created at the entrypoint of the draupnir + * application or attenuated for each draupnir. + * + * No i don't like it. Some of these stores ARE specific to the draupnir + * such as the hasStore's event emitter... that just can't be mixed. + * + * We could create a wrapper that disposes of only the stores that + * have been attenuated... but i don't know about it. + */ +export type DraupnirStores = { + hashStore?: SHA256RoomHashStore | undefined; + roomAuditLog?: RoomAuditLog | undefined; + /** + * Dispose of stores relevant to a specific draupnir instance. + * For example, the hash store is usually specific to a single draupnir. + */ + dispose(): void; +}; + +export function createDraupnirStores( + topLevelStores: TopLevelStores +): DraupnirStores { + return Object.freeze({ + roomAudditLog: topLevelStores.roomAuditLog, + hashStore: topLevelStores.hashStore + ? new StandardSHA256RoomHashStore(topLevelStores.hashStore) + : undefined, + dispose() {}, + }); +} diff --git a/src/draupnirfactory/DraupnirFactory.ts b/src/draupnirfactory/DraupnirFactory.ts index 85bb1af9..30d79e38 100644 --- a/src/draupnirfactory/DraupnirFactory.ts +++ b/src/draupnirfactory/DraupnirFactory.ts @@ -30,6 +30,10 @@ import { SafeModeCause } from "../safemode/SafeModeCause"; import { SafeModeToggle } from "../safemode/SafeModeToggle"; import { StandardManagementRoomDetail } from "../managementroom/ManagementRoomDetail"; import { DraupnirBotModeToggle } from "../DraupnirBotMode"; +import { + createDraupnirStores, + TopLevelStores, +} from "../backingstore/DraupnirStores"; const log = new Logger("DraupnirFactory"); @@ -59,7 +63,8 @@ export class DraupnirFactory { private readonly clientsInRoomMap: ClientsInRoomMap, private readonly clientCapabilityFactory: ClientCapabilityFactory, private readonly clientProvider: ClientForUserID, - private readonly roomStateManagerFactory: RoomStateManagerFactory + private readonly roomStateManagerFactory: RoomStateManagerFactory, + private readonly stores: TopLevelStores ) { // nothing to do. } @@ -143,6 +148,7 @@ export class DraupnirFactory { config, configLogTracker, toggle, + createDraupnirStores(this.stores), // synapseHTTPAntispam is only available in bot mode. toggle instanceof DraupnirBotModeToggle ? toggle.synapseHTTPAntispam diff --git a/src/protections/RoomTakedown/SqliteRoomAuditLog.ts b/src/protections/RoomTakedown/SqliteRoomAuditLog.ts index 8b161cc8..de0d3ecc 100644 --- a/src/protections/RoomTakedown/SqliteRoomAuditLog.ts +++ b/src/protections/RoomTakedown/SqliteRoomAuditLog.ts @@ -14,8 +14,10 @@ import { import { BetterSqliteOptions, BetterSqliteStore, + makeBetterSqliteDB, } from "../../backingstore/better-sqlite3/BetterSqliteStore"; import { Database } from "better-sqlite3"; +import path from "path"; // NOTE: This should only be used to check in bulk whether rooms are taken down // upon getting a policy, you probably always want to try again or @@ -67,6 +69,16 @@ export class SqliteRoomAuditLog this.takedownRooms = new Set(this.loadTakendownRooms()); } + public static createToplevel(storagePath: string): SqliteRoomAuditLog { + const options = { + path: path.join(storagePath, "room-audit-log.db"), + WALMode: true, + foreignKeys: true, + fileMustExist: false, + }; + return new SqliteRoomAuditLog(options, makeBetterSqliteDB(options)); + } + public async takedownRoom(policy: LiteralPolicyRule): Promise> { if (policy.kind !== PolicyRuleType.Room) { throw new TypeError( diff --git a/test/integration/mjolnirSetupUtils.ts b/test/integration/mjolnirSetupUtils.ts index 509226d4..adc91f15 100644 --- a/test/integration/mjolnirSetupUtils.ts +++ b/test/integration/mjolnirSetupUtils.ts @@ -165,6 +165,7 @@ export async function makeBotModeToggle( client, new SafeMatrixEmitterWrapper(client, DefaultEventDecoder), config, + {}, // we don't support top level stores for now. backingStore ); // we don't want to send status on startup incase we want to test e2ee from the manual launch script.