forked from mystiq/hydrogen-web
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 {Decryption as OlmDecryption} from "./e2ee/olm/Decryption.js";
|
||||||
import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption.js";
|
import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption.js";
|
||||||
import {Decryption as MegOlmDecryption} from "./e2ee/megolm/Decryption.js";
|
import {Decryption as MegOlmDecryption} from "./e2ee/megolm/Decryption.js";
|
||||||
|
import {RoomEncryption} from "./e2ee/RoomEncryption.js";
|
||||||
import {DeviceTracker} from "./e2ee/DeviceTracker.js";
|
import {DeviceTracker} from "./e2ee/DeviceTracker.js";
|
||||||
import {LockMap} from "../utils/LockMap.js";
|
import {LockMap} from "../utils/LockMap.js";
|
||||||
|
|
||||||
|
@ -56,6 +57,7 @@ export class Session {
|
||||||
ownDeviceId: sessionInfo.deviceId,
|
ownDeviceId: sessionInfo.deviceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this._createRoomEncryption = this._createRoomEncryption.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called once this._e2eeAccount is assigned
|
// called once this._e2eeAccount is assigned
|
||||||
|
@ -85,6 +87,26 @@ export class Session {
|
||||||
this._deviceMessageHandler.enableEncryption({olmDecryption, megolmDecryption});
|
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
|
// called after load
|
||||||
async beforeFirstSync(isNewLogin) {
|
async beforeFirstSync(isNewLogin) {
|
||||||
if (this._olm) {
|
if (this._olm) {
|
||||||
|
@ -202,6 +224,7 @@ export class Session {
|
||||||
sendScheduler: this._sendScheduler,
|
sendScheduler: this._sendScheduler,
|
||||||
pendingEvents,
|
pendingEvents,
|
||||||
user: this._user,
|
user: this._user,
|
||||||
|
createRoomEncryption: this._createRoomEncryption
|
||||||
});
|
});
|
||||||
this._rooms.add(roomId, room);
|
this._rooms.add(roomId, room);
|
||||||
return room;
|
return room;
|
||||||
|
@ -222,12 +245,6 @@ export class Session {
|
||||||
changes.syncInfo = syncInfo;
|
changes.syncInfo = syncInfo;
|
||||||
}
|
}
|
||||||
if (this._deviceTracker) {
|
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;
|
const deviceLists = syncResponse.device_lists;
|
||||||
if (deviceLists) {
|
if (deviceLists) {
|
||||||
await this._deviceTracker.writeDeviceChanges(deviceLists, txn);
|
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);
|
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() {
|
get mediaRepository() {
|
||||||
return this._mediaRepository;
|
return this._mediaRepository;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {MemberList} from "./members/MemberList.js";
|
||||||
import {Heroes} from "./members/Heroes.js";
|
import {Heroes} from "./members/Heroes.js";
|
||||||
|
|
||||||
export class Room extends EventEmitter {
|
export class Room extends EventEmitter {
|
||||||
constructor({roomId, storage, hsApi, emitCollectionChange, sendScheduler, pendingEvents, user}) {
|
constructor({roomId, storage, hsApi, emitCollectionChange, sendScheduler, pendingEvents, user, createRoomEncryption}) {
|
||||||
super();
|
super();
|
||||||
this._roomId = roomId;
|
this._roomId = roomId;
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
|
@ -41,6 +41,8 @@ export class Room extends EventEmitter {
|
||||||
this._user = user;
|
this._user = user;
|
||||||
this._changedMembersDuringSync = null;
|
this._changedMembersDuringSync = null;
|
||||||
this._memberList = null;
|
this._memberList = null;
|
||||||
|
this._createRoomEncryption = createRoomEncryption;
|
||||||
|
this._roomEncryption = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @package */
|
||||||
|
@ -62,6 +64,10 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
heroChanges = await this._heroes.calculateChanges(summaryChanges.heroes, memberChanges, txn);
|
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;
|
let removedPendingEvents;
|
||||||
if (roomResponse.timeline && roomResponse.timeline.events) {
|
if (roomResponse.timeline && roomResponse.timeline.events) {
|
||||||
removedPendingEvents = this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn);
|
removedPendingEvents = this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn);
|
||||||
|
@ -79,6 +85,10 @@ export class Room extends EventEmitter {
|
||||||
/** @package */
|
/** @package */
|
||||||
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges}) {
|
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges}) {
|
||||||
this._syncWriter.afterSync(newLiveKey);
|
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 (memberChanges.size) {
|
||||||
if (this._changedMembersDuringSync) {
|
if (this._changedMembersDuringSync) {
|
||||||
for (const [userId, memberChange] of memberChanges.entries()) {
|
for (const [userId, memberChange] of memberChanges.entries()) {
|
||||||
|
@ -125,6 +135,9 @@ export class Room extends EventEmitter {
|
||||||
async load(summary, txn) {
|
async load(summary, txn) {
|
||||||
try {
|
try {
|
||||||
this._summary.load(summary);
|
this._summary.load(summary);
|
||||||
|
if (this._summary.encryption) {
|
||||||
|
this._roomEncryption = this._createRoomEncryption(this, this._summary.encryption);
|
||||||
|
}
|
||||||
// need to load members for name?
|
// need to load members for name?
|
||||||
if (this._summary.needsHeroes) {
|
if (this._summary.needsHeroes) {
|
||||||
this._heroes = new Heroes(this._roomId);
|
this._heroes = new Heroes(this._roomId);
|
||||||
|
|
|
@ -17,12 +17,7 @@ limitations under the License.
|
||||||
import {SortedArray} from "../../../observable/list/SortedArray.js";
|
import {SortedArray} from "../../../observable/list/SortedArray.js";
|
||||||
import {ConnectionError} from "../../error.js";
|
import {ConnectionError} from "../../error.js";
|
||||||
import {PendingEvent} from "./PendingEvent.js";
|
import {PendingEvent} from "./PendingEvent.js";
|
||||||
|
import {makeTxnId} from "../../common.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SendQueue {
|
export class SendQueue {
|
||||||
constructor({roomId, storage, sendScheduler, pendingEvents}) {
|
constructor({roomId, storage, sendScheduler, pendingEvents}) {
|
||||||
|
|
Loading…
Reference in a new issue