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

333 lines
18 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.SqliteCryptoStorageProvider = void 0;
const Database = require("better-sqlite3");
/**
* Sqlite crypto storage provider. Requires `better-sqlite3` package to be installed.
* @category Storage providers
*/
class SqliteCryptoStorageProvider {
/**
* Creates a new Sqlite storage provider.
* @param {string} path The file path to store the database at. Use ":memory:" to
* store the database entirely in memory, or an empty string to do the equivalent
* on the disk.
*/
constructor(path) {
this.db = new Database(path);
this.db.exec("CREATE TABLE IF NOT EXISTS kv (name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)");
this.db.exec("CREATE TABLE IF NOT EXISTS rooms (room_id TEXT PRIMARY KEY NOT NULL, config TEXT NOT NULL)");
this.db.exec("CREATE TABLE IF NOT EXISTS users (user_id TEXT PRIMARY KEY NOT NULL, outdated TINYINT NOT NULL)");
this.db.exec("CREATE TABLE IF NOT EXISTS user_devices (user_id TEXT NOT NULL, device_id TEXT NOT NULL, device TEXT NOT NULL, active TINYINT NOT NULL, PRIMARY KEY (user_id, device_id))");
this.db.exec("CREATE TABLE IF NOT EXISTS outbound_group_sessions (session_id TEXT NOT NULL, room_id TEXT NOT NULL, current TINYINT NOT NULL, pickled TEXT NOT NULL, uses_left NUMBER NOT NULL, expires_ts NUMBER NOT NULL, PRIMARY KEY (session_id, room_id))");
this.db.exec("CREATE TABLE IF NOT EXISTS sent_outbound_group_sessions (session_id TEXT NOT NULL, room_id TEXT NOT NULL, session_index INT NOT NULL, user_id TEXT NOT NULL, device_id TEXT NOT NULL, PRIMARY KEY (session_id, room_id, user_id, device_id, session_index))");
this.db.exec("CREATE TABLE IF NOT EXISTS olm_sessions (user_id TEXT NOT NULL, device_id TEXT NOT NULL, session_id TEXT NOT NULL, last_decryption_ts NUMBER NOT NULL, pickled TEXT NOT NULL, PRIMARY KEY (user_id, device_id, session_id))");
this.db.exec("CREATE TABLE IF NOT EXISTS inbound_group_sessions (session_id TEXT NOT NULL, room_id TEXT NOT NULL, user_id TEXT NOT NULL, device_id TEXT NOT NULL, pickled TEXT NOT NULL, PRIMARY KEY (session_id, room_id, user_id, device_id))");
this.db.exec("CREATE TABLE IF NOT EXISTS decrypted_event_metadata (room_id TEXT NOT NULL, event_id TEXT NOT NULL, session_id TEXT NOT NULL, message_index INT NOT NULL, PRIMARY KEY (room_id, event_id))");
this.db.exec("CREATE INDEX IF NOT EXISTS idx_decrypted_event_metadata_by_message_index ON decrypted_event_metadata (room_id, session_id, message_index)");
this.kvUpsert = this.db.prepare("INSERT INTO kv (name, value) VALUES (@name, @value) ON CONFLICT (name) DO UPDATE SET value = @value");
this.kvSelect = this.db.prepare("SELECT name, value FROM kv WHERE name = @name");
this.roomUpsert = this.db.prepare("INSERT INTO rooms (room_id, config) VALUES (@roomId, @config) ON CONFLICT (room_id) DO UPDATE SET config = @config");
this.roomSelect = this.db.prepare("SELECT room_id, config FROM rooms WHERE room_id = @roomId");
this.userUpsert = this.db.prepare("INSERT INTO users (user_id, outdated) VALUES (@userId, @outdated) ON CONFLICT (user_id) DO UPDATE SET outdated = @outdated");
this.userSelect = this.db.prepare("SELECT user_id, outdated FROM users WHERE user_id = @userId");
this.userDeviceUpsert = this.db.prepare("INSERT INTO user_devices (user_id, device_id, device, active) VALUES (@userId, @deviceId, @device, @active) ON CONFLICT (user_id, device_id) DO UPDATE SET device = @device, active = @active");
this.userDevicesDelete = this.db.prepare("UPDATE user_devices SET active = 0 WHERE user_id = @userId");
this.userDevicesSelect = this.db.prepare("SELECT user_id, device_id, device, active FROM user_devices WHERE user_id = @userId");
this.userActiveDevicesSelect = this.db.prepare("SELECT user_id, device_id, device, active FROM user_devices WHERE user_id = @userId AND active = 1");
this.userActiveDeviceSelect = this.db.prepare("SELECT user_id, device_id, device, active FROM user_devices WHERE user_id = @userId AND device_id = @deviceId AND active = 1");
this.obGroupSessionUpsert = this.db.prepare("INSERT INTO outbound_group_sessions (session_id, room_id, current, pickled, uses_left, expires_ts) VALUES (@sessionId, @roomId, @current, @pickled, @usesLeft, @expiresTs) ON CONFLICT (session_id, room_id) DO UPDATE SET pickled = @pickled, current = @current, uses_left = @usesLeft, expires_ts = @expiresTs");
this.obGroupSessionSelect = this.db.prepare("SELECT session_id, room_id, current, pickled, uses_left, expires_ts FROM outbound_group_sessions WHERE session_id = @sessionId AND room_id = @roomId");
this.obGroupCurrentSessionSelect = this.db.prepare("SELECT session_id, room_id, current, pickled, uses_left, expires_ts FROM outbound_group_sessions WHERE room_id = @roomId AND current = 1");
this.obGroupSessionMarkAllInactive = this.db.prepare("UPDATE outbound_group_sessions SET current = 0 WHERE room_id = @roomId");
this.obSentGroupSessionUpsert = this.db.prepare("INSERT INTO sent_outbound_group_sessions (session_id, room_id, session_index, user_id, device_id) VALUES (@sessionId, @roomId, @sessionIndex, @userId, @deviceId) ON CONFLICT (session_id, room_id, user_id, device_id, session_index) DO NOTHING");
this.obSentSelectLastSent = this.db.prepare("SELECT session_id, room_id, session_index, user_id, device_id FROM sent_outbound_group_sessions WHERE user_id = @userId AND device_id = @deviceId AND room_id = @roomId");
this.olmSessionUpsert = this.db.prepare("INSERT INTO olm_sessions (user_id, device_id, session_id, last_decryption_ts, pickled) VALUES (@userId, @deviceId, @sessionId, @lastDecryptionTs, @pickled) ON CONFLICT (user_id, device_id, session_id) DO UPDATE SET last_decryption_ts = @lastDecryptionTs, pickled = @pickled");
this.olmSessionCurrentSelect = this.db.prepare("SELECT user_id, device_id, session_id, last_decryption_ts, pickled FROM olm_sessions WHERE user_id = @userId AND device_id = @deviceId ORDER BY last_decryption_ts DESC LIMIT 1");
this.olmSessionSelect = this.db.prepare("SELECT user_id, device_id, session_id, last_decryption_ts, pickled FROM olm_sessions WHERE user_id = @userId AND device_id = @deviceId");
this.ibGroupSessionUpsert = this.db.prepare("INSERT INTO inbound_group_sessions (session_id, room_id, user_id, device_id, pickled) VALUES (@sessionId, @roomId, @userId, @deviceId, @pickled) ON CONFLICT (session_id, room_id, user_id, device_id) DO UPDATE SET pickled = @pickled");
this.ibGroupSessionSelect = this.db.prepare("SELECT session_id, room_id, user_id, device_id, pickled FROM inbound_group_sessions WHERE session_id = @sessionId AND room_id = @roomId AND user_id = @userId AND device_id = @deviceId");
this.deMetadataUpsert = this.db.prepare("INSERT INTO decrypted_event_metadata (room_id, event_id, session_id, message_index) VALUES (@roomId, @eventId, @sessionId, @messageIndex) ON CONFLICT (room_id, event_id) DO UPDATE SET message_index = @messageIndex, session_id = @sessionId");
this.deMetadataSelect = this.db.prepare("SELECT room_id, event_id, session_id, message_index FROM decrypted_event_metadata WHERE room_id = @roomId AND session_id = @sessionId AND message_index = @messageIndex LIMIT 1");
}
setDeviceId(deviceId) {
return __awaiter(this, void 0, void 0, function* () {
this.kvUpsert.run({
name: 'deviceId',
value: deviceId,
});
});
}
getDeviceId() {
return __awaiter(this, void 0, void 0, function* () {
const row = this.kvSelect.get({ name: 'deviceId' });
return row === null || row === void 0 ? void 0 : row.value;
});
}
setPickleKey(pickleKey) {
return __awaiter(this, void 0, void 0, function* () {
this.kvUpsert.run({
name: 'pickleKey',
value: pickleKey,
});
});
}
getPickleKey() {
return __awaiter(this, void 0, void 0, function* () {
const row = this.kvSelect.get({ name: 'pickleKey' });
return row === null || row === void 0 ? void 0 : row.value;
});
}
setPickledAccount(pickled) {
return __awaiter(this, void 0, void 0, function* () {
this.kvUpsert.run({
name: 'pickled',
value: pickled,
});
});
}
getPickledAccount() {
return __awaiter(this, void 0, void 0, function* () {
const row = this.kvSelect.get({ name: 'pickled' });
return row === null || row === void 0 ? void 0 : row.value;
});
}
storeRoom(roomId, config) {
return __awaiter(this, void 0, void 0, function* () {
this.roomUpsert.run({
roomId: roomId,
config: JSON.stringify(config),
});
});
}
getRoom(roomId) {
return __awaiter(this, void 0, void 0, function* () {
const row = this.roomSelect.get({ roomId: roomId });
const val = row === null || row === void 0 ? void 0 : row.config;
return val ? JSON.parse(val) : null;
});
}
setActiveUserDevices(userId, devices) {
return __awaiter(this, void 0, void 0, function* () {
this.db.transaction(() => {
this.userUpsert.run({ userId: userId, outdated: 0 });
this.userDevicesDelete.run({ userId: userId });
for (const device of devices) {
this.userDeviceUpsert.run({ userId: userId, deviceId: device.device_id, device: JSON.stringify(device), active: 1 });
}
})();
});
}
getActiveUserDevices(userId) {
return __awaiter(this, void 0, void 0, function* () {
const results = this.userActiveDevicesSelect.all({ userId: userId });
if (!results)
return [];
return results.map(d => JSON.parse(d.device));
});
}
getActiveUserDevice(userId, deviceId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.userActiveDeviceSelect.get({ userId: userId, deviceId: deviceId });
if (!result)
return null;
return JSON.parse(result.device);
});
}
getAllUserDevices(userId) {
return __awaiter(this, void 0, void 0, function* () {
const results = this.userDevicesSelect.all({ userId: userId });
if (!results)
return [];
return results.map(d => Object.assign({}, JSON.parse(d.device), { unsigned: { bsdkIsActive: d.active === 1 } }));
});
}
flagUsersOutdated(userIds) {
return __awaiter(this, void 0, void 0, function* () {
this.db.transaction(() => {
for (const userId of userIds) {
this.userUpsert.run({ userId: userId, outdated: 1 });
}
})();
});
}
isUserOutdated(userId) {
return __awaiter(this, void 0, void 0, function* () {
const user = this.userSelect.get({ userId: userId });
return user ? Boolean(user.outdated) : true;
});
}
storeOutboundGroupSession(session) {
return __awaiter(this, void 0, void 0, function* () {
this.db.transaction(() => {
if (session.isCurrent) {
this.obGroupSessionMarkAllInactive.run({
roomId: session.roomId,
});
}
this.obGroupSessionUpsert.run({
sessionId: session.sessionId,
roomId: session.roomId,
pickled: session.pickled,
current: session.isCurrent ? 1 : 0,
usesLeft: session.usesLeft,
expiresTs: session.expiresTs,
});
})();
});
}
getOutboundGroupSession(sessionId, roomId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.obGroupSessionSelect.get({ sessionId: sessionId, roomId: roomId });
if (result) {
return {
sessionId: result.session_id,
roomId: result.room_id,
pickled: result.pickled,
isCurrent: result.current === 1,
usesLeft: result.uses_left,
expiresTs: result.expires_ts,
};
}
return null;
});
}
getCurrentOutboundGroupSession(roomId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.obGroupCurrentSessionSelect.get({ roomId: roomId });
if (result) {
return {
sessionId: result.session_id,
roomId: result.room_id,
pickled: result.pickled,
isCurrent: result.current === 1,
usesLeft: result.uses_left,
expiresTs: result.expires_ts,
};
}
return null;
});
}
storeSentOutboundGroupSession(session, index, device) {
return __awaiter(this, void 0, void 0, function* () {
this.obSentGroupSessionUpsert.run({
sessionId: session.sessionId,
roomId: session.roomId,
sessionIndex: index,
userId: device.user_id,
deviceId: device.device_id,
});
});
}
getLastSentOutboundGroupSession(userId, deviceId, roomId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.obSentSelectLastSent.get({ userId: userId, deviceId: deviceId, roomId: roomId });
if (result) {
return { sessionId: result.session_id, index: result.session_index };
}
return null;
});
}
storeOlmSession(userId, deviceId, session) {
return __awaiter(this, void 0, void 0, function* () {
this.olmSessionUpsert.run({
userId: userId,
deviceId: deviceId,
sessionId: session.sessionId,
lastDecryptionTs: session.lastDecryptionTs,
pickled: session.pickled,
});
});
}
getCurrentOlmSession(userId, deviceId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.olmSessionCurrentSelect.get({ userId: userId, deviceId: deviceId });
if (!result)
return null;
return {
sessionId: result.session_id,
pickled: result.pickled,
lastDecryptionTs: result.last_decryption_ts,
};
});
}
getOlmSessions(userId, deviceId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.olmSessionSelect.all({
userId: userId,
deviceId: deviceId,
});
return (result || []).map(r => ({
sessionId: r.session_id,
pickled: r.pickled,
lastDecryptionTs: r.last_decryption_ts,
}));
});
}
storeInboundGroupSession(session) {
return __awaiter(this, void 0, void 0, function* () {
this.ibGroupSessionUpsert.run({
sessionId: session.sessionId,
roomId: session.roomId,
userId: session.senderUserId,
deviceId: session.senderDeviceId,
pickled: session.pickled,
});
});
}
getInboundGroupSession(senderUserId, senderDeviceId, roomId, sessionId) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.ibGroupSessionSelect.get({
sessionId: sessionId,
roomId: roomId,
userId: senderUserId,
deviceId: senderDeviceId,
});
if (result) {
return {
sessionId: result.session_id,
roomId: result.room_id,
senderUserId: result.user_id,
senderDeviceId: result.device_id,
pickled: result.pickled,
};
}
return null;
});
}
setMessageIndexForEvent(roomId, eventId, sessionId, messageIndex) {
return __awaiter(this, void 0, void 0, function* () {
this.deMetadataUpsert.run({
roomId: roomId,
eventId: eventId,
sessionId: sessionId,
messageIndex: messageIndex,
});
});
}
getEventForMessageIndex(roomId, sessionId, messageIndex) {
return __awaiter(this, void 0, void 0, function* () {
const result = this.deMetadataSelect.get({ roomId: roomId, sessionId: sessionId, messageIndex: messageIndex });
return result === null || result === void 0 ? void 0 : result.event_id;
});
}
/**
* Closes the crypto store. Primarily for testing purposes.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
this.db.close();
});
}
}
exports.SqliteCryptoStorageProvider = SqliteCryptoStorageProvider;