Provide a labs flag for encrypted state events (MSC3414) (#31513)

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
Co-authored-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
Andy Balaam
2025-12-18 14:45:52 +00:00
committed by GitHub
co-authored by Skye Elliot
parent 6f0369e623
commit ff3f069122
14 changed files with 858 additions and 26 deletions
@@ -40,6 +40,7 @@ interface IProps {
defaultName?: string;
parentSpace?: Room;
defaultEncrypted?: boolean;
defaultStateEncrypted?: boolean;
onFinished(proceed?: false): void;
onFinished(proceed: true, opts: IOpts): void;
}
@@ -58,6 +59,11 @@ interface IState {
* Indicates whether end-to-end encryption is enabled for the room.
*/
isEncrypted: boolean;
/**
* Indicates whether end-to-end state encryption is enabled for this room.
* See MSC4362. Available if feature_msc4362_encrypted_state_events is enabled.
*/
isStateEncrypted: boolean;
/**
* The room name.
*/
@@ -117,6 +123,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
this.state = {
isPublicKnockRoom: defaultPublic || false,
isEncrypted: this.props.defaultEncrypted ?? privateShouldBeEncrypted(cli),
isStateEncrypted: this.props.defaultStateEncrypted ?? false,
joinRule,
name: this.props.defaultName || "",
topic: "",
@@ -141,7 +148,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
const { alias } = this.state;
createOpts.room_alias_name = alias.substring(1, alias.indexOf(":"));
} else {
const encryptedStateFeature = SettingsStore.getValue("feature_msc4362_encrypted_state_events", null, false);
opts.encryption = this.state.isEncrypted;
opts.stateEncryption = encryptedStateFeature && this.state.isStateEncrypted;
}
if (this.state.topic) {
@@ -236,6 +246,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
this.setState({ isEncrypted: evt.target.checked });
};
private onStateEncryptedChange: ChangeEventHandler<HTMLInputElement> = (evt): void => {
this.setState({ isStateEncrypted: evt.target.checked });
};
private onAliasChange = (alias: string): void => {
this.setState({ alias });
};
@@ -378,6 +392,29 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
);
}
let e2eeStateSection: JSX.Element | undefined;
if (
SettingsStore.getValue("feature_msc4362_encrypted_state_events", null, false) &&
this.state.joinRule !== JoinRule.Public
) {
let microcopy: string;
if (!this.state.canChangeEncryption) {
microcopy = _t("create_room|encryption_forced");
} else {
microcopy = _t("create_room|state_encrypted_warning");
}
e2eeStateSection = (
<SettingsToggleInput
name="state-encryption-toggle"
label={_t("create_room|state_encryption_label")}
onChange={this.onStateEncryptedChange}
checked={this.state.isStateEncrypted}
disabled={!this.state.canChangeEncryption}
helpMessage={microcopy}
/>
);
}
let federateLabel = _t("create_room|unfederated_label_default_off");
if (SdkConfig.get().default_federate === false) {
// We only change the label if the default setting is different to avoid jarring text changes to the
@@ -441,6 +478,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
{visibilitySection}
{e2eeSection}
{e2eeStateSection}
{aliasField}
{this.advancedSettingsEnabled && (
<details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details">
@@ -40,6 +40,9 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
let subtitle: string;
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
const room = cli?.getRoom(roomId);
const stateEncrypted = content["io.element.msc4362.encrypt_state_events"] && cli.enableEncryptedStateEvents;
if (prevContent.algorithm === MEGOLM_ENCRYPTION_ALGORITHM) {
subtitle = _t("timeline|m.room.encryption|parameters_changed");
} else if (dmPartner) {
@@ -47,6 +50,8 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
subtitle = _t("timeline|m.room.encryption|enabled_dm", { displayName });
} else if (room && isLocalRoom(room)) {
subtitle = _t("timeline|m.room.encryption|enabled_local");
} else if (stateEncrypted) {
subtitle = _t("timeline|m.room.encryption|state_enabled");
} else {
subtitle = _t("timeline|m.room.encryption|enabled");
}
@@ -54,7 +59,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return (
<EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("common|encryption_enabled")}
title={stateEncrypted ? _t("common|state_encryption_enabled") : _t("common|encryption_enabled")}
subtitle={subtitle}
timestamp={timestamp}
/>
@@ -54,6 +54,7 @@ interface IState {
history: HistoryVisibility;
hasAliases: boolean;
encrypted: boolean | null;
stateEncrypted: boolean | null;
showAdvancedSection: boolean;
}
@@ -79,6 +80,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
),
hasAliases: false, // async loaded in componentDidMount
encrypted: null, // async loaded in componentDidMount
stateEncrypted: null, // async loaded in componentDidMount
showAdvancedSection: false,
};
}
@@ -89,6 +91,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
this.setState({
hasAliases: await this.hasAliases(),
encrypted: Boolean(await this.context.getCrypto()?.isEncryptionEnabledInRoom(this.props.room.roomId)),
stateEncrypted: Boolean(
await this.context.getCrypto()?.isStateEncryptionEnabledInRoom(this.props.room.roomId),
),
});
}
@@ -480,6 +485,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
const client = this.context;
const room = this.props.room;
const isEncrypted = this.state.encrypted;
const isStateEncrypted = this.state.stateEncrypted;
const isEncryptionLoading = isEncrypted === null;
const hasEncryptionPermission = room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client);
const isEncryptionForceDisabled = shouldForceDisableEncryption(client);
@@ -533,6 +539,14 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{isEncryptionForceDisabled && !isEncrypted && (
<Caption>{_t("room_settings|security|encryption_forced")}</Caption>
)}
{isStateEncrypted && (
<SettingsToggleInput
name="enable-state-encryption"
checked={isStateEncrypted}
label={_t("common|state_encryption_enabled")}
disabled={true}
/>
)}
{encryptionSettings}
</>
)}