Change getPublicUrlForMxc function for authmedia

This commit is contained in:
Cadence Ember 2024-09-12 16:59:48 +12:00
parent 96fd046530
commit decc32f7e6
6 changed files with 39 additions and 14 deletions

View file

@ -11,6 +11,8 @@ const {discord, sync, db, select} = require("../passthrough")
const api = sync.require("../matrix/api") const api = sync.require("../matrix/api")
/** @type {import("../matrix/file")} */ /** @type {import("../matrix/file")} */
const file = sync.require("../matrix/file") const file = sync.require("../matrix/file")
/** @type {import("../m2d/converters/utils")} */
const mxUtils = sync.require("../matrix/utils")
/** @type {import("../d2m/actions/create-space")} */ /** @type {import("../d2m/actions/create-space")} */
const createSpace = sync.require("../d2m/actions/create-space") const createSpace = sync.require("../d2m/actions/create-space")
/** @type {import("./utils")} */ /** @type {import("./utils")} */
@ -91,9 +93,8 @@ const commands = [{
// Current avatar // Current avatar
const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "") const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "")
const avatarURLParts = avatarEvent?.url.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
let currentAvatarMessage = let currentAvatarMessage =
( avatarURLParts ? `Current room-specific avatar: ${reg.ooye.server_origin}/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}` ( avatarEvent.url ? `Current room-specific avatar: ${mxUtils.getPublicUrlForMxc(avatarEvent.url)}`
: "No avatar. Now's your time to strike. Use `//icon` again with a link or upload to set the room-specific avatar.") : "No avatar. Now's your time to strike. Use `//icon` again with a link or upload to set the room-specific avatar.")
// Next potential avatar // Next potential avatar

View file

@ -15,6 +15,8 @@ const {sync, db, discord, select, from} = passthrough
const mxUtils = sync.require("../converters/utils") const mxUtils = sync.require("../converters/utils")
/** @type {import("../../discord/utils")} */ /** @type {import("../../discord/utils")} */
const dUtils = sync.require("../../discord/utils") const dUtils = sync.require("../../discord/utils")
/** @type {import("../../matrix/file")} */
const file = sync.require("../../matrix/file")
/** @type {import("./emoji-sheet")} */ /** @type {import("./emoji-sheet")} */
const emojiSheet = sync.require("./emoji-sheet") const emojiSheet = sync.require("./emoji-sheet")

View file

@ -1,8 +1,13 @@
// @ts-check // @ts-check
const assert = require("assert").strict
const passthrough = require("../../passthrough")
const {db} = passthrough
const {reg} = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
const assert = require("assert").strict
/** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore /** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore
let hasher = null let hasher = null
// @ts-ignore // @ts-ignore
@ -35,16 +40,6 @@ function eventSenderIsFromDiscord(sender) {
return false return false
} }
/**
* @param {string} mxc
* @returns {string?}
*/
function getPublicUrlForMxc(mxc) {
const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
if (avatarURLParts) return `${reg.ooye.server_origin}/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}`
else return null
}
/** /**
* Event IDs are really big and have more entropy than we need. * Event IDs are really big and have more entropy than we need.
* If we want to store the event ID in the database, we can store a more compact version by hashing it with this. * If we want to store the event ID in the database, we can store a more compact version by hashing it with this.
@ -213,6 +208,32 @@ async function getViaServersQuery(roomID, api) {
return qs return qs
} }
/**
* Since the introduction of authenticated media, this can no longer just be the /_matrix/media/r0/download URL
* because Discord and Discord users cannot use those URLs. Media now has to be proxied through the bridge.
* To avoid the bridge acting as a proxy for *any* media, there is a list of permitted media stored in the database.
* (The other approach would be signing the URLs with a MAC (or similar) and adding the signature, but I'm not a
* cryptographer, so I don't want to.) To reduce database disk space usage, instead of storing each permitted URL,
* we just store its xxhash as a signed (as in +/-, not signature) 64-bit integer, which fits in an SQLite integer field.
* @see https://matrix.org/blog/2024/06/26/sunsetting-unauthenticated-media/ background
* @see https://matrix.org/blog/2024/06/20/matrix-v1.11-release/ implementation details
* @see https://www.sqlite.org/fileformat2.html#record_format SQLite integer field size
* @param {string} mxc
* @returns {string?}
*/
function getPublicUrlForMxc(mxc) {
assert(hasher, "xxhash is not ready yet")
const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
if (!avatarURLParts) return null
const serverAndMediaID = `${avatarURLParts[1]}/${avatarURLParts[2]}`
const unsignedHash = hasher.h64(serverAndMediaID)
const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range
db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash)
return `${reg.ooye.bridge_origin}/download/matrix/${serverAndMediaID}`
}
module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
module.exports.getPublicUrlForMxc = getPublicUrlForMxc module.exports.getPublicUrlForMxc = getPublicUrlForMxc

View file

@ -32,7 +32,6 @@ if (process.stdin.isTTY) {
} else { } else {
Object.assign(passthrough.repl.context, extraContext) Object.assign(passthrough.repl.context, extraContext)
} }
// @ts-expect-error Says exit isn't assignable to a string
sync.addTemporaryListener(passthrough.repl, "exit", () => process.exit()) sync.addTemporaryListener(passthrough.repl, "exit", () => process.exit())
}) })
} }

View file

@ -23,6 +23,7 @@ reg.ooye.server_name = "cadence.moe"
reg.id = "baby" // don't actually take authenticated actions on the server reg.id = "baby" // don't actually take authenticated actions on the server
reg.as_token = "baby" reg.as_token = "baby"
reg.hs_token = "baby" reg.hs_token = "baby"
reg.ooye.bridge_origin = "https://bridge.example.org"
reg.ooye.invite = [] reg.ooye.invite = []
const sync = new HeatSync({watchFS: false}) const sync = new HeatSync({watchFS: false})

1
types.d.ts vendored
View file

@ -21,6 +21,7 @@ export type AppServiceRegistrationConfig = {
max_file_size: number max_file_size: number
server_name: string server_name: string
server_origin: string server_origin: string
bridge_origin: string
content_length_workaround: boolean content_length_workaround: boolean
include_user_id_in_mxid: boolean include_user_id_in_mxid: boolean
invite: string[] invite: string[]