-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Implement lab for encrypted state events (MSC3414/MSC4362) #30877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,8 +20,12 @@ import { | |
| Preset, | ||
| RestrictedAllowType, | ||
| Visibility, | ||
| Direction, | ||
| RoomStateEvent, | ||
| type RoomState, | ||
| } from "matrix-js-sdk/src/matrix"; | ||
| import { logger } from "matrix-js-sdk/src/logger"; | ||
| import { type RoomEncryptionEventContent } from "matrix-js-sdk/src/types"; | ||
|
|
||
| import Modal, { type IHandle } from "./Modal"; | ||
| import { _t, UserFriendlyError } from "./languageHandler"; | ||
|
|
@@ -65,6 +69,7 @@ export interface IOpts { | |
| spinner?: boolean; | ||
| guestAccess?: boolean; | ||
| encryption?: boolean; | ||
| stateEncryption?: boolean; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. new public field needs doc-comment, please |
||
| inlineErrors?: boolean; | ||
| andView?: boolean; | ||
| avatar?: File | string; // will upload if given file, else mxcUrl is needed | ||
|
|
@@ -112,6 +117,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro | |
| if (opts.spinner === undefined) opts.spinner = true; | ||
| if (opts.guestAccess === undefined) opts.guestAccess = true; | ||
| if (opts.encryption === undefined) opts.encryption = false; | ||
| if (opts.stateEncryption === undefined) opts.stateEncryption = false; | ||
|
|
||
| if (client.isGuest()) { | ||
| dis.dispatch({ action: "require_registration" }); | ||
|
|
@@ -221,12 +227,16 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro | |
| } | ||
|
|
||
| if (opts.encryption) { | ||
| const content: RoomEncryptionEventContent = { | ||
| algorithm: MEGOLM_ENCRYPTION_ALGORITHM, | ||
| }; | ||
| if (opts.stateEncryption) { | ||
| content["io.element.msc3414.encrypt_state_events"] = true; | ||
| } | ||
| createOpts.initial_state.push({ | ||
| type: "m.room.encryption", | ||
| state_key: "", | ||
| content: { | ||
| algorithm: MEGOLM_ENCRYPTION_ALGORITHM, | ||
| }, | ||
| content, | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -263,24 +273,28 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro | |
| }); | ||
| } | ||
|
|
||
| if (opts.name) { | ||
| createOpts.name = opts.name; | ||
| } | ||
|
|
||
| if (opts.topic) { | ||
| createOpts.topic = opts.topic; | ||
| } | ||
| // If we are not encrypting state, copy name, topic, avatar over to | ||
| // createOpts so we pass them in when we call Client.createRoom(). | ||
| if (!opts.stateEncryption) { | ||
| if (opts.name) { | ||
| createOpts.name = opts.name; | ||
| } | ||
|
|
||
| if (opts.avatar) { | ||
| let url = opts.avatar; | ||
| if (opts.avatar instanceof File) { | ||
| ({ content_uri: url } = await client.uploadContent(opts.avatar)); | ||
| if (opts.topic) { | ||
| createOpts.topic = opts.topic; | ||
| } | ||
|
|
||
| createOpts.initial_state.push({ | ||
| type: EventType.RoomAvatar, | ||
| content: { url }, | ||
| }); | ||
| if (opts.avatar) { | ||
| let url = opts.avatar; | ||
| if (opts.avatar instanceof File) { | ||
| ({ content_uri: url } = await client.uploadContent(opts.avatar)); | ||
| } | ||
|
|
||
| createOpts.initial_state.push({ | ||
| type: EventType.RoomAvatar, | ||
| content: { url }, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if (opts.historyVisibility) { | ||
|
|
@@ -340,6 +354,13 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro | |
|
|
||
| if (opts.dmUserId) await Rooms.setDMRoom(client, roomId, opts.dmUserId); | ||
| }) | ||
| .then(async () => { | ||
| // We need to set up initial state manually if state encryption is enabled, since it needs | ||
| // to be encrypted. | ||
| if (opts.encryption && opts.stateEncryption) { | ||
| await enableStateEventEncryption(client, await room, opts); | ||
| } | ||
| }) | ||
| .then(() => { | ||
| if (opts.parentSpace) { | ||
| return SpaceStore.instance.addRoomToSpace( | ||
|
|
@@ -414,6 +435,49 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro | |
| ); | ||
| } | ||
|
|
||
| async function enableStateEventEncryption(client: MatrixClient, room: Room, opts: IOpts): Promise<void> { | ||
| await new Promise<void>((resolve, reject) => { | ||
| if (room.hasEncryptionStateEvent()) { | ||
| return resolve(); | ||
| } | ||
|
|
||
| const roomState = room.getLiveTimeline().getState(Direction.Forward)!; | ||
|
|
||
| // Soft fail, since the room will still be functional if the initial state is not encrypted. | ||
| const timeout = setTimeout(() => { | ||
| logger.warn("Timed out while waiting for room to enable encryption"); | ||
| roomState.off(RoomStateEvent.Update, onRoomStateUpdate); | ||
| resolve(); | ||
| }, 3000); | ||
|
|
||
| const onRoomStateUpdate = (state: RoomState): void => { | ||
| if (state.getStateEvents(EventType.RoomEncryption, "")) { | ||
| roomState.off(RoomStateEvent.Update, onRoomStateUpdate); | ||
| clearTimeout(timeout); | ||
| resolve(); | ||
| } | ||
| }; | ||
|
|
||
| roomState.on(RoomStateEvent.Update, onRoomStateUpdate); | ||
| }); | ||
|
|
||
| // Set room name | ||
| if (opts.name) { | ||
| await client.setRoomName(room.roomId, opts.name); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to do topic here too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (and we need a test for this) |
||
|
|
||
| // Set room avatar | ||
| if (opts.avatar) { | ||
| let url: string; | ||
| if (opts.avatar instanceof File) { | ||
| ({ content_uri: url } = await client.uploadContent(opts.avatar)); | ||
| } else { | ||
| url = opts.avatar; | ||
| } | ||
| await client.sendStateEvent(room.roomId, EventType.RoomAvatar, { url }, ""); | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * Ensure that for every user in a room, there is at least one device that we | ||
| * can encrypt to. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| Copyright 2024 New Vector Ltd. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to update to reflect company name change |
||
| SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import PlatformPeg from "../../PlatformPeg"; | ||
| import { SettingLevel } from "../SettingLevel"; | ||
| import SettingsStore from "../SettingsStore"; | ||
| import SettingController from "./SettingController"; | ||
|
|
||
| export default class EncryptedStateEventsController extends SettingController { | ||
| public onChange(level: SettingLevel, roomId: string | null, newValue: boolean): void { | ||
| SettingsStore.setValue("feature_share_history_on_invite", null, SettingLevel.CONFIG, newValue); | ||
| PlatformPeg.get()?.reload(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we replace 3414 with 4362 everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, no because MSC4362 says to use prefix
io.element.msc3414.encrypt_state_eventsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we could rename the feature flag, though that might get confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do kinda think that we should rename the feature flag, and change the comments in the rust-sdk. We're implementing the proposals of MSC4362, not MSC3414.