134 lines
7.4 KiB
JavaScript
134 lines
7.4 KiB
JavaScript
"use strict";
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.DeviceTracker = void 0;
|
|
const LogService_1 = require("../logging/LogService");
|
|
const Crypto_1 = require("../models/Crypto");
|
|
/**
|
|
* Tracks user devices for encryption operations.
|
|
* @category Encryption
|
|
*/
|
|
class DeviceTracker {
|
|
constructor(client) {
|
|
this.client = client;
|
|
this.deviceListUpdates = {};
|
|
}
|
|
/**
|
|
* Gets the device lists for the given user IDs. Outdated device lists will be updated before
|
|
* returning.
|
|
* @param {string[]} userIds The user IDs to get the device lists of.
|
|
* @returns {Promise<Record<string, UserDevice[]>>} Resolves to a map of user ID to device list.
|
|
* If a user has no devices, they may be excluded from the result or appear as an empty array.
|
|
*/
|
|
getDevicesFor(userIds) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
const outdatedUserIds = [];
|
|
for (const userId of userIds) {
|
|
const isOutdated = yield this.client.cryptoStore.isUserOutdated(userId);
|
|
if (isOutdated)
|
|
outdatedUserIds.push(userId);
|
|
}
|
|
yield this.updateUsersDeviceLists(outdatedUserIds);
|
|
const userDeviceMap = {};
|
|
for (const userId of userIds) {
|
|
userDeviceMap[userId] = yield this.client.cryptoStore.getActiveUserDevices(userId);
|
|
}
|
|
return userDeviceMap;
|
|
});
|
|
}
|
|
/**
|
|
* Flags multiple user's device lists as outdated, optionally queuing an immediate update.
|
|
* @param {string} userIds The user IDs to flag the device lists of.
|
|
* @param {boolean} resync True (default) to queue an immediate update, false otherwise.
|
|
* @returns {Promise<void>} Resolves when the flagging has completed. Will wait for the resync
|
|
* if requested too.
|
|
*/
|
|
flagUsersOutdated(userIds, resync = true) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
yield this.client.cryptoStore.flagUsersOutdated(userIds);
|
|
if (resync) {
|
|
yield this.updateUsersDeviceLists(userIds);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Updates multiple user's device lists regardless of outdated flag.
|
|
* @param {string[]} userIds The user IDs to update.
|
|
* @returns {Promise<void>} Resolves when complete.
|
|
*/
|
|
updateUsersDeviceLists(userIds) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
// We wait for the lock, but still run through with our update just in case we are lagged.
|
|
// This can happen if the server is slow to reply to device list queries, but a user is
|
|
// changing information about their device a lot.
|
|
const existingPromises = userIds.map(u => this.deviceListUpdates[u]).filter(p => !!p);
|
|
if (existingPromises.length > 0) {
|
|
yield Promise.all(existingPromises);
|
|
}
|
|
const promise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
var _a, _b;
|
|
try {
|
|
const resp = yield this.client.getUserDevices(userIds);
|
|
for (const userId of Object.keys(resp.device_keys)) {
|
|
if (!userIds.includes(userId)) {
|
|
LogService_1.LogService.warn("DeviceTracker", `Server returned unexpected user ID: ${userId} - ignoring user`);
|
|
continue;
|
|
}
|
|
const validated = [];
|
|
for (const deviceId of Object.keys(resp.device_keys[userId])) {
|
|
const device = resp.device_keys[userId][deviceId];
|
|
if (device.user_id !== userId || device.device_id !== deviceId) {
|
|
LogService_1.LogService.warn("DeviceTracker", `Server appears to be lying about device lists: ${userId} ${deviceId} has unexpected device ${device.user_id} ${device.device_id} listed - ignoring device`);
|
|
continue;
|
|
}
|
|
const ed25519 = device.keys[`${Crypto_1.DeviceKeyAlgorithm.Ed25519}:${deviceId}`];
|
|
const curve25519 = device.keys[`${Crypto_1.DeviceKeyAlgorithm.Curve25519}:${deviceId}`];
|
|
if (!ed25519 || !curve25519) {
|
|
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} is missing either an Ed25519 or Curve25519 key - ignoring device`);
|
|
continue;
|
|
}
|
|
const currentDevices = yield this.client.cryptoStore.getAllUserDevices(userId);
|
|
const existingDevice = currentDevices.find(d => d.device_id === deviceId);
|
|
if (existingDevice) {
|
|
const existingEd25519 = existingDevice.keys[`${Crypto_1.DeviceKeyAlgorithm.Ed25519}:${deviceId}`];
|
|
if (existingEd25519 !== ed25519) {
|
|
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} appears compromised: Ed25519 key changed - ignoring device`);
|
|
continue;
|
|
}
|
|
}
|
|
const signature = (_b = (_a = device.signatures) === null || _a === void 0 ? void 0 : _a[userId]) === null || _b === void 0 ? void 0 : _b[`${Crypto_1.DeviceKeyAlgorithm.Ed25519}:${deviceId}`];
|
|
if (!signature) {
|
|
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} is missing a signature - ignoring device`);
|
|
continue;
|
|
}
|
|
const validSignature = yield this.client.crypto.verifySignature(device, ed25519, signature);
|
|
if (!validSignature) {
|
|
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} has an invalid signature - ignoring device`);
|
|
continue;
|
|
}
|
|
validated.push(device);
|
|
}
|
|
yield this.client.cryptoStore.setActiveUserDevices(userId, validated);
|
|
}
|
|
}
|
|
catch (e) {
|
|
LogService_1.LogService.error("DeviceTracker", "Error updating device lists:", e);
|
|
// return reject(e);
|
|
}
|
|
resolve();
|
|
}));
|
|
userIds.forEach(u => this.deviceListUpdates[u] = promise);
|
|
yield promise;
|
|
});
|
|
}
|
|
}
|
|
exports.DeviceTracker = DeviceTracker;
|