Hhertz/node_modules/matrix-bot-sdk/lib/e2ee/DeviceTracker.js
2025-07-31 23:47:20 +03:00

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;