remove devices not present in /keys/query response

This commit is contained in:
Bruno Windels 2020-09-14 15:44:47 +02:00
parent 504371eaf3
commit 3325f12092
2 changed files with 68 additions and 23 deletions

View File

@ -149,36 +149,17 @@ export class DeviceTracker {
}).response(); }).response();
const verifiedKeysPerUser = this._filterVerifiedDeviceKeys(deviceKeyResponse["device_keys"]); const verifiedKeysPerUser = this._filterVerifiedDeviceKeys(deviceKeyResponse["device_keys"]);
const flattenedVerifiedKeysPerUser = verifiedKeysPerUser.reduce((all, {verifiedKeys}) => all.concat(verifiedKeys), []);
const deviceIdentitiesWithPossibleChangedKeys = flattenedVerifiedKeysPerUser.map(deviceKeysAsDeviceIdentity);
const txn = await this._storage.readWriteTxn([ const txn = await this._storage.readWriteTxn([
this._storage.storeNames.userIdentities, this._storage.storeNames.userIdentities,
this._storage.storeNames.deviceIdentities, this._storage.storeNames.deviceIdentities,
]); ]);
let deviceIdentities; let deviceIdentities;
try { try {
// check ed25519 key has not changed if we've seen the device before const devicesIdentitiesPerUser = await Promise.all(verifiedKeysPerUser.map(async ({userId, verifiedKeys}) => {
deviceIdentities = await Promise.all(deviceIdentitiesWithPossibleChangedKeys.map(async (deviceIdentity) => { const deviceIdentities = verifiedKeys.map(deviceKeysAsDeviceIdentity);
const existingDevice = await txn.deviceIdentities.get(deviceIdentity.userId, deviceIdentity.deviceId); return await this._storeQueriedDevicesForUserId(userId, deviceIdentities, txn);
if (!existingDevice || existingDevice.ed25519Key === deviceIdentity.ed25519Key) {
return deviceIdentity;
}
// ignore devices where the keys have changed
return null;
}));
// filter out nulls
deviceIdentities = deviceIdentities.filter(di => !!di);
// store devices
for (const deviceIdentity of deviceIdentities) {
txn.deviceIdentities.set(deviceIdentity);
}
// mark user identities as up to date
await Promise.all(verifiedKeysPerUser.map(async ({userId}) => {
const identity = await txn.userIdentities.get(userId);
identity.deviceTrackingStatus = TRACKING_STATUS_UPTODATE;
txn.userIdentities.set(identity);
})); }));
deviceIdentities = devicesIdentitiesPerUser.reduce((all, devices) => all.concat(devices), []);
} catch (err) { } catch (err) {
txn.abort(); txn.abort();
throw err; throw err;
@ -187,6 +168,46 @@ export class DeviceTracker {
return deviceIdentities; return deviceIdentities;
} }
async _storeQueriedDevicesForUserId(userId, deviceIdentities, txn) {
const knownDeviceIds = await txn.deviceIdentities.getAllForUserId(userId);
// delete any devices that we know off but are not in the response anymore.
// important this happens before checking if the ed25519 key changed,
// otherwise we would end up deleting existing devices with changed keys.
for (const deviceId of knownDeviceIds) {
if (deviceIdentities.every(di => di.deviceId !== deviceId)) {
txn.deviceIdentities.remove(userId, deviceId);
}
}
// all the device identities as we will have them in storage
const allDeviceIdentities = [];
const deviceIdentitiesToStore = [];
// filter out devices that have changed their ed25519 key since last time we queried them
deviceIdentities = await Promise.all(deviceIdentities.map(async deviceIdentity => {
if (knownDeviceIds.includes(deviceIdentity.deviceId)) {
const existingDevice = await txn.deviceIdentities.get(deviceIdentity.userId, deviceIdentity.deviceId);
if (existingDevice.ed25519Key !== deviceIdentity.ed25519Key) {
allDeviceIdentities.push(existingDevice);
}
}
allDeviceIdentities.push(deviceIdentity);
deviceIdentitiesToStore.push(deviceIdentity);
}));
// store devices
for (const deviceIdentity of deviceIdentitiesToStore) {
txn.deviceIdentities.set(deviceIdentity);
}
// mark user identities as up to date
const identity = await txn.userIdentities.get(userId);
identity.deviceTrackingStatus = TRACKING_STATUS_UPTODATE;
txn.userIdentities.set(identity);
return allDeviceIdentities;
}
/**
* @return {Array<{userId, verifiedKeys: Array<DeviceSection>>}
*/
_filterVerifiedDeviceKeys(keyQueryDeviceKeysResponse) { _filterVerifiedDeviceKeys(keyQueryDeviceKeysResponse) {
const curve25519Keys = new Set(); const curve25519Keys = new Set();
const verifiedKeys = Object.entries(keyQueryDeviceKeysResponse).map(([userId, keysByDevice]) => { const verifiedKeys = Object.entries(keyQueryDeviceKeysResponse).map(([userId, keysByDevice]) => {

View File

@ -18,6 +18,11 @@ function encodeKey(userId, deviceId) {
return `${userId}|${deviceId}`; return `${userId}|${deviceId}`;
} }
function decodeKey(key) {
const [userId, deviceId] = key.split("|");
return {userId, deviceId};
}
export class DeviceIdentityStore { export class DeviceIdentityStore {
constructor(store) { constructor(store) {
this._store = store; this._store = store;
@ -30,6 +35,21 @@ export class DeviceIdentityStore {
}); });
} }
async getAllDeviceIds(userId) {
const deviceIds = [];
const range = IDBKeyRange.lowerBound(encodeKey(userId, ""));
await this._store.iterateKeys(range, key => {
const decodedKey = decodeKey(key);
// prevent running into the next room
if (decodedKey.userId === userId) {
deviceIds.push(decodedKey.deviceId);
return false; // fetch more
}
return true; // done
});
return deviceIds;
}
get(userId, deviceId) { get(userId, deviceId) {
return this._store.get(encodeKey(userId, deviceId)); return this._store.get(encodeKey(userId, deviceId));
} }
@ -42,4 +62,8 @@ export class DeviceIdentityStore {
getByCurve25519Key(curve25519Key) { getByCurve25519Key(curve25519Key) {
return this._store.index("byCurve25519Key").get(curve25519Key); return this._store.index("byCurve25519Key").get(curve25519Key);
} }
remove(userId, deviceId) {
this._store.delete(encodeKey(userId, deviceId));
}
} }