add RoomEncryption to room
This commit is contained in:
parent
2a40c89a24
commit
5cafef96f5
6 changed files with 124 additions and 13 deletions
|
@ -23,6 +23,7 @@ import {DeviceMessageHandler} from "./DeviceMessageHandler.js";
|
|||
import {Decryption as OlmDecryption} from "./e2ee/olm/Decryption.js";
|
||||
import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption.js";
|
||||
import {Decryption as MegOlmDecryption} from "./e2ee/megolm/Decryption.js";
|
||||
import {RoomEncryption} from "./e2ee/RoomEncryption.js";
|
||||
import {DeviceTracker} from "./e2ee/DeviceTracker.js";
|
||||
import {LockMap} from "../utils/LockMap.js";
|
||||
|
||||
|
@ -56,6 +57,7 @@ export class Session {
|
|||
ownDeviceId: sessionInfo.deviceId,
|
||||
});
|
||||
}
|
||||
this._createRoomEncryption = this._createRoomEncryption.bind(this);
|
||||
}
|
||||
|
||||
// called once this._e2eeAccount is assigned
|
||||
|
@ -85,6 +87,26 @@ export class Session {
|
|||
this._deviceMessageHandler.enableEncryption({olmDecryption, megolmDecryption});
|
||||
}
|
||||
|
||||
_createRoomEncryption(room, encryptionEventContent) {
|
||||
// TODO: this will actually happen when users start using the e2ee version for the first time
|
||||
|
||||
// this should never happen because either a session was already synced once
|
||||
// and thus an e2ee account was created as well and _setupEncryption is called from load
|
||||
// OR
|
||||
// this is a new session and loading it will load zero rooms, thus not calling this method.
|
||||
// in this case _setupEncryption is called from beforeFirstSync, right after load,
|
||||
// so any incoming synced rooms won't be there yet
|
||||
if (!this._olmEncryption) {
|
||||
throw new Error("creating room encryption before encryption got globally enabled");
|
||||
}
|
||||
return new RoomEncryption({
|
||||
room,
|
||||
deviceTracker: this._deviceTracker,
|
||||
olmEncryption: this._olmEncryption,
|
||||
encryptionEventContent
|
||||
});
|
||||
}
|
||||
|
||||
// called after load
|
||||
async beforeFirstSync(isNewLogin) {
|
||||
if (this._olm) {
|
||||
|
@ -202,6 +224,7 @@ export class Session {
|
|||
sendScheduler: this._sendScheduler,
|
||||
pendingEvents,
|
||||
user: this._user,
|
||||
createRoomEncryption: this._createRoomEncryption
|
||||
});
|
||||
this._rooms.add(roomId, room);
|
||||
return room;
|
||||
|
@ -222,12 +245,6 @@ export class Session {
|
|||
changes.syncInfo = syncInfo;
|
||||
}
|
||||
if (this._deviceTracker) {
|
||||
for (const {room, changes} of roomChanges) {
|
||||
// TODO: move this so the room passes this to it's "encryption" object in its own writeSync method?
|
||||
if (room.isTrackingMembers && changes.memberChanges?.size) {
|
||||
await this._deviceTracker.writeMemberChanges(room, changes.memberChanges, txn);
|
||||
}
|
||||
}
|
||||
const deviceLists = syncResponse.device_lists;
|
||||
if (deviceLists) {
|
||||
await this._deviceTracker.writeDeviceChanges(deviceLists, txn);
|
||||
|
|
22
src/matrix/common.js
Normal file
22
src/matrix/common.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function makeTxnId() {
|
||||
const n = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
const str = n.toString(16);
|
||||
return "t" + "0".repeat(14 - str.length) + str;
|
||||
}
|
60
src/matrix/e2ee/RoomEncryption.js
Normal file
60
src/matrix/e2ee/RoomEncryption.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {groupBy} from "../../utils/groupBy.js";
|
||||
import {makeTxnId} from "../common.js";
|
||||
|
||||
|
||||
export class RoomEncryption {
|
||||
constructor({room, deviceTracker, olmEncryption, encryptionEventContent}) {
|
||||
this._room = room;
|
||||
this._deviceTracker = deviceTracker;
|
||||
this._olmEncryption = olmEncryption;
|
||||
// content of the m.room.encryption event
|
||||
this._encryptionEventContent = encryptionEventContent;
|
||||
}
|
||||
|
||||
async writeMemberChanges(memberChanges, txn) {
|
||||
return await this._deviceTracker.writeMemberChanges(this._room, memberChanges, txn);
|
||||
}
|
||||
|
||||
async encrypt(type, content, hsApi) {
|
||||
await this._deviceTracker.trackRoom(this._room);
|
||||
const devices = await this._deviceTracker.deviceIdentitiesForTrackedRoom(this._room.id, hsApi);
|
||||
const messages = await this._olmEncryption.encrypt("m.foo", {body: "hello at " + new Date()}, devices, hsApi);
|
||||
await this._sendMessagesToDevices("m.room.encrypted", messages, hsApi);
|
||||
return {type, content};
|
||||
// return {
|
||||
// type: "m.room.encrypted",
|
||||
// content: encryptedContent,
|
||||
// }
|
||||
}
|
||||
|
||||
async _sendMessagesToDevices(type, messages, hsApi) {
|
||||
const messagesByUser = groupBy(messages, message => message.device.userId);
|
||||
const payload = {
|
||||
messages: Array.from(messagesByUser.entries()).reduce((userMap, [userId, messages]) => {
|
||||
userMap[userId] = messages.reduce((deviceMap, message) => {
|
||||
deviceMap[message.device.deviceId] = message.content;
|
||||
return deviceMap;
|
||||
}, {});
|
||||
return userMap;
|
||||
}, {})
|
||||
};
|
||||
const txnId = makeTxnId();
|
||||
await hsApi.sendToDevice(type, payload, txnId).response();
|
||||
}
|
||||
}
|
|
@ -172,6 +172,10 @@ export class HomeServerApi {
|
|||
return this._post("/keys/claim", null, payload, options);
|
||||
}
|
||||
|
||||
sendToDevice(type, payload, txnId, options = null) {
|
||||
return this._put(`/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, null, payload, options);
|
||||
}
|
||||
|
||||
get mediaRepository() {
|
||||
return this._mediaRepository;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import {MemberList} from "./members/MemberList.js";
|
|||
import {Heroes} from "./members/Heroes.js";
|
||||
|
||||
export class Room extends EventEmitter {
|
||||
constructor({roomId, storage, hsApi, emitCollectionChange, sendScheduler, pendingEvents, user}) {
|
||||
constructor({roomId, storage, hsApi, emitCollectionChange, sendScheduler, pendingEvents, user, createRoomEncryption}) {
|
||||
super();
|
||||
this._roomId = roomId;
|
||||
this._storage = storage;
|
||||
|
@ -41,6 +41,8 @@ export class Room extends EventEmitter {
|
|||
this._user = user;
|
||||
this._changedMembersDuringSync = null;
|
||||
this._memberList = null;
|
||||
this._createRoomEncryption = createRoomEncryption;
|
||||
this._roomEncryption = null;
|
||||
}
|
||||
|
||||
/** @package */
|
||||
|
@ -62,6 +64,10 @@ export class Room extends EventEmitter {
|
|||
}
|
||||
heroChanges = await this._heroes.calculateChanges(summaryChanges.heroes, memberChanges, txn);
|
||||
}
|
||||
// pass member changes to device tracker
|
||||
if (this._roomEncryption && this.isTrackingMembers && memberChanges?.size) {
|
||||
await this._roomEncryption.writeMemberChanges(memberChanges, txn);
|
||||
}
|
||||
let removedPendingEvents;
|
||||
if (roomResponse.timeline && roomResponse.timeline.events) {
|
||||
removedPendingEvents = this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn);
|
||||
|
@ -79,6 +85,10 @@ export class Room extends EventEmitter {
|
|||
/** @package */
|
||||
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges}) {
|
||||
this._syncWriter.afterSync(newLiveKey);
|
||||
// encryption got enabled
|
||||
if (!this._summary.encryption && summaryChanges.encryption && !this._roomEncryption) {
|
||||
this._roomEncryption = this._createRoomEncryption(this, summaryChanges.encryption);
|
||||
}
|
||||
if (memberChanges.size) {
|
||||
if (this._changedMembersDuringSync) {
|
||||
for (const [userId, memberChange] of memberChanges.entries()) {
|
||||
|
@ -125,6 +135,9 @@ export class Room extends EventEmitter {
|
|||
async load(summary, txn) {
|
||||
try {
|
||||
this._summary.load(summary);
|
||||
if (this._summary.encryption) {
|
||||
this._roomEncryption = this._createRoomEncryption(this, this._summary.encryption);
|
||||
}
|
||||
// need to load members for name?
|
||||
if (this._summary.needsHeroes) {
|
||||
this._heroes = new Heroes(this._roomId);
|
||||
|
|
|
@ -17,12 +17,7 @@ limitations under the License.
|
|||
import {SortedArray} from "../../../observable/list/SortedArray.js";
|
||||
import {ConnectionError} from "../../error.js";
|
||||
import {PendingEvent} from "./PendingEvent.js";
|
||||
|
||||
function makeTxnId() {
|
||||
const n = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
const str = n.toString(16);
|
||||
return "t" + "0".repeat(14 - str.length) + str;
|
||||
}
|
||||
import {makeTxnId} from "../../common.js";
|
||||
|
||||
export class SendQueue {
|
||||
constructor({roomId, storage, sendScheduler, pendingEvents}) {
|
||||
|
|
Reference in a new issue