Skip to content

Commit 1bdf5aa

Browse files
committed
Add MediaProxy support to serve authenticated Matrix media
1 parent 55d8e65 commit 1bdf5aa

File tree

8 files changed

+113
-11
lines changed

8 files changed

+113
-11
lines changed

config/config.sample.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ db:
5555
#key_file: /path/to/tls.key
5656
#crt_file: /path/to/tls.crt
5757

58+
mediaProxy:
59+
# To generate a .jwk file:
60+
# $ node src/generate-signing-key.js > signingkey.jwk
61+
signingKeyPath: "signingkey.jwk"
62+
# How long should the generated URLs be valid for
63+
ttlSeconds: 3600
64+
# The port for the media proxy to listen on
65+
bindPort: 11111
66+
# The publically accessible URL to the media proxy
67+
publicUrl: "https://slack.bridge/media"
68+
5869
# Real Time Messaging API (RTM)
5970
# Optional if slack_hook_port and inbound_uri_prefix are defined, required otherwise.
6071
#

config/slack-config-schema.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ properties:
3434
type: number
3535
inactive_after_days:
3636
type: number
37+
mediaProxy:
38+
type: "object"
39+
properties:
40+
signingKeyPath:
41+
type: "string"
42+
ttlSeconds:
43+
type: "integer"
44+
bindPort:
45+
type: "integer"
46+
publicUrl:
47+
type: "string"
48+
required: ["signingKeyPath", "ttlSeconds", "bindPort", "publicUrl"]
3749
rtm:
3850
type: object
3951
required: ["enable"]

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@
4343
"axios": "^0.27.2",
4444
"classnames": "^2.3.2",
4545
"escape-string-regexp": "^4.0.0",
46-
"https-proxy-agent": "^5.0.1",
47-
"matrix-appservice-bridge": "^8.1.2",
48-
"matrix-widget-api": "^1.1.1",
49-
"minimist": "^1.2.6",
46+
"https-proxy-agent": "^7.0.4",
47+
"matrix-appservice-bridge": "^10.3.1",
48+
"matrix-bot-sdk": "^0.7.1",
49+
"matrix-widget-api": "^1.6.0",
50+
"minimist": "^1.2.8",
5051
"nedb": "^1.8.0",
5152
"node-emoji": "^1.10.0",
5253
"nunjucks": "^3.2.4",

src/IConfig.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,11 @@ export interface IConfig {
109109
onboard_users?: boolean;
110110
direct_messages?: AllowDenyConfig;
111111
}
112+
113+
mediaProxy: {
114+
signingKeyPath: string;
115+
ttlSeconds: number;
116+
bindPort: number;
117+
publicUrl: string;
118+
}
112119
}

src/Main.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ limitations under the License.
1515
*/
1616

1717
import {
18-
Bridge, BridgeBlocker, PrometheusMetrics, StateLookup,
18+
Bridge, BridgeBlocker, PrometheusMetrics, StateLookup, MediaProxy,
1919
Logger, Intent, UserMembership, WeakEvent, PresenceEvent,
2020
AppService, AppServiceRegistration, UserActivityState, UserActivityTracker,
21-
UserActivityTrackerConfig, MembershipQueue, PowerLevelContent, StateLookupEvent } from "matrix-appservice-bridge";
21+
UserActivityTrackerConfig, MembershipQueue, PowerLevelContent, StateLookupEvent,
22+
} from "matrix-appservice-bridge";
2223
import { Gauge, Counter } from "prom-client";
2324
import * as path from "path";
25+
import * as fs from "fs";
2426
import * as randomstring from "randomstring";
27+
import { webcrypto } from "node:crypto";
2528
import { WebClient } from "@slack/web-api";
2629
import { IConfig, CACHING_DEFAULTS } from "./IConfig";
2730
import { OAuth2 } from "./OAuth2";
@@ -148,6 +151,8 @@ export class Main {
148151
public slackRtm?: SlackRTMHandler;
149152
private slackHookHandler?: SlackHookHandler;
150153

154+
public mediaProxy?: MediaProxy;
155+
151156
private provisioner: Provisioner;
152157

153158
private bridgeBlocker?: BridgeBlocker;
@@ -333,6 +338,22 @@ export class Main {
333338
);
334339
}
335340

