Refuse to operate on encrypted rooms
- Refuse to link to encrypted rooms - Do not show encrypted rooms as link candidates (if server supports) - Reject invites to encrypted rooms with message - Unbridge and leave room if it becomes encrypted
This commit is contained in:
@@ -442,8 +442,9 @@ function syncRoom(channelID) {
|
||||
/**
|
||||
* @param {{id: string, topic?: string?}} channel channel-ish (just needs an id, topic is optional)
|
||||
* @param {string} guildID
|
||||
* @param {string} messageBeforeLeave
|
||||
*/
|
||||
async function unbridgeChannel(channel, guildID) {
|
||||
async function unbridgeChannel(channel, guildID, messageBeforeLeave = "This room was removed from the bridge.") {
|
||||
const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get()
|
||||
assert.ok(roomID)
|
||||
const row = from("guild_space").join("guild_active", "guild_id").select("space_id", "autocreate").where({guild_id: guildID}).get()
|
||||
@@ -493,7 +494,7 @@ async function unbridgeChannel(channel, guildID) {
|
||||
// send a notification in the room
|
||||
await api.sendEvent(roomID, "m.room.message", {
|
||||
msgtype: "m.notice",
|
||||
body: "⚠️ This room was removed from the bridge."
|
||||
body: `⚠️ ${messageBeforeLeave}`
|
||||
})
|
||||
|
||||
// if it is an easy mode room, clean up the room from the managed space and make it clear it's not being bridged
|
||||
|
||||
@@ -413,6 +413,7 @@ async event => {
|
||||
console.error(e)
|
||||
return await api.leaveRoomWithReason(event.room_id, `I wasn't able to find out what this room is. Please report this as a bug. Check console for more details. (${e.toString()})`)
|
||||
}
|
||||
if (inviteRoomState?.encryption) return await api.leaveRoomWithReason(event.room_id, "Encrypted rooms are not supported for bridging. Please use an unencrypted room.")
|
||||
if (!inviteRoomState?.name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite.`)
|
||||
await api.joinRoom(event.room_id)
|
||||
db.prepare("REPLACE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, inviteRoomState.type, inviteRoomState.name, inviteRoomState.topic, inviteRoomState.avatar)
|
||||
@@ -483,6 +484,20 @@ async event => {
|
||||
await roomUpgrade.onTombstone(event, api)
|
||||
}))
|
||||
|
||||
sync.addTemporaryListener(as, "type:m.room.encryption", guard("m.room.encryption",
|
||||
/**
|
||||
* @param {Ty.Event.StateOuter<Ty.Event.M_Room_Encryption>} event
|
||||
*/
|
||||
async event => {
|
||||
// Dramatically unbridge rooms if they become encrypted
|
||||
if (event.state_key !== "") return
|
||||
const channelID = select("channel_room", "channel_id", {room_id: event.room_id}).pluck().get()
|
||||
if (!channelID) return
|
||||
const channel = discord.channels.get(channelID)
|
||||
if (!channel) return
|
||||
await createRoom.unbridgeChannel(channel, channel["guild_id"], "Encrypted rooms are not supported. This room was removed from the bridge.")
|
||||
}))
|
||||
|
||||
module.exports.stringifyErrorStack = stringifyErrorStack
|
||||
module.exports.sendError = sendError
|
||||
module.exports.printError = printError
|
||||
|
||||
@@ -172,7 +172,7 @@ function getStateEventOuter(roomID, type, key) {
|
||||
/**
|
||||
* @param {string} roomID
|
||||
* @param {{unsigned?: {invite_room_state?: Ty.Event.InviteStrippedState[]}}} [event]
|
||||
* @returns {Promise<{name: string?, topic: string?, avatar: string?, type: string?}>}
|
||||
* @returns {Promise<{name: string?, topic: string?, avatar: string?, type: string?, encryption: string?}>}
|
||||
*/
|
||||
async function getInviteState(roomID, event) {
|
||||
function getFromInviteRoomState(strippedState, nskey, key) {
|
||||
@@ -191,7 +191,8 @@ async function getInviteState(roomID, event) {
|
||||
name: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.name", "name"),
|
||||
topic: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.topic", "topic"),
|
||||
avatar: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.avatar", "url"),
|
||||
type: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.create", "type")
|
||||
type: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.create", "type"),
|
||||
encryption: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.encryption", "algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +228,8 @@ async function getInviteState(roomID, event) {
|
||||
name: getFromInviteRoomState(strippedState, "m.room.name", "name"),
|
||||
topic: getFromInviteRoomState(strippedState, "m.room.topic", "topic"),
|
||||
avatar: getFromInviteRoomState(strippedState, "m.room.avatar", "url"),
|
||||
type: getFromInviteRoomState(strippedState, "m.room.create", "type")
|
||||
type: getFromInviteRoomState(strippedState, "m.room.create", "type"),
|
||||
encryption: getFromInviteRoomState(strippedState, "m.room.encryption", "algorithm")
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
@@ -240,7 +242,8 @@ async function getInviteState(roomID, event) {
|
||||
name: room.name ?? null,
|
||||
topic: room.topic ?? null,
|
||||
avatar: room.avatar_url ?? null,
|
||||
type: room.room_type ?? null
|
||||
type: room.room_type ?? null,
|
||||
encryption: (room.encryption || room["im.nheko.summary.encryption"]) ?? null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
src/types.d.ts
vendored
9
src/types.d.ts
vendored
@@ -157,7 +157,7 @@ export namespace Event {
|
||||
type: string
|
||||
state_key: string
|
||||
sender: string
|
||||
content: Event.M_Room_Create | Event.M_Room_Name | Event.M_Room_Avatar | Event.M_Room_Topic | Event.M_Room_JoinRules | Event.M_Room_CanonicalAlias
|
||||
content: Event.M_Room_Create | Event.M_Room_Name | Event.M_Room_Avatar | Event.M_Room_Topic | Event.M_Room_JoinRules | Event.M_Room_CanonicalAlias | Event.M_Room_Encryption
|
||||
}
|
||||
|
||||
export type M_Room_Create = {
|
||||
@@ -390,6 +390,12 @@ export namespace Event {
|
||||
body: string
|
||||
replacement_room: string
|
||||
}
|
||||
|
||||
export type M_Room_Encryption = {
|
||||
algorithm: string
|
||||
rotation_period_ms?: number
|
||||
rotation_period_msgs?: number
|
||||
}
|
||||
}
|
||||
|
||||
export namespace R {
|
||||
@@ -437,6 +443,7 @@ export namespace R {
|
||||
num_joined_members: number
|
||||
room_id: string
|
||||
room_type?: string
|
||||
encryption?: string
|
||||
}
|
||||
|
||||
export type ResolvedRoom = {
|
||||
|
||||
@@ -249,6 +249,11 @@ block body
|
||||
ul.my8.ml24
|
||||
each row in removedLinkedRooms
|
||||
li: a(href=`https://matrix.to/#/${row.room_id}`)= row.name
|
||||
h3.mt24 Unavailable rooms: Encryption not supported
|
||||
.s-card.p0
|
||||
ul.my8.ml24
|
||||
each row in removedEncryptedRooms
|
||||
li: a(href=`https://matrix.to/#/${row.room_id}`)= row.name
|
||||
h3.mt24 Unavailable rooms: Wrong type
|
||||
.s-card.p0
|
||||
ul.my8.ml24
|
||||
|
||||
@@ -123,13 +123,14 @@ function getChannelRoomsLinks(guild, rooms, roles) {
|
||||
let unlinkedRooms = [...rooms]
|
||||
let removedLinkedRooms = dUtils.filterTo(unlinkedRooms, r => !linkedRoomIDs.includes(r.room_id))
|
||||
let removedWrongTypeRooms = dUtils.filterTo(unlinkedRooms, r => !r.room_type)
|
||||
let removedEncryptedRooms = dUtils.filterTo(unlinkedRooms, r => !r.encryption && !r["im.nheko.summary.encryption"])
|
||||
// https://discord.com/developers/docs/topics/threads#active-archived-threads
|
||||
// need to filter out linked archived threads from unlinkedRooms, will just do that by comparing against the name
|
||||
let removedArchivedThreadRooms = dUtils.filterTo(unlinkedRooms, r => r.name && !r.name.match(/^\[(🔒)?⛓️\]/))
|
||||
|
||||
return {
|
||||
linkedChannelsWithDetails, unlinkedChannels, unlinkedRooms,
|
||||
removedUncachedChannels, removedWrongTypeChannels, removedPrivateChannels, removedLinkedRooms, removedWrongTypeRooms, removedArchivedThreadRooms
|
||||
removedUncachedChannels, removedWrongTypeChannels, removedPrivateChannels, removedLinkedRooms, removedWrongTypeRooms, removedArchivedThreadRooms, removedEncryptedRooms
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +204,12 @@ as.router.post("/api/link", defineEventHandler(async event => {
|
||||
throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`})
|
||||
}
|
||||
|
||||
// Check room is not encrypted
|
||||
const encryption = await api.getStateEvent(parsedBody.matrix, "m.room.encryption", "").catch(() => null)
|
||||
if (encryption) {
|
||||
throw createError({status: 400, message: "Bad Request", data: "Encrypted rooms are not supported for bridging. Please replace it with an unencrypted room."})
|
||||
}
|
||||
|
||||
// Check bridge has PL 100
|
||||
const {powerLevels, powers: {[utils.bot]: selfPowerLevel}} = await utils.getEffectivePower(parsedBody.matrix, [utils.bot], api)
|
||||
if (selfPowerLevel < (powerLevels?.state_default ?? 50) || selfPowerLevel < 100) throw createError({status: 400, message: "Bad Request", data: "OOYE needs power level 100 (admin) in the target Matrix room"})
|
||||
|
||||
@@ -435,6 +435,47 @@ test("web link room: check that bridge can join room (uses via for join attempt)
|
||||
t.equal(called, 2)
|
||||
})
|
||||
|
||||
test("web link room: check that room is not encrypted", async t => {
|
||||
let called = 0
|
||||
const [error] = await tryToCatch(() => router.test("post", "/api/link", {
|
||||
sessionData: {
|
||||
managedGuilds: ["665289423482519565"]
|
||||
},
|
||||
body: {
|
||||
discord: "665310973967597573",
|
||||
matrix: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
guild_id: "665289423482519565"
|
||||
},
|
||||
api: {
|
||||
async joinRoom(roomID) {
|
||||
called++
|
||||
return roomID
|
||||
},
|
||||
async *generateFullHierarchy(spaceID) {
|
||||
called++
|
||||
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
|
||||
yield {
|
||||
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
children_state: [],
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
/* c8 ignore next */
|
||||
},
|
||||
async getStateEvent(roomID, type, key) {
|
||||
called++
|
||||
t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe")
|
||||
if (type === "m.room.encryption" && key === "") {
|
||||
return {algorithm: "m.megolm.v1.aes-sha2"}
|
||||
}
|
||||
throw new Error("Unknown state event")
|
||||
}
|
||||
}
|
||||
}))
|
||||
t.equal(error.data, "Encrypted rooms are not supported for bridging. Please replace it with an unencrypted room.")
|
||||
t.equal(called, 3)
|
||||
})
|
||||
|
||||
test("web link room: check that bridge has PL 100 in target room", async t => {
|
||||
let called = 0
|
||||
const [error] = await tryToCatch(() => router.test("post", "/api/link", {
|
||||
@@ -465,9 +506,10 @@ test("web link room: check that bridge has PL 100 in target room", async t => {
|
||||
async getStateEvent(roomID, type, key) {
|
||||
called++
|
||||
t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe")
|
||||
t.equal(type, "m.room.power_levels")
|
||||
t.equal(key, "")
|
||||
return {users_default: 50}
|
||||
if (type === "m.room.power_levels" && key === "") {
|
||||
return {users_default: 50}
|
||||
}
|
||||
throw new Error("Unknown state event")
|
||||
},
|
||||
async getStateEventOuter(roomID, type, key) {
|
||||
called++
|
||||
@@ -489,7 +531,7 @@ test("web link room: check that bridge has PL 100 in target room", async t => {
|
||||
}
|
||||
}))
|
||||
t.equal(error.data, "OOYE needs power level 100 (admin) in the target Matrix room")
|
||||
t.equal(called, 4)
|
||||
t.equal(called, 5)
|
||||
})
|
||||
|
||||
test("web link room: successfully calls createRoom", async t => {
|
||||
|
||||
Reference in New Issue
Block a user