From 25990cf69026d265664ee20f5585d5677933934c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 29 May 2024 12:22:12 +0200 Subject: [PATCH 01/17] Integrate MediaProxy to bridge authenticated Matrix media (MSC3910) --- config.sample.yaml | 18 ++++++++++++------ config.schema.yml | 14 ++++++++++++-- src/bridge/IrcBridge.ts | 36 +++++++++++++++++++++++++++++++++--- src/bridge/MatrixHandler.ts | 25 ++++++++++++------------- src/config/BridgeConfig.ts | 7 ++++++- src/generate-signing-key.ts | 11 +++++++++++ src/main.ts | 2 +- src/models/MatrixAction.ts | 8 ++++---- 8 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 src/generate-signing-key.ts diff --git a/config.sample.yaml b/config.sample.yaml index 84ab35cba..491897676 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -5,12 +5,6 @@ homeserver: # The URL to the home server for client-server API calls, also used to form the # media URLs as displayed in bridged IRC channels: url: "http://localhost:8008" - # - # The URL of the homeserver hosting media files. This is only used to transform - # mxc URIs to http URIs when bridging m.room.[file|image] events. Optional. By - # default, this is the homeserver URL, specified above. - # This key CAN be hot-reloaded. - # media_url: "http://media.repo:8008" # Drop Matrix messages which are older than this number of seconds, according to # the event's origin_server_ts. @@ -607,6 +601,18 @@ ircService: # Ignore users mentioned in a io.element.functional_members state event when checking admin room membership ignoreFunctionalMembersInAdminRooms: false + # Config for the media proxy, required to serve publically accessible URLs to authenticated Matrix media + mediaProxy: + # To generate a .jwk file: + # $ yarn -s ts-node src/generate-signing-key.ts > signingkey.jwk + signingKeyPath: "signingkey.jwk" + # How long should the generated URLs be valid for + ttlSeconds: 3600 + # The port for the media proxy to listen on + bindPort: 11111 + # The publically accessible URL to the media proxy + publicUrl: "https://irc.bridge/media" + # Maximum number of montly active users, beyond which the bridge gets blocked (both ways) # RMAUlimit: 100 diff --git a/config.schema.yml b/config.schema.yml index 17d78fe2a..90ed862e9 100644 --- a/config.schema.yml +++ b/config.schema.yml @@ -44,8 +44,6 @@ properties: properties: url: type: "string" - media_url: - type: "string" domain: type: "string" dropMatrixMessagesAfterSecs: @@ -173,6 +171,18 @@ properties: type: "integer" ignoreFunctionalMembersInAdminRooms: type: "boolean" + mediaProxy: + type: "object" + properties: + signingKeyPath: + type: "string" + ttlSeconds: + type: "integer" + bindPort: + type: "integer" + publicUrl: + type: "string" + required: ["signingKeyPath", "ttlSeconds", "bindPort", "publicUrl"] ircHandler: type: "object" properties: diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index 365711f43..ac58854b9 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -1,5 +1,6 @@ import Bluebird from "bluebird"; import extend from "extend"; +import * as fs from "fs"; import * as promiseutil from "../promiseutil"; import { IrcHandler, MatrixMembership } from "./IrcHandler"; import { MatrixHandler, MatrixEventInvite, OnMemberEventData, MatrixEventKick } from "./MatrixHandler"; @@ -41,6 +42,7 @@ import { UserActivityTracker, UserActivityTrackerConfig, WeakStateEvent, + MediaProxy, } from "matrix-appservice-bridge"; import { IrcAction } from "../models/IrcAction"; import { DataStore } from "../datastore/DataStore"; @@ -56,6 +58,7 @@ import { TestingOptions } from "../config/TestOpts"; import { MatrixBanSync } from "./MatrixBanSync"; import { configure } from "../logging"; import { IrcPoolClient } from "../pool-service/IrcPoolClient"; +import { webcrypto } from "node:crypto"; const log = getLogger("IrcBridge"); const DEFAULT_PORT = 8090; @@ -88,6 +91,7 @@ export class IrcBridge { public activityTracker: ActivityTracker|null = null; public readonly roomConfigs: RoomConfig; public readonly matrixBanSyncer?: MatrixBanSync; + private _mediaProxy?: MediaProxy; private clientPool!: ClientPool; // This gets defined in the `run` function private ircServers: IrcServer[] = []; private memberListSyncers: {[domain: string]: MemberListSyncer} = {}; @@ -209,6 +213,7 @@ export class IrcBridge { defaultTtlMs: 10 * 60 * 1000, // 10 mins }); this.matrixBanSyncer = this.config.ircService.banLists && new MatrixBanSync(this.config.ircService.banLists); + this.matrixHandler = new MatrixHandler(this, this.config.ircService.matrixHandler, this.membershipQueue); this.privacyProtection = new PrivacyProtection(this); this.ircHandler = new IrcHandler( @@ -255,9 +260,10 @@ export class IrcBridge { log.info(`Adjusted dropMatrixMessagesAfterSecs to ${newConfig.homeserver.dropMatrixMessagesAfterSecs}`); } - if (oldConfig.homeserver.media_url !== newConfig.homeserver.media_url) { - oldConfig.homeserver.media_url = newConfig.homeserver.media_url; - log.info(`Adjusted media_url to ${newConfig.homeserver.media_url}`); + if (JSON.stringify(oldConfig.ircService.mediaProxy) !== JSON.stringify(newConfig.ircService.mediaProxy)) { + await this._mediaProxy?.close(); + this._mediaProxy = await this.initialiseMediaProxy(); + log.info(`Media proxy reinitialized`); } await this.setupStateSyncer(newConfig); @@ -308,6 +314,21 @@ export class IrcBridge { await this.clientPool.checkForBannedConnectedUsers(); } + private async initialiseMediaProxy(): Promise { + const config = this.config.ircService.mediaProxy; + const jwk = JSON.parse(fs.readFileSync(config.signingKeyPath, "utf8").toString()); + const signingKey = await webcrypto.subtle.importKey('jwk', jwk, { + name: 'HMAC', + hash: 'SHA-512', + }, true, ['sign', 'verify']); + const publicUrl = new URL(config.publicUrl); + + const mediaProxy = new MediaProxy({ publicUrl, signingKey, ttl: config.ttlSeconds * 1000 }, this.bridge.getIntent().matrixClient); + mediaProxy.start(config.bindPort); + + return mediaProxy; + } + private initialiseMetrics(bindPort: number) { const zeroAge = new AgeCounters(); const registry = new Registry(); @@ -523,6 +544,13 @@ export class IrcBridge { return `@${this.registration.getSenderLocalpart()}:${this.domain}`; } + public get mediaProxy(): MediaProxy { + if (!this._mediaProxy) { + throw new Error(`Bridge not yet initialized`); + } + return this._mediaProxy; + } + public getStore() { return this.dataStore; } @@ -654,6 +682,8 @@ export class IrcBridge { await this.dataStore.removeConfigMappings(); + this._mediaProxy = await this.initialiseMediaProxy(); + if (this.activityTracker) { log.info("Restoring last active times from DB"); const users = await this.dataStore.getLastSeenTimeForUsers(); diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 89a0885b7..2027e0e26 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -8,6 +8,7 @@ import { StateLookup, StateLookupEvent, Intent, + MediaProxy, } from "matrix-appservice-bridge"; import { IrcUser } from "../models/IrcUser"; import { ActionType, MatrixAction, MatrixMessageEvent } from "../models/MatrixAction"; @@ -142,7 +143,6 @@ export class MatrixHandler { private readonly metrics: {[domain: string]: { [metricName: string]: number; };} = {}; - private readonly mediaUrl: string; private memberTracker?: StateLookup; private adminHandler: AdminRoomHandler; private config: MatrixHandlerConfig = DEFAULTS; @@ -155,15 +155,16 @@ export class MatrixHandler { constructor( private readonly ircBridge: IrcBridge, config: MatrixHandlerConfig|undefined, - private readonly membershipQueue: MembershipQueue + private readonly membershipQueue: MembershipQueue, ) { this.onConfigChanged(config); - - // The media URL to use to transform mxc:// URLs when handling m.room.[file|image]s - this.mediaUrl = ircBridge.config.homeserver.media_url || ircBridge.config.homeserver.url; this.adminHandler = new AdminRoomHandler(ircBridge, this); } + private get mediaProxy(): MediaProxy { + return this.ircBridge.mediaProxy; + } + public initialise() { this.memberTracker = new StateLookup({ intent: this.ircBridge.getAppServiceBridge().getIntent(), @@ -909,9 +910,7 @@ export class MatrixHandler { req.log.debug("Message body: %s", event.content.body); } - const mxAction = MatrixAction.fromEvent( - event, this.mediaUrl - ); + const mxAction = await MatrixAction.fromEvent(event, this.mediaProxy); // check if this message is from one of our virtual users const servers = this.ircBridge.getServers(); @@ -1171,7 +1170,7 @@ export class MatrixHandler { // This is true if the upload was a success if (contentUri) { - const httpUrl = ContentRepo.getHttpUriForMxc(this.mediaUrl, contentUri); + const httpUrl = await this.mediaProxy.generateMediaUrl(contentUri); // we check event.content.body since ircAction already has the markers stripped const codeBlockMatch = event.content.body.match(/^```(\w+)?/); if (codeBlockMatch) { @@ -1182,7 +1181,7 @@ export class MatrixHandler { }; } else { - const explanation = renderTemplate(this.config.truncatedMessageTemplate, { url: httpUrl }); + const explanation = renderTemplate(this.config.truncatedMessageTemplate, { url: httpUrl.toString() }); let messagePreview = trimString( potentialMessages[0], ircClient.getMaxLineLength() - 4 /* "... " */ - explanation.length - ircRoom.channel.length @@ -1198,7 +1197,7 @@ export class MatrixHandler { } const truncatedIrcAction = IrcAction.fromMatrixAction( - MatrixAction.fromEvent(event, this.mediaUrl) + await MatrixAction.fromEvent(event, this.mediaProxy) ); if (truncatedIrcAction) { await this.ircBridge.sendIrcAction(ircRoom, ircClient, truncatedIrcAction); @@ -1220,9 +1219,9 @@ export class MatrixHandler { // Recreate action from modified event const truncatedIrcAction = IrcAction.fromMatrixAction( - MatrixAction.fromEvent( + await MatrixAction.fromEvent( sendingEvent, - this.mediaUrl, + this.mediaProxy, ) ); if (truncatedIrcAction) { diff --git a/src/config/BridgeConfig.ts b/src/config/BridgeConfig.ts index 0ae31069a..a6e7f905b 100644 --- a/src/config/BridgeConfig.ts +++ b/src/config/BridgeConfig.ts @@ -13,7 +13,6 @@ export interface BridgeConfig { }; homeserver: { url: string; - media_url?: string; domain: string; enablePresence?: boolean; dropMatrixMessagesAfterSecs?: number; @@ -23,6 +22,12 @@ export interface BridgeConfig { ircService: { servers: {[domain: string]: IrcServerConfig}; matrixHandler?: MatrixHandlerConfig; + mediaProxy: { + signingKeyPath: string; + ttlSeconds: number; + bindPort: number; + publicUrl: string; + }; ircHandler?: IrcHandlerConfig; provisioning: ProvisionerConfig; logging: LoggerConfig; diff --git a/src/generate-signing-key.ts b/src/generate-signing-key.ts new file mode 100644 index 000000000..e083d9324 --- /dev/null +++ b/src/generate-signing-key.ts @@ -0,0 +1,11 @@ +import { webcrypto } from 'node:crypto'; + +async function main() { + const key = await webcrypto.subtle.generateKey({ + name: 'HMAC', + hash: 'SHA-512', + }, true, ['sign', 'verify']); + console.log(await webcrypto.subtle.exportKey('jwk', key)); +} + +main().then(() => process.exit(0)).catch(err => { throw err }); diff --git a/src/main.ts b/src/main.ts index f4f7ad67d..0ca9ee6c4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ import Datastore from "nedb"; import extend from "extend"; import http from "http"; import https from "https"; -import { RoomBridgeStore, UserBridgeStore, AppServiceRegistration } from "matrix-appservice-bridge"; +import { RoomBridgeStore, UserBridgeStore, AppServiceRegistration, MediaProxy } from "matrix-appservice-bridge"; import { IrcBridge } from "./bridge/IrcBridge"; import { IrcServer, IrcServerConfig } from "./irc/IrcServer"; import ident from "./irc/Ident"; diff --git a/src/models/MatrixAction.ts b/src/models/MatrixAction.ts index e213aeb46..d73eb7901 100644 --- a/src/models/MatrixAction.ts +++ b/src/models/MatrixAction.ts @@ -17,7 +17,7 @@ limitations under the License. import { IrcAction } from "./IrcAction"; import ircFormatting = require("../irc/formatting"); -import { ContentRepo, Intent } from "matrix-appservice-bridge"; +import { ContentRepo, Intent, MediaProxy } from "matrix-appservice-bridge"; import escapeStringRegexp from "escape-string-regexp"; import logging from "../logging"; const log = logging("MatrixAction"); @@ -110,6 +110,7 @@ export class MatrixAction { public htmlText: string|null = null, public readonly ts: number = 0, public replyEvent?: string, + private mediaProxy?: MediaProxy, ) { } public get msgType() { @@ -183,7 +184,7 @@ export class MatrixAction { } } - public static fromEvent(event: MatrixMessageEvent, mediaUrl: string, filename?: string) { + public static async fromEvent(event: MatrixMessageEvent, mediaProxy: MediaProxy, filename?: string) { event.content = event.content || {}; let type = EVENT_TO_TYPE[event.type] || "message"; // mx event type to action type let text = event.content.body; @@ -212,14 +213,13 @@ export class MatrixAction { fileSize = "(" + Math.round(event.content.info.size / 1024) + "KiB)"; } - let url = ContentRepo.getHttpUriForMxc(mediaUrl, event.content.url); + const url = await mediaProxy.generateMediaUrl(event.content.url); if (!filename && event.content.body && /\S*\.[\w\d]{2,4}$/.test(event.content.body)) { // Add filename to url if body is a filename. filename = event.content.body; } if (filename) { - url += `/${encodeURIComponent(filename)}`; text = `${fileSize} < ${url} >`; } else { From 4efccccac2da5ce23a443f0f69223da25f601826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 14 Aug 2024 12:30:19 +0200 Subject: [PATCH 02/17] linting --- src/bridge/IrcBridge.ts | 6 +++++- src/bridge/MatrixHandler.ts | 1 - src/main.ts | 2 +- src/models/MatrixAction.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index ac58854b9..dadaaffad 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -323,7 +323,11 @@ export class IrcBridge { }, true, ['sign', 'verify']); const publicUrl = new URL(config.publicUrl); - const mediaProxy = new MediaProxy({ publicUrl, signingKey, ttl: config.ttlSeconds * 1000 }, this.bridge.getIntent().matrixClient); + const mediaProxy = new MediaProxy({ + publicUrl, + signingKey, + ttl: config.ttlSeconds * 1000 + }, this.bridge.getIntent().matrixClient); mediaProxy.start(config.bindPort); return mediaProxy; diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 2027e0e26..4e4616732 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -1,7 +1,6 @@ import { IrcBridge } from "./IrcBridge"; import { BridgeRequest, BridgeRequestErr } from "../models/BridgeRequest"; import { - ContentRepo, MatrixUser, MatrixRoom, MembershipQueue, diff --git a/src/main.ts b/src/main.ts index 0ca9ee6c4..f4f7ad67d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ import Datastore from "nedb"; import extend from "extend"; import http from "http"; import https from "https"; -import { RoomBridgeStore, UserBridgeStore, AppServiceRegistration, MediaProxy } from "matrix-appservice-bridge"; +import { RoomBridgeStore, UserBridgeStore, AppServiceRegistration } from "matrix-appservice-bridge"; import { IrcBridge } from "./bridge/IrcBridge"; import { IrcServer, IrcServerConfig } from "./irc/IrcServer"; import ident from "./irc/Ident"; diff --git a/src/models/MatrixAction.ts b/src/models/MatrixAction.ts index d73eb7901..6f211b45d 100644 --- a/src/models/MatrixAction.ts +++ b/src/models/MatrixAction.ts @@ -17,7 +17,7 @@ limitations under the License. import { IrcAction } from "./IrcAction"; import ircFormatting = require("../irc/formatting"); -import { ContentRepo, Intent, MediaProxy } from "matrix-appservice-bridge"; +import { Intent, MediaProxy } from "matrix-appservice-bridge"; import escapeStringRegexp from "escape-string-regexp"; import logging from "../logging"; const log = logging("MatrixAction"); From 515c09d155f8569eaa4066c002350a1fb44aa0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 14 Aug 2024 12:30:33 +0200 Subject: [PATCH 03/17] Make singing key generator emit valid JSON; include a sample in test files --- spec/util/test-config.json | 6 ++++++ src/generate-signing-key.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/util/test-config.json b/spec/util/test-config.json index 870da20e3..88bc9705f 100644 --- a/spec/util/test-config.json +++ b/spec/util/test-config.json @@ -46,6 +46,12 @@ "matrixHandler": { "ignoreFunctionalMembersInAdminRooms": true }, + "mediaProxy": { + "signingKeyPath": "./spec/support/signingkey.jwk", + "ttlSeconds": 5, + "bindPort": 11111, + "publicUrl": "https://irc.bridge/media" + }, "servers": { "irc.example": { "port": 6667, diff --git a/src/generate-signing-key.ts b/src/generate-signing-key.ts index e083d9324..92426b3d5 100644 --- a/src/generate-signing-key.ts +++ b/src/generate-signing-key.ts @@ -5,7 +5,7 @@ async function main() { name: 'HMAC', hash: 'SHA-512', }, true, ['sign', 'verify']); - console.log(await webcrypto.subtle.exportKey('jwk', key)); + console.log(JSON.stringify(await webcrypto.subtle.exportKey('jwk', key), undefined, 4)); } main().then(() => process.exit(0)).catch(err => { throw err }); From 3d822e8d6d32b45d334a095625d3a61730febe95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 14 Aug 2024 12:48:38 +0200 Subject: [PATCH 04/17] Fix media-related tests --- spec/integ/matrix-to-irc.spec.js | 36 ++++++++------------------------ 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index 9aff5ede5..5393e0cb5 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -632,10 +632,10 @@ describe("Matrix-to-IRC message bridging", function() { expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); // don't be too brittle when checking this, but I expect to see the - // start of the first line and the mxc fragment + // start of the first line and the media proxy url expect(text.indexOf(tBody[0])).toEqual(0); expect(text.indexOf(tBody[1])).not.toEqual(0); - expect(text.indexOf('deadbeefcafe')).not.toEqual(-1); + expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); @@ -667,9 +667,9 @@ describe("Matrix-to-IRC message bridging", function() { expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); // don't be too brittle when checking this, but I expect to see the - // code type and the mxc fragment. + // code type and the media proxy url expect(text.indexOf('javascript')).not.toEqual(-1); - expect(text.indexOf('deadbeefcafe')).not.toEqual(-1); + expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); @@ -695,10 +695,10 @@ describe("Matrix-to-IRC message bridging", function() { expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); // don't be too brittle when checking this, but I expect to see the - // start of the first line and the mxc fragment + // start of the first line and the media proxy URL expect(text.indexOf(tBody[0])).toEqual(0); expect(text.indexOf(tBody[1])).not.toEqual(0); - expect(text.indexOf('deadbeefcafe')).not.toEqual(-1); + expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); @@ -716,17 +716,12 @@ describe("Matrix-to-IRC message bridging", function() { it("should bridge matrix images as IRC action with a URL", function(done) { const tBody = "the_image.jpg"; const tMxcSegment = "/somecontentid"; - const tHsUrl = "https://some.home.server.goeshere/"; env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", (client, channel, text) => { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - // don't be too brittle when checking this, but I expect to see the - // filename (body) and the http url. - expect(text.indexOf(tBody)).not.toEqual(-1); - expect(text.indexOf(tHsUrl)).not.toEqual(-1); - expect(text.indexOf(tMxcSegment)).not.toEqual(-1); + expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); @@ -745,17 +740,12 @@ describe("Matrix-to-IRC message bridging", function() { it("should bridge matrix files as IRC action with a URL", function(done) { const tBody = "a_file.apk"; const tMxcSegment = "/somecontentid"; - const tHsUrl = "https://some.home.server.goeshere/"; env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", (client, channel, text) => { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - // don't be too brittle when checking this, but I expect to see the - // filename (body) and the http url. - expect(text.indexOf(tBody)).not.toEqual(-1); - expect(text.indexOf(tHsUrl)).not.toEqual(-1); - expect(text.indexOf(tMxcSegment)).not.toEqual(-1); + expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); @@ -1087,21 +1077,13 @@ describe("Matrix-to-IRC message bridging with media URL and drop time", function it("should bridge matrix files as IRC action with a configured media URL", function(done) { let tBody = "a_file.apk"; let tMxcSegment = "/somecontentid"; - let tMediaUrl = mediaUrl; - let tHsUrl = "http://somedomain.com"; - const sdk = env.clientMock._client(config._botUserId); env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", function(client, channel, text) { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - // don't be too brittle when checking this, but I expect to see the - // filename (body) and the http url. - expect(text.indexOf(tBody)).not.toEqual(-1, "File name not present"); - expect(text.indexOf(tHsUrl)).toEqual(-1, "HS URL present instead of media URL"); - expect(text.indexOf(tMediaUrl)).not.toEqual(-1, "No media URL"); - expect(text.indexOf(tMxcSegment)).not.toEqual(-1, "No Mxc segment"); + expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); From b573de2c87a82b6f344197cc5a32cf06a4ea0181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 14 Aug 2024 13:53:31 +0200 Subject: [PATCH 05/17] Make mediaProxy required in the configuration --- config.schema.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.schema.yml b/config.schema.yml index 90ed862e9..74bd389a1 100644 --- a/config.schema.yml +++ b/config.schema.yml @@ -495,4 +495,4 @@ properties: kickReason: type: "string" required: ["regex"] - required: ["servers"] + required: ["servers", "mediaProxy"] From 395a8c1f0a848e57f09c5f81cd25a425840f7768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 14 Aug 2024 13:55:28 +0200 Subject: [PATCH 06/17] Await MediaProxy's startup --- src/bridge/IrcBridge.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index dadaaffad..ce0d3b57c 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -213,7 +213,6 @@ export class IrcBridge { defaultTtlMs: 10 * 60 * 1000, // 10 mins }); this.matrixBanSyncer = this.config.ircService.banLists && new MatrixBanSync(this.config.ircService.banLists); - this.matrixHandler = new MatrixHandler(this, this.config.ircService.matrixHandler, this.membershipQueue); this.privacyProtection = new PrivacyProtection(this); this.ircHandler = new IrcHandler( @@ -328,7 +327,7 @@ export class IrcBridge { signingKey, ttl: config.ttlSeconds * 1000 }, this.bridge.getIntent().matrixClient); - mediaProxy.start(config.bindPort); + await mediaProxy.start(config.bindPort); return mediaProxy; } From 4958454c11e03aca2745eb598c2e965a0d5bd111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 21 Aug 2024 11:58:41 +0200 Subject: [PATCH 07/17] Add a test signingkey --- spec/support/signingkey.jwk | 10 ++++++++++ ...generate-signing-key.ts => generate-signing-key.js} | 0 2 files changed, 10 insertions(+) create mode 100644 spec/support/signingkey.jwk rename src/{generate-signing-key.ts => generate-signing-key.js} (100%) diff --git a/spec/support/signingkey.jwk b/spec/support/signingkey.jwk new file mode 100644 index 000000000..783d02f96 --- /dev/null +++ b/spec/support/signingkey.jwk @@ -0,0 +1,10 @@ +{ + "key_ops": [ + "sign", + "verify" + ], + "ext": true, + "kty": "oct", + "k": "1bWvvQPL7rVtRYMmesyR5z5LDDMZCktGgdiDwTtuYZARd_OIDtxUpRZP8zh46uPy06CaPpJQrCl84BzSKpSzMSsbYcvYM1Zn25VsEzH_OYJP2B0if-mAblVDMNoXva2ZobKXMz0dXrxeE0I-fwrORBKOcuZzveqH4VYGaeFPDkk", + "alg": "HS512" +} diff --git a/src/generate-signing-key.ts b/src/generate-signing-key.js similarity index 100% rename from src/generate-signing-key.ts rename to src/generate-signing-key.js From e992b5cbd36563f13e9e7871c9807d885d0eb801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 21 Aug 2024 11:58:48 +0200 Subject: [PATCH 08/17] De-typescriptify signing key generator --- config.sample.yaml | 2 +- src/generate-signing-key.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index 491897676..3e1b419e4 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -604,7 +604,7 @@ ircService: # Config for the media proxy, required to serve publically accessible URLs to authenticated Matrix media mediaProxy: # To generate a .jwk file: - # $ yarn -s ts-node src/generate-signing-key.ts > signingkey.jwk + # $ node src/generate-signing-key.js > signingkey.jwk signingKeyPath: "signingkey.jwk" # How long should the generated URLs be valid for ttlSeconds: 3600 diff --git a/src/generate-signing-key.js b/src/generate-signing-key.js index 92426b3d5..cb032b43c 100644 --- a/src/generate-signing-key.js +++ b/src/generate-signing-key.js @@ -1,4 +1,4 @@ -import { webcrypto } from 'node:crypto'; +const webcrypto = require('node:crypto'); async function main() { const key = await webcrypto.subtle.generateKey({ From fa75a9c2d8440071431478b567baaa96517650d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 21 Aug 2024 12:23:09 +0200 Subject: [PATCH 09/17] Update MAB dependency to pull in Media Proxy --- package.json | 2 +- yarn.lock | 65 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index a224cb059..434999b83 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "he": "^1.2.0", "ioredis": "^5.3.1", "logform": "^2.4.2", - "matrix-appservice-bridge": "^10.1.0", + "matrix-appservice-bridge": "^10.2.0", "matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@^0.7.0-element.1", "matrix-org-irc": "^3.0.0", "nopt": "^7.2.0", diff --git a/yarn.lock b/yarn.lock index 62abf1486..2e241cc33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1406,7 +1406,7 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vector-im/matrix-bot-sdk@^0.7.0-element.0", "matrix-bot-sdk@npm:@vector-im/matrix-bot-sdk@^0.7.0-element.1": +"@vector-im/matrix-bot-sdk@^0.7.0-element.0": version "0.7.0-element.1" resolved "https://registry.yarnpkg.com/@vector-im/matrix-bot-sdk/-/matrix-bot-sdk-0.7.0-element.1.tgz#94704e6abda3e5098d40e5b626ab7288645bb090" integrity sha512-klP0F9YQZyjuvWOtfHnJ4ftBOzQ9jn0TSTSHv/UuGUnQuvWeSQytZAu1zLgHHQ9fs7Li16QwpuTPQ0/MNre+lw== @@ -4677,10 +4677,10 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -matrix-appservice-bridge@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/matrix-appservice-bridge/-/matrix-appservice-bridge-10.1.0.tgz#8d23bb4c671822684e3121224f1df339f0615b9d" - integrity sha512-WiyovdQv3WfguffCTTycZ+tM+gwc4DvxSS6em0q5kjdCYiw+ogXezTTkUZZ2QBsIqmgVzF96UQC8PuFS7iWoBA== +matrix-appservice-bridge@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/matrix-appservice-bridge/-/matrix-appservice-bridge-10.2.0.tgz#79b6f53593ab21cf6211a3ba1051a0439b2f2824" + integrity sha512-GYpIBPgQnc0/p93KoqzrOYwh6JJEWZc+t9N0GxMO7EXb0IEkfKMji6lOOEqk9QdNs2+fpMBKIS2mjT3X5iuTUg== dependencies: "@alloc/quick-lru" "^5.2.0" "@types/nedb" "^1.8.16" @@ -4711,6 +4711,30 @@ matrix-appservice@^2.0.0: js-yaml "^4.1.0" morgan "^1.10.0" +"matrix-bot-sdk@npm:@vector-im/matrix-bot-sdk@^0.7.0-element.1": + version "0.7.0-element.1" + resolved "https://registry.yarnpkg.com/@vector-im/matrix-bot-sdk/-/matrix-bot-sdk-0.7.0-element.1.tgz#94704e6abda3e5098d40e5b626ab7288645bb090" + integrity sha512-klP0F9YQZyjuvWOtfHnJ4ftBOzQ9jn0TSTSHv/UuGUnQuvWeSQytZAu1zLgHHQ9fs7Li16QwpuTPQ0/MNre+lw== + dependencies: + "@matrix-org/matrix-sdk-crypto-nodejs" "0.1.0-beta.11" + "@types/express" "^4.17.21" + another-json "^0.2.0" + async-lock "^1.4.0" + chalk "4" + express "^4.18.2" + glob-to-regexp "^0.4.1" + hash.js "^1.1.7" + html-to-text "^9.0.5" + htmlencode "^0.0.4" + lowdb "1" + lru-cache "^10.0.1" + mkdirp "^3.0.1" + morgan "^1.10.0" + postgres "^3.4.1" + request "^2.88.2" + request-promise "^4.2.6" + sanitize-html "^2.11.0" + matrix-org-irc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matrix-org-irc/-/matrix-org-irc-3.0.0.tgz#de7c23cc6b69e12574704bd4aa43a4d89976d589" @@ -6216,7 +6240,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6284,7 +6317,14 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6895,7 +6935,7 @@ winston@^3.8.2: triple-beam "^1.3.0" winston-transport "^4.7.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -6913,6 +6953,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From df2c0d53da593bca5305041543731ca2c343b326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 21 Aug 2024 12:31:53 +0200 Subject: [PATCH 10/17] Add mediaProxy config to e2e tests --- spec/e2e/jest.config.js | 2 +- spec/util/e2e-test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/e2e/jest.config.js b/spec/e2e/jest.config.js index e78492374..f2681d05e 100644 --- a/spec/e2e/jest.config.js +++ b/spec/e2e/jest.config.js @@ -2,7 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - reporters: [['github-actions', {silent: false}], 'summary'], + // reporters: ['text', ['github-actions', {silent: false}], 'summary'], transformIgnorePatterns: ['/node_modules/'], testTimeout: 60000, transform: { diff --git a/spec/util/e2e-test.ts b/spec/util/e2e-test.ts index ccf306dfa..870eb118c 100644 --- a/spec/util/e2e-test.ts +++ b/spec/util/e2e-test.ts @@ -242,6 +242,12 @@ export class IrcBridgeE2ETest { ...MatrixHandlerDefaults, shortReplyTresholdSeconds: opts.shortReplyTresholdSeconds ?? 0, }, + "mediaProxy": { + "signingKeyPath": "./spec/support/signingkey.jwk", + "ttlSeconds": 5, + "bindPort": 11111, + "publicUrl": "https://irc.bridge/media" + }, servers: { localhost: { ...IrcServer.DEFAULT_CONFIG, From 659981c46cc02c03f8256f43a37af75cf67bf60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Tue, 27 Aug 2024 15:25:59 +0200 Subject: [PATCH 11/17] Close the media proxy when killing the bridge --- src/bridge/IrcBridge.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index ce0d3b57c..0943dab9f 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -991,6 +991,10 @@ export class IrcBridge { public async kill(reason?: string) { log.info("Killing bridge"); this.bridgeState = "killed"; + if (this._mediaProxy) { + log.info("Killing media proxy"); + await this._mediaProxy.close(); + } log.info("Killing all clients"); if (!this.config.connectionPool?.persistConnectionsOnShutdown) { this.clientPool.killAllClients(reason); From 86dc504c44dde0773e9f75b46027c39c2bb682eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 28 Aug 2024 11:55:02 +0200 Subject: [PATCH 12/17] Make MediaProxy listen on random ports in tests This reduces the chance of conflict with other processes, and also stops one non-clean shutdown from blowing up the rest of the test suite. --- spec/util/e2e-test.ts | 2 +- spec/util/test-config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/util/e2e-test.ts b/spec/util/e2e-test.ts index 870eb118c..5dae30035 100644 --- a/spec/util/e2e-test.ts +++ b/spec/util/e2e-test.ts @@ -245,7 +245,7 @@ export class IrcBridgeE2ETest { "mediaProxy": { "signingKeyPath": "./spec/support/signingkey.jwk", "ttlSeconds": 5, - "bindPort": 11111, + "bindPort": 0, "publicUrl": "https://irc.bridge/media" }, servers: { diff --git a/spec/util/test-config.json b/spec/util/test-config.json index 88bc9705f..883714a58 100644 --- a/spec/util/test-config.json +++ b/spec/util/test-config.json @@ -49,7 +49,7 @@ "mediaProxy": { "signingKeyPath": "./spec/support/signingkey.jwk", "ttlSeconds": 5, - "bindPort": 11111, + "bindPort": 0, "publicUrl": "https://irc.bridge/media" }, "servers": { From 9a591c3c85f9f17c2e8dfdceb8e83552f600f8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 28 Aug 2024 12:33:32 +0200 Subject: [PATCH 13/17] Care less about mediaProxy not shutting down cleanly in kill() --- src/bridge/IrcBridge.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index 0943dab9f..1dd4e07fd 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -993,7 +993,13 @@ export class IrcBridge { this.bridgeState = "killed"; if (this._mediaProxy) { log.info("Killing media proxy"); - await this._mediaProxy.close(); + // We don't care about the outcome since we don't really care + // about the state of the bridge after a kill(). + // Awaiting this tripped up the BOTS-70 unit test for reasons I couldn't figure out, + // and I spent a not-worth-it amount of time on it, so this is me giving up + // and techdebting it. + // Please let me know, future programmer, if you do fix this properly. -- tadzik + void this._mediaProxy.close(); } log.info("Killing all clients"); if (!this.config.connectionPool?.persistConnectionsOnShutdown) { From f1e5ecc05ce6eb77b32423518b3f5f2b1bcc5789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 28 Aug 2024 13:40:58 +0200 Subject: [PATCH 14/17] Log the failure to close mediaproxy to not leave dangling rejections --- src/bridge/IrcBridge.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index 1dd4e07fd..0aa94780f 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -996,10 +996,10 @@ export class IrcBridge { // We don't care about the outcome since we don't really care // about the state of the bridge after a kill(). // Awaiting this tripped up the BOTS-70 unit test for reasons I couldn't figure out, - // and I spent a not-worth-it amount of time on it, so this is me giving up - // and techdebting it. + // and I spent a not-worth-it amount of time on it, + // so this is me giving up and techdebting it. // Please let me know, future programmer, if you do fix this properly. -- tadzik - void this._mediaProxy.close(); + void this._mediaProxy.close().catch(err => log.warn(`Failed to close MediaProxy: ${err}`)); } log.info("Killing all clients"); if (!this.config.connectionPool?.persistConnectionsOnShutdown) { From ad63db49e327301efe15df2dddd8b07f39730121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Wed, 28 Aug 2024 13:46:48 +0200 Subject: [PATCH 15/17] Changelog --- changelog.d/1805.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1805.feature diff --git a/changelog.d/1805.feature b/changelog.d/1805.feature new file mode 100644 index 000000000..6fe874292 --- /dev/null +++ b/changelog.d/1805.feature @@ -0,0 +1 @@ +Use MediaProxy to serve authenticated Matrix media. From ae284637ce967add255c811ad86d3daf0caa0d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Thu, 29 Aug 2024 12:48:32 +0200 Subject: [PATCH 16/17] Undo accidental jest config changes --- spec/e2e/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/e2e/jest.config.js b/spec/e2e/jest.config.js index f2681d05e..e78492374 100644 --- a/spec/e2e/jest.config.js +++ b/spec/e2e/jest.config.js @@ -2,7 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - // reporters: ['text', ['github-actions', {silent: false}], 'summary'], + reporters: [['github-actions', {silent: false}], 'summary'], transformIgnorePatterns: ['/node_modules/'], testTimeout: 60000, transform: { From dabae2bab4becd2ff8a3f43f8f55ad3cc093e53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Mon, 2 Sep 2024 11:37:35 +0200 Subject: [PATCH 17/17] Remove the mention of HS url being used for media --- config.sample.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index 3e1b419e4..46e4d02bb 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -2,8 +2,7 @@ # are *REQUIRED*. # Unless otherwise specified, these keys CANNOT be hot-reloaded. homeserver: - # The URL to the home server for client-server API calls, also used to form the - # media URLs as displayed in bridged IRC channels: + # The URL to the home server for client-server API calls url: "http://localhost:8008" # Drop Matrix messages which are older than this number of seconds, according to