341+
private async initialiseMediaProxy(config: IConfig['mediaProxy']): Promise<void> {
342+
const jwk = JSON.parse(fs.readFileSync(config.signingKeyPath, "utf8").toString());
343+
const signingKey = await webcrypto.subtle.importKey('jwk', jwk, {
344+
name: 'HMAC',
345+
hash: 'SHA-512',
346+
}, true, ['sign', 'verify']);
347+
const publicUrl = new URL(config.publicUrl);
348+
349+
this.mediaProxy = new MediaProxy({
350+
publicUrl,
351+
signingKey,
352+
ttl: config.ttlSeconds * 1000
353+
}, this.bridge.getIntent().matrixClient);
354+
await this.mediaProxy.start(config.bindPort);
355+
}
356+
336357
public teamIsUsingRtm(teamId: string): boolean {
337358
return (this.slackRtm !== undefined) && this.slackRtm.teamIsUsingRtm(teamId);
338359
}
@@ -1142,6 +1163,14 @@ export class Main {
11421163
path: "/ready",
11431164
});
11441165

1166+
if (this.config.mediaProxy) {
1167+
await this.initialiseMediaProxy(this.config.mediaProxy).catch(err => {
1168+
throw Error(`Failed to start Media Proxy: ${err}`);
1169+
});
1170+
} else {
1171+
log.warn("Media Proxy not configured: media bridging to Slack won't work on servers requiring authenticated media (default since Synapse v1.120.0)");
1172+
}
1173+
11451174

11461175
await this.pingBridge();
11471176

src/generate-signing-key.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const webcrypto = require('node:crypto');
2+
3+
async function main() {
4+
const key = await webcrypto.subtle.generateKey({
5+
name: 'HMAC',
6+
hash: 'SHA-512',
7+
}, true, ['sign', 'verify']);
8+
console.log(JSON.stringify(await webcrypto.subtle.exportKey('jwk', key), undefined, 4));
9+
}
10+
11+
main().then(() => process.exit(0)).catch(err => { throw err });

src/substitutions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,12 @@ class Substitutions {
181181
// in this case.
182182
return null;
183183
}
184-
const url = main.getUrlForMxc(event.content.url, main.encryptRoom);
184+
let url: string;
185+
if (main.mediaProxy) {
186+
url = await main.mediaProxy.generateMediaUrl(event.content.url).then(url => url.toString());
187+
} else {
188+
url = main.getUrlForMxc(event.content.url, main.encryptRoom);
189+
}
185190
if (main.encryptRoom) {
186191
return {
187192
encrypted_file: url,

yarn.lock

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,11 @@ agent-base@6:
11691169
dependencies:
11701170
debug "4"
11711171

1172+
agent-base@^7.1.2:
1173+
version "7.1.3"
1174+
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1"
1175+
integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==
1176+
11721177
aggregate-error@^3.0.0:
11731178
version "3.1.0"
11741179
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -3008,6 +3013,22 @@ https-proxy-agent@^5.0.1:
30083013
agent-base "6"
30093014
debug "4"
30103015

3016+
https-proxy-agent@^7.0.4, https-proxy-agent@^7.0.5:
3017+
version "7.0.6"
3018+
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
3019+
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
3020+
dependencies:
3021+
agent-base "^7.1.2"
3022+
debug "4"
3023+
3024+
https-proxy-agent@^7.0.5:
3025+
version "7.0.6"
3026+
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
3027+
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
3028+
dependencies:
3029+
agent-base "^7.1.2"
3030+
debug "4"
3031+
30113032
30123033
version "0.4.24"
30133034
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -3612,10 +3633,15 @@ make-error@^1.1.1:
36123633
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
36133634
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
36143635

3615-
matrix-appservice-bridge@^8.1.2:
3616-
version "8.1.2"
3617-
resolved "https://registry.yarnpkg.com/matrix-appservice-bridge/-/matrix-appservice-bridge-8.1.2.tgz#30953a4599533fe61a0c37bd5500b654cd236e30"
3618-
integrity sha512-OTQVEuYgsnlg7fBFASCaiYHgwi5+I/+vxwjOmR4y9n5ESKXoqI465bN+6zvW8MazdNfBl6NgZVWSm4DZohz8Zw==
3636+
math-intrinsics@^1.0.0, math-intrinsics@^1.1.0:
3637+
version "1.1.0"
3638+
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
3639+
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
3640+
3641+
matrix-appservice-bridge@^10.3.1:
3642+
version "10.3.1"
3643+
resolved "https://registry.yarnpkg.com/matrix-appservice-bridge/-/matrix-appservice-bridge-10.3.1.tgz#8f45b616a69ddd599bc8e4fb9b60c950cce10550"
3644+
integrity sha512-umBLAqLUdm6TefEdQuHUE0QfSmbtJf+LnHDYRM5XwpMjScIXQ/5pI6559gVQrxVVT5T58jbNuoJCz2+8+XLr3Q==
36193645
dependencies:
36203646
"@alloc/quick-lru" "^5.2.0"
36213647
"@types/pkginfo" "^0.4.0"

0 commit comments

Comments
 (0)