diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index a14b42f3..32e2d00c 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -19,6 +19,22 @@ import {verifyEd25519Signature, SIGNATURE_ALGORITHM} from "./common.js"; const TRACKING_STATUS_OUTDATED = 0; const TRACKING_STATUS_UPTODATE = 1; +export function addRoomToIdentity(identity, userId, roomId) { + if (!identity) { + identity = { + userId: userId, + roomIds: [roomId], + deviceTrackingStatus: TRACKING_STATUS_OUTDATED, + }; + return identity; + } else { + if (!identity.roomIds.includes(roomId)) { + identity.roomIds.push(roomId); + return identity; + } + } +} + // map 1 device from /keys/query response to DeviceIdentity function deviceKeysAsDeviceIdentity(deviceSection) { const deviceId = deviceSection["device_id"]; diff --git a/src/matrix/storage/idb/schema.js b/src/matrix/storage/idb/schema.js index 352c810c..6f02b828 100644 --- a/src/matrix/storage/idb/schema.js +++ b/src/matrix/storage/idb/schema.js @@ -1,8 +1,10 @@ import {iterateCursor, reqAsPromise} from "./utils.js"; import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js"; +import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js"; import {RoomMemberStore} from "./stores/RoomMemberStore.js"; import {SessionStore} from "./stores/SessionStore.js"; import {encodeScopeTypeKey} from "./stores/OperationStore.js"; +import {MAX_UNICODE} from "./stores/common.js"; // FUNCTIONS SHOULD ONLY BE APPENDED!! // the index in the array is the database version @@ -17,6 +19,7 @@ export const schema = [ createArchivedRoomSummaryStore, migrateOperationScopeIndex, createTimelineRelationsStore, + fixMissingRoomsInUserIdentities ]; // TODO: how to deal with git merge conflicts of this array? @@ -142,3 +145,47 @@ async function migrateOperationScopeIndex(db, txn) { function createTimelineRelationsStore(db) { db.createObjectStore("timelineRelations", {keyPath: "key"}); } + +//v11 doesn't change the schema, but ensures all userIdentities have all the roomIds they should (see #470) +async function fixMissingRoomsInUserIdentities(db, txn, log) { + const roomSummaryStore = txn.objectStore("roomSummary"); + const trackedRoomIds = []; + await iterateCursor(roomSummaryStore.openCursor(), roomSummary => { + if (roomSummary.isTrackingMembers) { + trackedRoomIds.push(roomSummary.roomId); + } + }); + const outboundGroupSessionsStore = txn.objectStore("outboundGroupSessions"); + const userIdentitiesStore = txn.objectStore("userIdentities"); + const roomMemberStore = txn.objectStore("roomMembers"); + for (const roomId of trackedRoomIds) { + let foundMissing = false; + const joinedUserIds = []; + const memberRange = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true); + await log.wrap({l: "room", id: roomId}, async log => { + await iterateCursor(roomMemberStore.openCursor(memberRange), member => { + if (member.membership === "join") { + joinedUserIds.push(member.userId); + } + }); + log.set("joinedUserIds", joinedUserIds.length); + for (const userId of joinedUserIds) { + const identity = await reqAsPromise(userIdentitiesStore.get(userId)); + const originalRoomCount = identity?.roomIds?.length; + const updatedIdentity = addRoomToIdentity(identity, userId, roomId); + if (updatedIdentity) { + log.log({l: `fixing up`, id: userId, + roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}); + userIdentitiesStore.put(updatedIdentity); + foundMissing = true; + } + } + log.set("foundMissing", foundMissing); + if (foundMissing) { + // clear outbound megolm session, + // so we'll create a new one on the next message that will be properly shared + outboundGroupSessionsStore.delete(roomId); + } + }); + } +}