diff --git a/src/@types/partials.ts b/src/@types/partials.ts index 19c11d153..f66dbe971 100644 --- a/src/@types/partials.ts +++ b/src/@types/partials.ts @@ -74,3 +74,11 @@ export enum HistoryVisibility { Shared = "shared", WorldReadable = "world_readable", } + +// XXX move to OlmDevice when converted +export interface InboundGroupSessionData { + room_id: string; // eslint-disable-line camelcase + session: string; + keysClaimed: Record; + forwardingCurve25519KeyChain: string[]; +} diff --git a/src/client.ts b/src/client.ts index e7d7bfb48..72cabc6ce 100644 --- a/src/client.ts +++ b/src/client.ts @@ -146,10 +146,10 @@ import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@ import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "./@types/spaces"; import { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, RuleId } from "./@types/PushRules"; import { IThreepid } from "./@types/threepids"; +import { CryptoStore } from "./crypto/store/base"; export type Store = IStore; export type SessionStore = WebStorageSessionStore; -export type CryptoStore = MemoryCryptoStore | LocalStorageCryptoStore | IndexedDBCryptoStore; export type Callback = (err: Error | any | null, data?: any) => void; export type ResetTimelineCallback = (roomId: string) => boolean; diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index aacb47148..76f651209 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -28,10 +28,11 @@ import { decryptAES, encryptAES } from './aes'; import { PkSigning } from "@matrix-org/olm"; import { DeviceInfo } from "./deviceinfo"; import { SecretStorage } from "./SecretStorage"; -import { CryptoStore, ICrossSigningKey, ISignedKey, MatrixClient } from "../client"; +import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; import { ICryptoCallbacks } from "../matrix"; import { ISignatures } from "../@types/signed"; +import { CryptoStore } from "./store/base"; const KEY_REQUEST_TIMEOUT_MS = 1000 * 60; @@ -47,6 +48,12 @@ export interface ICacheCallbacks { storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise; } +export interface ICrossSigningInfo { + keys: Record; + firstUse: boolean; + crossSigningVerifiedBefore: boolean; +} + export class CrossSigningInfo extends EventEmitter { public keys: Record = {}; public firstUse = true; @@ -75,7 +82,7 @@ export class CrossSigningInfo extends EventEmitter { super(); } - public static fromStorage(obj: object, userId: string): CrossSigningInfo { + public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo { const res = new CrossSigningInfo(userId); for (const prop in obj) { if (obj.hasOwnProperty(prop)) { @@ -85,7 +92,7 @@ export class CrossSigningInfo extends EventEmitter { return res; } - public toStorage(): object { + public toStorage(): ICrossSigningInfo { return { keys: this.keys, firstUse: this.firstUse, diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index 890f3bfa3..d27af3c54 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -24,12 +24,13 @@ import { EventEmitter } from 'events'; import { logger } from '../logger'; import { DeviceInfo, IDevice } from './deviceinfo'; -import { CrossSigningInfo } from './CrossSigning'; +import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning'; import * as olmlib from './olmlib'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { chunkPromises, defer, IDeferred, sleep } from '../utils'; -import { MatrixClient, CryptoStore } from "../client"; +import { MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; +import { CryptoStore } from "./store/base"; /* State transition diagram for DeviceList.deviceTrackingStatus * @@ -52,7 +53,7 @@ import { OlmDevice } from "./OlmDevice"; */ // constants for DeviceList.deviceTrackingStatus -enum TrackingStatus { +export enum TrackingStatus { NotTracked, PendingDownload, DownloadInProgress, @@ -65,32 +66,22 @@ export type DeviceInfoMap = Record>; * @alias module:crypto/DeviceList */ export class DeviceList extends EventEmitter { - // userId -> { - // deviceId -> { - // [device info] - // } - // } - private devices: Record> = {}; + private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {}; - // userId -> { - // [key info] - // } - public crossSigningInfo: Record = {}; + public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {}; // map of identity keys to the user who owns it private userByIdentityKey: Record = {}; // which users we are tracking device status for. - // userId -> TRACKING_STATUS_* - private deviceTrackingStatus: Record = {}; // loaded from storage in load() + private deviceTrackingStatus: { [userId: string]: TrackingStatus } = {}; // loaded from storage in load() // The 'next_batch' sync token at the point the data was written, // ie. a token representing the point immediately after the // moment represented by the snapshot in the db. private syncToken: string = null; - // userId -> promise - private keyDownloadsInProgressByUser: Record> = {}; + private keyDownloadsInProgressByUser: { [userId: string]: Promise } = {}; // Set whenever changes are made other than setting the sync token private dirty = false; @@ -375,7 +366,7 @@ export class DeviceList extends EventEmitter { return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId); } - public storeCrossSigningForUser(userId: string, info: CrossSigningInfo): void { + public storeCrossSigningForUser(userId: string, info: ICrossSigningInfo): void { this.crossSigningInfo[userId] = info; this.dirty = true; } @@ -603,7 +594,7 @@ export class DeviceList extends EventEmitter { } } - public setRawStoredCrossSigningForUser(userId: string, info: object): void { + public setRawStoredCrossSigningForUser(userId: string, info: ICrossSigningInfo): void { this.crossSigningInfo[userId] = info; } @@ -864,9 +855,7 @@ class DeviceListUpdateSerialiser { crossSigning.setKeys(crossSigningResponse); - this.deviceList.setRawStoredCrossSigningForUser( - userId, crossSigning.toStorage(), - ); + this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); // NB. Unlike most events in the js-sdk, this one is internal to the // js-sdk and is not re-emitted diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index b0410df72..4d8d2618a 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -949,7 +949,7 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) { * data stored in the session store about an inbound group session * * @typedef {Object} InboundGroupSessionData - * @property {string} room_Id + * @property {string} room_id * @property {string} session pickled Olm.InboundGroupSession * @property {Object} keysClaimed * @property {Array} forwardingCurve25519KeyChain Devices involved in forwarding diff --git a/src/crypto/OutgoingRoomKeyRequestManager.ts b/src/crypto/OutgoingRoomKeyRequestManager.ts index d01245722..b152f248d 100644 --- a/src/crypto/OutgoingRoomKeyRequestManager.ts +++ b/src/crypto/OutgoingRoomKeyRequestManager.ts @@ -15,9 +15,9 @@ limitations under the License. */ import { logger } from '../logger'; -import { CryptoStore, MatrixClient } from "../client"; +import { MatrixClient } from "../client"; import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index"; -import { OutgoingRoomKeyRequest } from './store/base'; +import { CryptoStore, OutgoingRoomKeyRequest } from './store/base'; import { EventType } from "../@types/event"; /** @@ -263,9 +263,8 @@ export class OutgoingRoomKeyRequestManager { 'deleting unnecessary room key request for ' + stringifyRequestBody(requestBody), ); - return this.cryptoStore.deleteOutgoingRoomKeyRequest( - req.requestId, RoomKeyRequestState.Unsent, - ); + return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent) + .then(); // match Promise signature case RoomKeyRequestState.Sent: { // send a cancellation. @@ -325,7 +324,7 @@ export class OutgoingRoomKeyRequestManager { * @return {Promise} resolves to a list of all the * {@link module:crypto/store/base~OutgoingRoomKeyRequest} */ - public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): OutgoingRoomKeyRequest[] { + public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): Promise { return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]); } @@ -415,7 +414,7 @@ export class OutgoingRoomKeyRequestManager { } // given a RoomKeyRequest, send it and update the request record - private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise { + private async sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise { logger.log( `Requesting keys for ${stringifyRequestBody(req.requestBody)}` + ` from ${stringifyRecipientList(req.recipients)}` + @@ -429,19 +428,19 @@ export class OutgoingRoomKeyRequestManager { body: req.requestBody, }; - return this.sendMessageToDevices( - requestMessage, req.recipients, req.requestTxnId || req.requestId, - ).then(() => { - return this.cryptoStore.updateOutgoingRoomKeyRequest( - req.requestId, RoomKeyRequestState.Unsent, - { state: RoomKeyRequestState.Sent }, - ); - }); + await this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId); + await this.cryptoStore.updateOutgoingRoomKeyRequest( + req.requestId, RoomKeyRequestState.Unsent, + { state: RoomKeyRequestState.Sent }, + ); } // Given a RoomKeyRequest, cancel it and delete the request record unless // andResend is set, in which case transition to UNSENT. - private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise { + private async sendOutgoingRoomKeyRequestCancellation( + req: OutgoingRoomKeyRequest, + andResend = false, + ): Promise { logger.log( `Sending cancellation for key request for ` + `${stringifyRequestBody(req.requestBody)} to ` + @@ -455,21 +454,19 @@ export class OutgoingRoomKeyRequestManager { request_id: req.requestId, }; - return this.sendMessageToDevices( - requestMessage, req.recipients, req.cancellationTxnId, - ).then(() => { - if (andResend) { - // We want to resend, so transition to UNSENT - return this.cryptoStore.updateOutgoingRoomKeyRequest( - req.requestId, - RoomKeyRequestState.CancellationPendingAndWillResend, - { state: RoomKeyRequestState.Unsent }, - ); - } - return this.cryptoStore.deleteOutgoingRoomKeyRequest( + await this.sendMessageToDevices(requestMessage, req.recipients, req.cancellationTxnId); + if (andResend) { + // We want to resend, so transition to UNSENT + await this.cryptoStore.updateOutgoingRoomKeyRequest( + req.requestId, + RoomKeyRequestState.CancellationPendingAndWillResend, + { state: RoomKeyRequestState.Unsent }, + ); + } else { + await this.cryptoStore.deleteOutgoingRoomKeyRequest( req.requestId, RoomKeyRequestState.CancellationPending, ); - }); + } } // send a RoomKeyRequest to a list of recipients diff --git a/src/crypto/RoomList.ts b/src/crypto/RoomList.ts index b2835362f..24315d4ec 100644 --- a/src/crypto/RoomList.ts +++ b/src/crypto/RoomList.ts @@ -20,8 +20,8 @@ limitations under the License. * Manages the list of encrypted rooms */ +import { CryptoStore } from './store/base'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; -import { CryptoStore } from "../client"; /* eslint-disable camelcase */ export interface IRoomEncryption { diff --git a/src/crypto/algorithms/megolm.ts b/src/crypto/algorithms/megolm.ts index 2a1a2d8db..381f85188 100644 --- a/src/crypto/algorithms/megolm.ts +++ b/src/crypto/algorithms/megolm.ts @@ -61,7 +61,7 @@ interface IBlockedMap { }; } -interface IOlmDevice { +export interface IOlmDevice { userId: string; deviceInfo: T; } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 761333f65..ea627dddf 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -59,12 +59,13 @@ import { IStore } from "../store"; import { Room } from "../models/room"; import { RoomMember } from "../models/room-member"; import { MatrixEvent } from "../models/event"; -import { MatrixClient, IKeysUploadResponse, SessionStore, CryptoStore, ISignedKey } from "../client"; +import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey } from "../client"; import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base"; import type { IRoomEncryption, RoomList } from "./RoomList"; import { IRecoveryKey, IEncryptedEventInfo } from "./api"; import { IKeyBackupInfo } from "./keybackup"; import { ISyncStateData } from "../sync"; +import { CryptoStore } from "./store/base"; const DeviceVerification = DeviceInfo.DeviceVerification; @@ -1410,9 +1411,7 @@ export class Crypto extends EventEmitter { crossSigning.updateCrossSigningVerifiedBefore( this.checkUserTrust(userId).isCrossSigningVerified(), ); - this.deviceList.setRawStoredCrossSigningForUser( - userId, crossSigning.toStorage(), - ); + this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); } this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId)); diff --git a/src/crypto/store/base.ts b/src/crypto/store/base.ts index d76fb9ead..03ba6ef8e 100644 --- a/src/crypto/store/base.ts +++ b/src/crypto/store/base.ts @@ -1,5 +1,33 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index"; +import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { TrackingStatus } from "../DeviceList"; +import { IRoomEncryption } from "../RoomList"; +import { IDevice } from "../deviceinfo"; +import { ICrossSigningInfo } from "../CrossSigning"; +import { PrefixedLogger } from "../../logger"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; + /** - * Internal module. Defintions for storage for the crypto module + * Internal module. Definitions for storage for the crypto module * * @module */ @@ -9,9 +37,141 @@ * * @interface CryptoStore */ +export interface CryptoStore { + startup(): Promise; + deleteAllData(): Promise; + getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise; + getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise; + getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise; + getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise; + getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise; + updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise; + deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise; -import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index"; -import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager"; + // Olm Account + getAccount(txn: unknown, func: (accountPickle: string) => void); + storeAccount(txn: unknown, accountPickle: string): void; + getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void; + getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): void; + storeCrossSigningKeys(txn: unknown, keys: Record): void; + storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void; + + // Olm Sessions + countEndToEndSessions(txn: unknown, func: (count: number) => void): void; + getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: unknown, + func: (session: ISessionInfo) => void, + ): void; + getEndToEndSessions( + deviceKey: string, + txn: unknown, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void; + getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void; + storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void; + storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise; + getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise; + filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise; + + // Inbound Group Sessions + getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: unknown, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void; + getAllEndToEndInboundGroupSessions( + txn: unknown, + func: (session: ISession | null) => void, + ): void; + addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void; + storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void; + storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: unknown, + ): void; + + // Device Data + getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void; + storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void; + storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void; + getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void; + getSessionsNeedingBackup(limit: number): Promise; + countSessionsNeedingBackup(txn?: unknown): Promise; + unmarkSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise; + markSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise; + addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: unknown): void; + getSharedHistoryInboundGroupSessions( + roomId: string, + txn?: IDBTransaction, + ): Promise<[senderKey: string, sessionId: string][]>; + + // Session key backups + doTxn(mode: Mode, stores: Iterable, func: (txn: unknown) => T, log?: PrefixedLogger): Promise; +} + +export type Mode = "readonly" | "readwrite"; + +export interface ISession { + senderKey: string; + sessionId: string; + sessionData?: InboundGroupSessionData; +} + +export interface ISessionInfo { + deviceKey?: string; + sessionId?: string; + session?: string; + lastReceivedMessageTs?: number; +} + +export interface IDeviceData { + devices: { + [ userId: string ]: { + [ deviceId: string ]: IDevice; + }; + }; + trackingStatus: { + [ userId: string ]: TrackingStatus; + }; + crossSigningInfo?: Record; + syncToken?: string; +} + +export interface IProblem { + type: string; + fixed: boolean; + time: number; +} + +export interface IWithheld { + // eslint-disable-next-line camelcase + room_id: string; + code: string; + reason: string; +} /** * Represents an outgoing room key request diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.ts similarity index 74% rename from src/crypto/store/indexeddb-crypto-store-backend.js rename to src/crypto/store/indexeddb-crypto-store-backend.ts index 9a54a3537..b4735ad5b 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,8 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { logger } from '../../logger'; +import { logger, PrefixedLogger } from '../../logger'; import * as utils from "../../utils"; +import { + CryptoStore, + IDeviceData, + IProblem, + ISession, + ISessionInfo, + IWithheld, + Mode, + OutgoingRoomKeyRequest, +} from "./base"; +import { IRoomKeyRequestBody } from "../index"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; export const VERSION = 10; const PROFILE_TRANSACTIONS = false; @@ -29,18 +43,17 @@ const PROFILE_TRANSACTIONS = false; * * @implements {module:crypto/store/base~CryptoStore} */ -export class Backend { +export class Backend implements CryptoStore { + private nextTxnId = 0; + /** * @param {IDBDatabase} db */ - constructor(db) { - this._db = db; - this._nextTxnId = 0; - + constructor(private db: IDBDatabase) { // make sure we close the db on `onversionchange` - otherwise // attempts to delete the database will block (and subsequent // attempts to re-create it will also block). - db.onversionchange = (ev) => { + db.onversionchange = () => { logger.log(`versionchange for indexeddb ${this._dbName}: closing`); db.close(); }; @@ -56,11 +69,11 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the * same instance as passed in, or the existing one. */ - getOrAddOutgoingRoomKeyRequest(request) { + public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { const requestBody = request.requestBody; return new Promise((resolve, reject) => { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); txn.onerror = reject; // first see if we already have an entry for this request. @@ -99,9 +112,9 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found */ - getOutgoingRoomKeyRequest(requestBody) { + public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { return new Promise((resolve, reject) => { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); txn.onerror = reject; this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => { @@ -122,7 +135,12 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found. */ - _getOutgoingRoomKeyRequest(txn, requestBody, callback) { + // eslint-disable-next-line @typescript-eslint/naming-convention + private _getOutgoingRoomKeyRequest( + txn: IDBTransaction, + requestBody: IRoomKeyRequestBody, + callback: (req: OutgoingRoomKeyRequest | null) => void, + ): void { const store = txn.objectStore("outgoingRoomKeyRequests"); const idx = store.index("session"); @@ -131,8 +149,8 @@ export class Backend { requestBody.session_id, ]); - cursorReq.onsuccess = (ev) => { - const cursor = ev.target.result; + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; if (!cursor) { // no match found callback(null); @@ -162,7 +180,7 @@ export class Backend { * there are no pending requests in those states. If there are multiple * requests in those states, an arbitrary one is chosen. */ - getOutgoingRoomKeyRequestByState(wantedStates) { + public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise { if (wantedStates.length === 0) { return Promise.resolve(null); } @@ -195,7 +213,7 @@ export class Backend { cursorReq.onsuccess = onsuccess; } - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); const store = txn.objectStore("outgoingRoomKeyRequests"); const wantedState = wantedStates[stateIndex]; @@ -210,19 +228,23 @@ export class Backend { * @param {Number} wantedState * @return {Promise>} All elements in a given state */ - getAllOutgoingRoomKeyRequestsByState(wantedState) { + public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise { return new Promise((resolve, reject) => { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); const store = txn.objectStore("outgoingRoomKeyRequests"); const index = store.index("state"); const request = index.getAll(wantedState); - request.onsuccess = (ev) => resolve(ev.target.result); - request.onerror = (ev) => reject(ev.target.error); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); }); } - getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + public getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise { let stateIndex = 0; const results = []; @@ -248,7 +270,7 @@ export class Backend { } } - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); const store = txn.objectStore("outgoingRoomKeyRequests"); const wantedState = wantedStates[stateIndex]; @@ -270,7 +292,11 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest} * updated request, or null if no matching row was found */ - updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { + public updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise { let result = null; function onsuccess(ev) { @@ -291,9 +317,8 @@ export class Backend { result = data; } - const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite"); - const cursorReq = txn.objectStore("outgoingRoomKeyRequests") - .openCursor(requestId); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId); cursorReq.onsuccess = onsuccess; return promiseifyTxn(txn).then(() => result); } @@ -307,12 +332,14 @@ export class Backend { * * @returns {Promise} resolves once the operation is completed */ - deleteOutgoingRoomKeyRequest(requestId, expectedState) { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite"); - const cursorReq = txn.objectStore("outgoingRoomKeyRequests") - .openCursor(requestId); - cursorReq.onsuccess = (ev) => { - const cursor = ev.target.result; + public deleteOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + ): Promise { + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId); + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; if (!cursor) { return; } @@ -326,12 +353,12 @@ export class Backend { } cursor.delete(); }; - return promiseifyTxn(txn); + return promiseifyTxn(txn); } // Olm Account - getAccount(txn, func) { + public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get("-"); getReq.onsuccess = function() { @@ -343,12 +370,12 @@ export class Backend { }; } - storeAccount(txn, newData) { + public storeAccount(txn: IDBTransaction, accountPickle: string): void { const objectStore = txn.objectStore("account"); - objectStore.put(newData, "-"); + objectStore.put(accountPickle, "-"); } - getCrossSigningKeys(txn, func) { + public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record) => void): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get("crossSigningKeys"); getReq.onsuccess = function() { @@ -360,7 +387,11 @@ export class Backend { }; } - getSecretStorePrivateKey(txn, func, type) { + public getSecretStorePrivateKey( + txn: IDBTransaction, + func: (key: IEncryptedPayload | null) => T, + type: string, + ): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get(`ssss_cache:${type}`); getReq.onsuccess = function() { @@ -372,19 +403,19 @@ export class Backend { }; } - storeCrossSigningKeys(txn, keys) { + public storeCrossSigningKeys(txn: IDBTransaction, keys: Record): void { const objectStore = txn.objectStore("account"); objectStore.put(keys, "crossSigningKeys"); } - storeSecretStorePrivateKey(txn, type, key) { + public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void { const objectStore = txn.objectStore("account"); objectStore.put(key, `ssss_cache:${type}`); } // Olm Sessions - countEndToEndSessions(txn, func) { + public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void { const objectStore = txn.objectStore("sessions"); const countReq = objectStore.count(); countReq.onsuccess = function() { @@ -396,7 +427,11 @@ export class Backend { }; } - getEndToEndSessions(deviceKey, txn, func) { + public getEndToEndSessions( + deviceKey: string, + txn: IDBTransaction, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { const objectStore = txn.objectStore("sessions"); const idx = objectStore.index("deviceKey"); const getReq = idx.openCursor(deviceKey); @@ -419,7 +454,12 @@ export class Backend { }; } - getEndToEndSession(deviceKey, sessionId, txn, func) { + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: IDBTransaction, + func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void, + ): void { const objectStore = txn.objectStore("sessions"); const getReq = objectStore.get([deviceKey, sessionId]); getReq.onsuccess = function() { @@ -438,7 +478,7 @@ export class Backend { }; } - getAllEndToEndSessions(txn, func) { + public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void { const objectStore = txn.objectStore("sessions"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { @@ -456,7 +496,12 @@ export class Backend { }; } - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + public storeEndToEndSession( + deviceKey: string, + sessionId: string, + sessionInfo: ISessionInfo, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("sessions"); objectStore.put({ deviceKey, @@ -466,8 +511,8 @@ export class Backend { }); } - async storeEndToEndSessionProblem(deviceKey, type, fixed) { - const txn = this._db.transaction("session_problems", "readwrite"); + public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { + const txn = this.db.transaction("session_problems", "readwrite"); const objectStore = txn.objectStore("session_problems"); objectStore.put({ deviceKey, @@ -478,13 +523,13 @@ export class Backend { return promiseifyTxn(txn); } - async getEndToEndSessionProblem(deviceKey, timestamp) { + public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { let result; - const txn = this._db.transaction("session_problems", "readwrite"); + const txn = this.db.transaction("session_problems", "readwrite"); const objectStore = txn.objectStore("session_problems"); const index = objectStore.index("deviceKey"); const req = index.getAll(deviceKey); - req.onsuccess = (event) => { + req.onsuccess = () => { const problems = req.result; if (!problems.length) { result = null; @@ -511,14 +556,14 @@ export class Backend { } // FIXME: we should probably prune this when devices get deleted - async filterOutNotifiedErrorDevices(devices) { - const txn = this._db.transaction("notified_error_devices", "readwrite"); + public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + const txn = this.db.transaction("notified_error_devices", "readwrite"); const objectStore = txn.objectStore("notified_error_devices"); - const ret = []; + const ret: IOlmDevice[] = []; await Promise.all(devices.map((device) => { - return new Promise((resolve) => { + return new Promise((resolve) => { const { userId, deviceInfo } = device; const getReq = objectStore.get([userId, deviceInfo.deviceId]); getReq.onsuccess = function() { @@ -536,9 +581,14 @@ export class Backend { // Inbound group sessions - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { - let session = false; - let withheld = false; + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: IDBTransaction, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { + let session: InboundGroupSessionData | boolean = false; + let withheld: IWithheld | boolean = false; const objectStore = txn.objectStore("inbound_group_sessions"); const getReq = objectStore.get([senderCurve25519Key, sessionId]); getReq.onsuccess = function() { @@ -549,7 +599,7 @@ export class Backend { session = null; } if (withheld !== false) { - func(session, withheld); + func(session as InboundGroupSessionData, withheld as IWithheld); } } catch (e) { abortWithException(txn, e); @@ -566,7 +616,7 @@ export class Backend { withheld = null; } if (session !== false) { - func(session, withheld); + func(session as InboundGroupSessionData, withheld as IWithheld); } } catch (e) { abortWithException(txn, e); @@ -574,7 +624,7 @@ export class Backend { }; } - getAllEndToEndInboundGroupSessions(txn, func) { + public getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void { const objectStore = txn.objectStore("inbound_group_sessions"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { @@ -600,7 +650,12 @@ export class Backend { }; } - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("inbound_group_sessions"); const addReq = objectStore.add({ senderCurve25519Key, sessionId, session: sessionData, @@ -623,23 +678,31 @@ export class Backend { }; } - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("inbound_group_sessions"); objectStore.put({ senderCurve25519Key, sessionId, session: sessionData, }); } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("inbound_group_sessions_withheld"); objectStore.put({ senderCurve25519Key, sessionId, session: sessionData, }); } - getEndToEndDeviceData(txn, func) { + public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void { const objectStore = txn.objectStore("device_data"); const getReq = objectStore.get("-"); getReq.onsuccess = function() { @@ -651,24 +714,24 @@ export class Backend { }; } - storeEndToEndDeviceData(deviceData, txn) { + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void { const objectStore = txn.objectStore("device_data"); objectStore.put(deviceData, "-"); } - storeEndToEndRoom(roomId, roomInfo, txn) { + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void { const objectStore = txn.objectStore("rooms"); objectStore.put(roomInfo, roomId); } - getEndToEndRooms(txn, func) { + public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record) => void): void { const rooms = {}; const objectStore = txn.objectStore("rooms"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { const cursor = getReq.result; if (cursor) { - rooms[cursor.key] = cursor.value; + rooms[cursor.key as string] = cursor.value; cursor.continue(); } else { try { @@ -682,11 +745,11 @@ export class Backend { // session backups - getSessionsNeedingBackup(limit) { + public getSessionsNeedingBackup(limit: number): Promise { return new Promise((resolve, reject) => { const sessions = []; - const txn = this._db.transaction( + const txn = this.db.transaction( ["sessions_needing_backup", "inbound_group_sessions"], "readonly", ); @@ -716,9 +779,9 @@ export class Backend { }); } - countSessionsNeedingBackup(txn) { + public countSessionsNeedingBackup(txn?: IDBTransaction): Promise { if (!txn) { - txn = this._db.transaction("sessions_needing_backup", "readonly"); + txn = this.db.transaction("sessions_needing_backup", "readonly"); } const objectStore = txn.objectStore("sessions_needing_backup"); return new Promise((resolve, reject) => { @@ -728,12 +791,12 @@ export class Backend { }); } - unmarkSessionsNeedingBackup(sessions, txn) { + public async unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { if (!txn) { - txn = this._db.transaction("sessions_needing_backup", "readwrite"); + txn = this.db.transaction("sessions_needing_backup", "readwrite"); } const objectStore = txn.objectStore("sessions_needing_backup"); - return Promise.all(sessions.map((session) => { + await Promise.all(sessions.map((session) => { return new Promise((resolve, reject) => { const req = objectStore.delete([session.senderKey, session.sessionId]); req.onsuccess = resolve; @@ -742,12 +805,12 @@ export class Backend { })); } - markSessionsNeedingBackup(sessions, txn) { + public async markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { if (!txn) { - txn = this._db.transaction("sessions_needing_backup", "readwrite"); + txn = this.db.transaction("sessions_needing_backup", "readwrite"); } const objectStore = txn.objectStore("sessions_needing_backup"); - return Promise.all(sessions.map((session) => { + await Promise.all(sessions.map((session) => { return new Promise((resolve, reject) => { const req = objectStore.put({ senderCurve25519Key: session.senderKey, @@ -759,9 +822,14 @@ export class Backend { })); } - addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) { + public addSharedHistoryInboundGroupSession( + roomId: string, + senderKey: string, + sessionId: string, + txn?: IDBTransaction, + ): void { if (!txn) { - txn = this._db.transaction( + txn = this.db.transaction( "shared_history_inbound_group_sessions", "readwrite", ); } @@ -774,9 +842,12 @@ export class Backend { }; } - getSharedHistoryInboundGroupSessions(roomId, txn) { + public getSharedHistoryInboundGroupSessions( + roomId: string, + txn?: IDBTransaction, + ): Promise<[senderKey: string, sessionId: string][]> { if (!txn) { - txn = this._db.transaction( + txn = this.db.transaction( "shared_history_inbound_group_sessions", "readonly", ); } @@ -791,16 +862,21 @@ export class Backend { }); } - doTxn(mode, stores, func, log = logger) { + public doTxn( + mode: Mode, + stores: Iterable, + func: (txn: IDBTransaction) => T, + log: PrefixedLogger = logger, + ): Promise { let startTime; let description; if (PROFILE_TRANSACTIONS) { - const txnId = this._nextTxnId++; + const txnId = this.nextTxnId++; startTime = Date.now(); description = `${mode} crypto store transaction ${txnId} in ${stores}`; log.debug(`Starting ${description}`); } - const txn = this._db.transaction(stores, mode); + const txn = this.db.transaction(stores, mode); const promise = promiseifyTxn(txn); const result = func(txn); if (PROFILE_TRANSACTIONS) { @@ -818,7 +894,7 @@ export class Backend { } } -export function upgradeDatabase(db, oldVersion) { +export function upgradeDatabase(db: IDBDatabase, oldVersion: number): void { logger.log( `Upgrading IndexedDBCryptoStore from version ${oldVersion}` + ` to ${VERSION}`, @@ -874,7 +950,7 @@ export function upgradeDatabase(db, oldVersion) { // Expand as needed. } -function createDatabase(db) { +function createDatabase(db: IDBDatabase): void { const outgoingRoomKeyRequestsStore = db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" }); @@ -887,15 +963,19 @@ function createDatabase(db) { outgoingRoomKeyRequestsStore.createIndex("state", "state"); } +interface IWrappedIDBTransaction extends IDBTransaction { + _mx_abortexception: Error; // eslint-disable-line camelcase +} + /* * Aborts a transaction with a given exception * The transaction promise will be rejected with this exception. */ -function abortWithException(txn, e) { +function abortWithException(txn: IDBTransaction, e: Error) { // We cheekily stick our exception onto the transaction object here // We could alternatively make the thing we pass back to the app // an object containing the transaction and exception. - txn._mx_abortexception = e; + (txn as IWrappedIDBTransaction)._mx_abortexception = e; try { txn.abort(); } catch (e) { @@ -904,28 +984,28 @@ function abortWithException(txn, e) { } } -function promiseifyTxn(txn) { +function promiseifyTxn(txn: IDBTransaction): Promise { return new Promise((resolve, reject) => { txn.oncomplete = () => { - if (txn._mx_abortexception !== undefined) { - reject(txn._mx_abortexception); + if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) { + reject((txn as IWrappedIDBTransaction)._mx_abortexception); } - resolve(); + resolve(null); }; txn.onerror = (event) => { - if (txn._mx_abortexception !== undefined) { - reject(txn._mx_abortexception); + if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) { + reject((txn as IWrappedIDBTransaction)._mx_abortexception); } else { logger.log("Error performing indexeddb txn", event); - reject(event.target.error); + reject(txn.error); } }; txn.onabort = (event) => { - if (txn._mx_abortexception !== undefined) { - reject(txn._mx_abortexception); + if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) { + reject((txn as IWrappedIDBTransaction)._mx_abortexception); } else { logger.log("Error performing indexeddb txn", event); - reject(event.target.error); + reject(txn.error); } }; }); diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.ts similarity index 62% rename from src/crypto/store/indexeddb-crypto-store.js rename to src/crypto/store/indexeddb-crypto-store.ts index 02a99e4e2..18008ae72 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,12 +14,28 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { logger } from '../../logger'; +import { logger, PrefixedLogger } from '../../logger'; import { LocalStorageCryptoStore } from './localStorage-crypto-store'; import { MemoryCryptoStore } from './memory-crypto-store'; import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend'; import { InvalidCryptoStoreError } from '../../errors'; import * as IndexedDBHelpers from "../../indexeddb-helpers"; +import { + CryptoStore, + IDeviceData, + IProblem, + ISession, + ISessionInfo, + IWithheld, + Mode, + OutgoingRoomKeyRequest, +} from "./base"; +import { IRoomKeyRequestBody } from "../index"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. indexeddb storage for e2e. @@ -35,23 +49,30 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers"; * * @implements {module:crypto/store/base~CryptoStore} */ -export class IndexedDBCryptoStore { +export class IndexedDBCryptoStore implements CryptoStore { + public static STORE_ACCOUNT = 'account'; + public static STORE_SESSIONS = 'sessions'; + public static STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions'; + public static STORE_INBOUND_GROUP_SESSIONS_WITHHELD = 'inbound_group_sessions_withheld'; + public static STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS = 'shared_history_inbound_group_sessions'; + public static STORE_DEVICE_DATA = 'device_data'; + public static STORE_ROOMS = 'rooms'; + public static STORE_BACKUP = 'sessions_needing_backup'; + + public static exists(indexedDB: IDBFactory, dbName: string): Promise { + return IndexedDBHelpers.exists(indexedDB, dbName); + } + + private backendPromise: Promise = null; + private backend: CryptoStore = null; + /** * Create a new IndexedDBCryptoStore * * @param {IDBFactory} indexedDB global indexedDB instance * @param {string} dbName name of db to connect to */ - constructor(indexedDB, dbName) { - this._indexedDB = indexedDB; - this._dbName = dbName; - this._backendPromise = null; - this._backend = null; - } - - static exists(indexedDB, dbName) { - return IndexedDBHelpers.exists(indexedDB, dbName); - } + constructor(private readonly indexedDB: IDBFactory, private readonly dbName: string) {} /** * Ensure the database exists and is up-to-date, or fall back to @@ -62,25 +83,23 @@ export class IndexedDBCryptoStore { * @return {Promise} resolves to either an IndexedDBCryptoStoreBackend.Backend, * or a MemoryCryptoStore */ - startup() { - if (this._backendPromise) { - return this._backendPromise; + public startup(): Promise { + if (this.backendPromise) { + return this.backendPromise; } - this._backendPromise = new Promise((resolve, reject) => { - if (!this._indexedDB) { + this.backendPromise = new Promise((resolve, reject) => { + if (!this.indexedDB) { reject(new Error('no indexeddb support available')); return; } - logger.log(`connecting to indexeddb ${this._dbName}`); + logger.log(`connecting to indexeddb ${this.dbName}`); - const req = this._indexedDB.open( - this._dbName, IndexedDBCryptoStoreBackend.VERSION, - ); + const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION); req.onupgradeneeded = (ev) => { - const db = ev.target.result; + const db = req.result; const oldVersion = ev.oldVersion; IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion); }; @@ -93,13 +112,13 @@ export class IndexedDBCryptoStore { req.onerror = (ev) => { logger.log("Error connecting to indexeddb", ev); - reject(ev.target.error); + reject(req.error); }; - req.onsuccess = (r) => { - const db = r.target.result; + req.onsuccess = () => { + const db = req.result; - logger.log(`connected to indexeddb ${this._dbName}`); + logger.log(`connected to indexeddb ${this.dbName}`); resolve(new IndexedDBCryptoStoreBackend.Backend(db)); }; }).then((backend) => { @@ -114,9 +133,7 @@ export class IndexedDBCryptoStore { ], (txn) => { backend.getEndToEndInboundGroupSession('', '', txn, () => {}); - }).then(() => { - return backend; - }, + }).then(() => backend, ); }).catch((e) => { if (e.name === 'VersionError') { @@ -126,7 +143,7 @@ export class IndexedDBCryptoStore { throw new InvalidCryptoStoreError(InvalidCryptoStoreError.TOO_NEW); } logger.warn( - `unable to connect to indexeddb ${this._dbName}` + + `unable to connect to indexeddb ${this.dbName}` + `: falling back to localStorage store: ${e}`, ); @@ -139,10 +156,11 @@ export class IndexedDBCryptoStore { return new MemoryCryptoStore(); } }).then(backend => { - this._backend = backend; + this.backend = backend; + return backend as CryptoStore; }); - return this._backendPromise; + return this.backendPromise; } /** @@ -150,15 +168,15 @@ export class IndexedDBCryptoStore { * * @returns {Promise} resolves when the store has been cleared. */ - deleteAllData() { - return new Promise((resolve, reject) => { - if (!this._indexedDB) { + public deleteAllData(): Promise { + return new Promise((resolve, reject) => { + if (!this.indexedDB) { reject(new Error('no indexeddb support available')); return; } - logger.log(`Removing indexeddb instance: ${this._dbName}`); - const req = this._indexedDB.deleteDatabase(this._dbName); + logger.log(`Removing indexeddb instance: ${this.dbName}`); + const req = this.indexedDB.deleteDatabase(this.dbName); req.onblocked = () => { logger.log( @@ -168,11 +186,11 @@ export class IndexedDBCryptoStore { req.onerror = (ev) => { logger.log("Error deleting data from indexeddb", ev); - reject(ev.target.error); + reject(req.error); }; req.onsuccess = () => { - logger.log(`Removed indexeddb instance: ${this._dbName}`); + logger.log(`Removed indexeddb instance: ${this.dbName}`); resolve(); }; }).catch((e) => { @@ -193,8 +211,8 @@ export class IndexedDBCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the * same instance as passed in, or the existing one. */ - getOrAddOutgoingRoomKeyRequest(request) { - return this._backend.getOrAddOutgoingRoomKeyRequest(request); + public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { + return this.backend.getOrAddOutgoingRoomKeyRequest(request); } /** @@ -207,8 +225,8 @@ export class IndexedDBCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found */ - getOutgoingRoomKeyRequest(requestBody) { - return this._backend.getOutgoingRoomKeyRequest(requestBody); + public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { + return this.backend.getOutgoingRoomKeyRequest(requestBody); } /** @@ -221,8 +239,8 @@ export class IndexedDBCryptoStore { * there are no pending requests in those states. If there are multiple * requests in those states, an arbitrary one is chosen. */ - getOutgoingRoomKeyRequestByState(wantedStates) { - return this._backend.getOutgoingRoomKeyRequestByState(wantedStates); + public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise { + return this.backend.getOutgoingRoomKeyRequestByState(wantedStates); } /** @@ -232,8 +250,8 @@ export class IndexedDBCryptoStore { * @param {Number} wantedState * @return {Promise>} Returns an array of requests in the given state */ - getAllOutgoingRoomKeyRequestsByState(wantedState) { - return this._backend.getAllOutgoingRoomKeyRequestsByState(wantedState); + public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise { + return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState); } /** @@ -246,8 +264,12 @@ export class IndexedDBCryptoStore { * @return {Promise} resolves to a list of all the * {@link module:crypto/store/base~OutgoingRoomKeyRequest} */ - getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { - return this._backend.getOutgoingRoomKeyRequestsByTarget( + public getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise { + return this.backend.getOutgoingRoomKeyRequestsByTarget( userId, deviceId, wantedStates, ); } @@ -264,8 +286,12 @@ export class IndexedDBCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest} * updated request, or null if no matching row was found */ - updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { - return this._backend.updateOutgoingRoomKeyRequest( + public updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise { + return this.backend.updateOutgoingRoomKeyRequest( requestId, expectedState, updates, ); } @@ -279,8 +305,11 @@ export class IndexedDBCryptoStore { * * @returns {Promise} resolves once the operation is completed */ - deleteOutgoingRoomKeyRequest(requestId, expectedState) { - return this._backend.deleteOutgoingRoomKeyRequest(requestId, expectedState); + public deleteOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + ): Promise { + return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState); } // Olm Account @@ -292,8 +321,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(string)} func Called with the account pickle */ - getAccount(txn, func) { - this._backend.getAccount(txn, func); + public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void) { + this.backend.getAccount(txn, func); } /** @@ -301,10 +330,10 @@ export class IndexedDBCryptoStore { * This requires an active transaction. See doTxn(). * * @param {*} txn An active transaction. See doTxn(). - * @param {string} newData The new account pickle to store. + * @param {string} accountPickle The new account pickle to store. */ - storeAccount(txn, newData) { - this._backend.storeAccount(txn, newData); + public storeAccount(txn: IDBTransaction, accountPickle: string): void { + this.backend.storeAccount(txn, accountPickle); } /** @@ -315,8 +344,8 @@ export class IndexedDBCryptoStore { * @param {function(string)} func Called with the account keys object: * { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed */ - getCrossSigningKeys(txn, func) { - this._backend.getCrossSigningKeys(txn, func); + public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record) => void): void { + this.backend.getCrossSigningKeys(txn, func); } /** @@ -324,8 +353,12 @@ export class IndexedDBCryptoStore { * @param {function(string)} func Called with the private key * @param {string} type A key type */ - getSecretStorePrivateKey(txn, func, type) { - this._backend.getSecretStorePrivateKey(txn, func, type); + public getSecretStorePrivateKey( + txn: IDBTransaction, + func: (key: IEncryptedPayload | null) => T, + type: string, + ): void { + this.backend.getSecretStorePrivateKey(txn, func, type); } /** @@ -334,8 +367,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {string} keys keys object as getCrossSigningKeys() */ - storeCrossSigningKeys(txn, keys) { - this._backend.storeCrossSigningKeys(txn, keys); + public storeCrossSigningKeys(txn: IDBTransaction, keys: Record): void { + this.backend.storeCrossSigningKeys(txn, keys); } /** @@ -345,8 +378,8 @@ export class IndexedDBCryptoStore { * @param {string} type The type of cross-signing private key to store * @param {string} key keys object as getCrossSigningKeys() */ - storeSecretStorePrivateKey(txn, type, key) { - this._backend.storeSecretStorePrivateKey(txn, type, key); + public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void { + this.backend.storeSecretStorePrivateKey(txn, type, key); } // Olm sessions @@ -356,8 +389,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(int)} func Called with the count of sessions */ - countEndToEndSessions(txn, func) { - this._backend.countEndToEndSessions(txn, func); + public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void { + this.backend.countEndToEndSessions(txn, func); } /** @@ -372,8 +405,13 @@ export class IndexedDBCryptoStore { * timestamp in milliseconds at which the session last received * a message. */ - getEndToEndSession(deviceKey, sessionId, txn, func) { - this._backend.getEndToEndSession(deviceKey, sessionId, txn, func); + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: IDBTransaction, + func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void, + ): void { + this.backend.getEndToEndSession(deviceKey, sessionId, txn, func); } /** @@ -387,8 +425,12 @@ export class IndexedDBCryptoStore { * timestamp in milliseconds at which the session last received * a message. */ - getEndToEndSessions(deviceKey, txn, func) { - this._backend.getEndToEndSessions(deviceKey, txn, func); + public getEndToEndSessions( + deviceKey: string, + txn: IDBTransaction, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { + this.backend.getEndToEndSessions(deviceKey, txn, func); } /** @@ -398,8 +440,8 @@ export class IndexedDBCryptoStore { * an object with, deviceKey, lastReceivedMessageTs, sessionId * and session keys. */ - getAllEndToEndSessions(txn, func) { - this._backend.getAllEndToEndSessions(txn, func); + public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void { + this.backend.getAllEndToEndSessions(txn, func); } /** @@ -409,22 +451,25 @@ export class IndexedDBCryptoStore { * @param {string} sessionInfo Session information object * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { - this._backend.storeEndToEndSession( - deviceKey, sessionId, sessionInfo, txn, - ); + public storeEndToEndSession( + deviceKey: string, + sessionId: string, + sessionInfo: ISessionInfo, + txn: IDBTransaction, + ): void { + this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn); } - storeEndToEndSessionProblem(deviceKey, type, fixed) { - return this._backend.storeEndToEndSessionProblem(deviceKey, type, fixed); + public storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { + return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed); } - getEndToEndSessionProblem(deviceKey, timestamp) { - return this._backend.getEndToEndSessionProblem(deviceKey, timestamp); + public getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { + return this.backend.getEndToEndSessionProblem(deviceKey, timestamp); } - filterOutNotifiedErrorDevices(devices) { - return this._backend.filterOutNotifiedErrorDevices(devices); + public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + return this.backend.filterOutNotifiedErrorDevices(devices); } // Inbound group sessions @@ -438,10 +483,13 @@ export class IndexedDBCryptoStore { * @param {function(object)} func Called with A map from sessionId * to Base64 end-to-end session. */ - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { - this._backend.getEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, txn, func, - ); + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: IDBTransaction, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { + this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func); } /** @@ -451,8 +499,11 @@ export class IndexedDBCryptoStore { * in the store with an object having keys {senderKey, sessionId, * sessionData}, then once with null to indicate the end of the list. */ - getAllEndToEndInboundGroupSessions(txn, func) { - this._backend.getAllEndToEndInboundGroupSessions(txn, func); + public getAllEndToEndInboundGroupSessions( + txn: IDBTransaction, + func: (session: ISession | null) => void, + ): void { + this.backend.getAllEndToEndInboundGroupSessions(txn, func); } /** @@ -464,10 +515,13 @@ export class IndexedDBCryptoStore { * @param {object} sessionData The session data structure * @param {*} txn An active transaction. See doTxn(). */ - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { - this._backend.addEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, sessionData, txn, - ); + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { + this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); } /** @@ -479,18 +533,22 @@ export class IndexedDBCryptoStore { * @param {object} sessionData The session data structure * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { - this._backend.storeEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, sessionData, txn, - ); + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { + this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { - this._backend.storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ); + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: IDBTransaction, + ): void { + this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn); } // End-to-end device tracking @@ -505,8 +563,8 @@ export class IndexedDBCryptoStore { * @param {Object} deviceData * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndDeviceData(deviceData, txn) { - this._backend.storeEndToEndDeviceData(deviceData, txn); + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void { + this.backend.storeEndToEndDeviceData(deviceData, txn); } /** @@ -516,8 +574,8 @@ export class IndexedDBCryptoStore { * @param {function(Object)} func Function called with the * device data */ - getEndToEndDeviceData(txn, func) { - this._backend.getEndToEndDeviceData(txn, func); + public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void { + this.backend.getEndToEndDeviceData(txn, func); } // End to End Rooms @@ -528,8 +586,8 @@ export class IndexedDBCryptoStore { * @param {object} roomInfo The end-to-end info for the room. * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndRoom(roomId, roomInfo, txn) { - this._backend.storeEndToEndRoom(roomId, roomInfo, txn); + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void { + this.backend.storeEndToEndRoom(roomId, roomInfo, txn); } /** @@ -537,20 +595,20 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(Object)} func Function called with the end to end encrypted rooms */ - getEndToEndRooms(txn, func) { - this._backend.getEndToEndRooms(txn, func); + public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record) => void): void { + this.backend.getEndToEndRooms(txn, func); } // session backups /** * Get the inbound group sessions that need to be backed up. - * @param {integer} limit The maximum number of sessions to retrieve. 0 + * @param {number} limit The maximum number of sessions to retrieve. 0 * for no limit. * @returns {Promise} resolves to an array of inbound group sessions */ - getSessionsNeedingBackup(limit) { - return this._backend.getSessionsNeedingBackup(limit); + public getSessionsNeedingBackup(limit: number): Promise { + return this.backend.getSessionsNeedingBackup(limit); } /** @@ -558,8 +616,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves to the number of sessions */ - countSessionsNeedingBackup(txn) { - return this._backend.countSessionsNeedingBackup(txn); + public countSessionsNeedingBackup(txn?: IDBTransaction): Promise { + return this.backend.countSessionsNeedingBackup(txn); } /** @@ -568,8 +626,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves when the sessions are unmarked */ - unmarkSessionsNeedingBackup(sessions, txn) { - return this._backend.unmarkSessionsNeedingBackup(sessions, txn); + public unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { + return this.backend.unmarkSessionsNeedingBackup(sessions, txn); } /** @@ -578,8 +636,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves when the sessions are marked */ - markSessionsNeedingBackup(sessions, txn) { - return this._backend.markSessionsNeedingBackup(sessions, txn); + public markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { + return this.backend.markSessionsNeedingBackup(sessions, txn); } /** @@ -589,10 +647,13 @@ export class IndexedDBCryptoStore { * @param {string} sessionId The ID of the session * @param {*} txn An active transaction. See doTxn(). (optional) */ - addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) { - this._backend.addSharedHistoryInboundGroupSession( - roomId, senderKey, sessionId, txn, - ); + public addSharedHistoryInboundGroupSession( + roomId: string, + senderKey: string, + sessionId: string, + txn?: IDBTransaction, + ): void { + this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn); } /** @@ -601,8 +662,11 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} Resolves to an array of [senderKey, sessionId] */ - getSharedHistoryInboundGroupSessions(roomId, txn) { - return this._backend.getSharedHistoryInboundGroupSessions(roomId, txn); + public getSharedHistoryInboundGroupSessions( + roomId: string, + txn?: IDBTransaction, + ): Promise<[senderKey: string, sessionId: string][]> { + return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn); } /** @@ -627,18 +691,7 @@ export class IndexedDBCryptoStore { * reject with that exception. On synchronous backends, the * exception will propagate to the caller of the getFoo method. */ - doTxn(mode, stores, func, log) { - return this._backend.doTxn(mode, stores, func, log); + doTxn(mode: Mode, stores: Iterable, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise { + return this.backend.doTxn(mode, stores, func, log); } } - -IndexedDBCryptoStore.STORE_ACCOUNT = 'account'; -IndexedDBCryptoStore.STORE_SESSIONS = 'sessions'; -IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions'; -IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD - = 'inbound_group_sessions_withheld'; -IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS - = 'shared_history_inbound_group_sessions'; -IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data'; -IndexedDBCryptoStore.STORE_ROOMS = 'rooms'; -IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup'; diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.ts similarity index 62% rename from src/crypto/store/localStorage-crypto-store.js rename to src/crypto/store/localStorage-crypto-store.ts index 0a982311e..2a96a7123 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -1,6 +1,5 @@ /* -Copyright 2017, 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +16,12 @@ limitations under the License. import { logger } from '../../logger'; import { MemoryCryptoStore } from './memory-crypto-store'; +import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode } from "./base"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { ICrossSigningKey } from "../../client"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. Partial localStorage backed storage for e2e. @@ -38,23 +43,23 @@ const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.w const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/"; const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup"; -function keyEndToEndSessions(deviceKey) { +function keyEndToEndSessions(deviceKey: string): string { return E2E_PREFIX + "sessions/" + deviceKey; } -function keyEndToEndSessionProblems(deviceKey) { +function keyEndToEndSessionProblems(deviceKey: string): string { return E2E_PREFIX + "session.problems/" + deviceKey; } -function keyEndToEndInboundGroupSession(senderKey, sessionId) { +function keyEndToEndInboundGroupSession(senderKey: string, sessionId: string): string { return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId; } -function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) { +function keyEndToEndInboundGroupSessionWithheld(senderKey: string, sessionId: string): string { return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId; } -function keyEndToEndRoomsPrefix(roomId) { +function keyEndToEndRoomsPrefix(roomId: string): string { return KEY_ROOMS_PREFIX + roomId; } @@ -62,24 +67,23 @@ function keyEndToEndRoomsPrefix(roomId) { * @implements {module:crypto/store/base~CryptoStore} */ export class LocalStorageCryptoStore extends MemoryCryptoStore { - constructor(webStore) { - super(); - this.store = webStore; - } - - static exists(webStore) { - const length = webStore.length; + public static exists(store: Storage): boolean { + const length = store.length; for (let i = 0; i < length; i++) { - if (webStore.key(i).startsWith(E2E_PREFIX)) { + if (store.key(i).startsWith(E2E_PREFIX)) { return true; } } return false; } + constructor(private readonly store: Storage) { + super(); + } + // Olm Sessions - countEndToEndSessions(txn, func) { + public countEndToEndSessions(txn: unknown, func: (count: number) => void): void { let count = 0; for (let i = 0; i < this.store.length; ++i) { if (this.store.key(i).startsWith(keyEndToEndSessions(''))) ++count; @@ -87,9 +91,10 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(count); } - _getEndToEndSessions(deviceKey, txn, func) { + // eslint-disable-next-line @typescript-eslint/naming-convention + private _getEndToEndSessions(deviceKey: string): Record { const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey)); - const fixedSessions = {}; + const fixedSessions: Record = {}; // fix up any old sessions to be objects rather than just the base64 pickle for (const [sid, val] of Object.entries(sessions || {})) { @@ -105,16 +110,25 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { return fixedSessions; } - getEndToEndSession(deviceKey, sessionId, txn, func) { + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: unknown, + func: (session: ISessionInfo) => void, + ): void { const sessions = this._getEndToEndSessions(deviceKey); func(sessions[sessionId] || {}); } - getEndToEndSessions(deviceKey, txn, func) { + public getEndToEndSessions( + deviceKey: string, + txn: unknown, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { func(this._getEndToEndSessions(deviceKey) || {}); } - getAllEndToEndSessions(txn, func) { + public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void { for (let i = 0; i < this.store.length; ++i) { if (this.store.key(i).startsWith(keyEndToEndSessions(''))) { const deviceKey = this.store.key(i).split('/')[1]; @@ -125,17 +139,15 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } } - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void { const sessions = this._getEndToEndSessions(deviceKey) || {}; sessions[sessionId] = sessionInfo; - setJsonItem( - this.store, keyEndToEndSessions(deviceKey), sessions, - ); + setJsonItem(this.store, keyEndToEndSessions(deviceKey), sessions); } - async storeEndToEndSessionProblem(deviceKey, type, fixed) { + public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { const key = keyEndToEndSessionProblems(deviceKey); - const problems = getJsonItem(this.store, key) || []; + const problems = getJsonItem(this.store, key) || []; problems.push({ type, fixed, time: Date.now() }); problems.sort((a, b) => { return a.time - b.time; @@ -143,9 +155,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { setJsonItem(this.store, key, problems); } - async getEndToEndSessionProblem(deviceKey, timestamp) { + async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { const key = keyEndToEndSessionProblems(deviceKey); - const problems = getJsonItem(this.store, key) || []; + const problems = getJsonItem(this.store, key) || []; if (!problems.length) { return null; } @@ -162,9 +174,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } } - async filterOutNotifiedErrorDevices(devices) { - const notifiedErrorDevices = - getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {}; + public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + const notifiedErrorDevices = getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {}; const ret = []; for (const device of devices) { @@ -187,7 +198,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { // Inbound Group Sessions - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: unknown, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { func( getJsonItem( this.store, @@ -200,7 +216,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { ); } - getAllEndToEndInboundGroupSessions(txn, func) { + public getAllEndToEndInboundGroupSessions(txn: unknown, func: (session: ISession | null) => void): void { for (let i = 0; i < this.store.length; ++i) { const key = this.store.key(i); if (key.startsWith(KEY_INBOUND_SESSION_PREFIX)) { @@ -219,7 +235,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(null); } - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { const existing = getJsonItem( this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), @@ -231,7 +252,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } } - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { setJsonItem( this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), @@ -239,9 +265,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { ); } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: unknown, + ): void { setJsonItem( this.store, keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId), @@ -249,25 +278,19 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { ); } - getEndToEndDeviceData(txn, func) { - func(getJsonItem( - this.store, KEY_DEVICE_DATA, - )); + public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void { + func(getJsonItem(this.store, KEY_DEVICE_DATA)); } - storeEndToEndDeviceData(deviceData, txn) { - setJsonItem( - this.store, KEY_DEVICE_DATA, deviceData, - ); + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void { + setJsonItem(this.store, KEY_DEVICE_DATA, deviceData); } - storeEndToEndRoom(roomId, roomInfo, txn) { - setJsonItem( - this.store, keyEndToEndRoomsPrefix(roomId), roomInfo, - ); + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void { + setJsonItem(this.store, keyEndToEndRoomsPrefix(roomId), roomInfo); } - getEndToEndRooms(txn, func) { + public getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void { const result = {}; const prefix = keyEndToEndRoomsPrefix(''); @@ -281,9 +304,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(result); } - getSessionsNeedingBackup(limit) { - const sessionsNeedingBackup - = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + public getSessionsNeedingBackup(limit: number): Promise { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; const sessions = []; for (const session in sessionsNeedingBackup) { @@ -309,13 +331,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { return Promise.resolve(sessions); } - countSessionsNeedingBackup() { - const sessionsNeedingBackup - = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + public countSessionsNeedingBackup(): Promise { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; return Promise.resolve(Object.keys(sessionsNeedingBackup).length); } - unmarkSessionsNeedingBackup(sessions) { + public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise { const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; for (const session of sessions) { @@ -327,7 +348,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { return Promise.resolve(); } - markSessionsNeedingBackup(sessions) { + public markSessionsNeedingBackup(sessions: ISession[]): Promise { const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; for (const session of sessions) { @@ -344,52 +365,46 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { * * @returns {Promise} Promise which resolves when the store has been cleared. */ - deleteAllData() { + public deleteAllData(): Promise { this.store.removeItem(KEY_END_TO_END_ACCOUNT); return Promise.resolve(); } // Olm account - getAccount(txn, func) { - const account = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT); - func(account); + public getAccount(txn: unknown, func: (accountPickle: string) => void): void { + const accountPickle = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT); + func(accountPickle); } - storeAccount(txn, newData) { - setJsonItem( - this.store, KEY_END_TO_END_ACCOUNT, newData, - ); + public storeAccount(txn: unknown, accountPickle: string): void { + setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle); } - getCrossSigningKeys(txn, func) { - const keys = getJsonItem(this.store, KEY_CROSS_SIGNING_KEYS); + public getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void { + const keys = getJsonItem>(this.store, KEY_CROSS_SIGNING_KEYS); func(keys); } - getSecretStorePrivateKey(txn, func, type) { - const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); + public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T { + const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); func(key); } - storeCrossSigningKeys(txn, keys) { - setJsonItem( - this.store, KEY_CROSS_SIGNING_KEYS, keys, - ); + public storeCrossSigningKeys(txn: unknown, keys: Record): void { + setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys); } - storeSecretStorePrivateKey(txn, type, key) { - setJsonItem( - this.store, E2E_PREFIX + `ssss_cache.${type}`, key, - ); + public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void { + setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key); } - doTxn(mode, stores, func) { + doTxn(mode: Mode, stores: Iterable, func: (txn: unknown) => T): Promise { return Promise.resolve(func(null)); } } -function getJsonItem(store, key) { +function getJsonItem(store: Storage, key: string): T | null { try { // if the key is absent, store.getItem() returns null, and // JSON.parse(null) === null, so this returns null. @@ -401,6 +416,6 @@ function getJsonItem(store, key) { return null; } -function setJsonItem(store, key, val) { +function setJsonItem(store: Storage, key: string, val: T): void { store.setItem(key, JSON.stringify(val)); } diff --git a/src/crypto/store/memory-crypto-store.js b/src/crypto/store/memory-crypto-store.ts similarity index 51% rename from src/crypto/store/memory-crypto-store.js rename to src/crypto/store/memory-crypto-store.ts index 9577fcad3..eca11b9e9 100644 --- a/src/crypto/store/memory-crypto-store.js +++ b/src/crypto/store/memory-crypto-store.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,6 +16,22 @@ limitations under the License. import { logger } from '../../logger'; import * as utils from "../../utils"; +import { + CryptoStore, + IDeviceData, + IProblem, + ISession, + ISessionInfo, + IWithheld, + Mode, + OutgoingRoomKeyRequest, +} from "./base"; +import { IRoomKeyRequestBody } from "../index"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. in-memory storage for e2e. @@ -28,32 +42,22 @@ import * as utils from "../../utils"; /** * @implements {module:crypto/store/base~CryptoStore} */ -export class MemoryCryptoStore { - constructor() { - this._outgoingRoomKeyRequests = []; - this._account = null; - this._crossSigningKeys = null; - this._privateKeys = {}; - this._backupKeys = {}; +export class MemoryCryptoStore implements CryptoStore { + private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = []; + private account: string = null; + private crossSigningKeys: Record = null; + private privateKeys: Record = {}; - // Map of {devicekey -> {sessionId -> session pickle}} - this._sessions = {}; - // Map of {devicekey -> array of problems} - this._sessionProblems = {}; - // Map of {userId -> deviceId -> true} - this._notifiedErrorDevices = {}; - // Map of {senderCurve25519Key+'/'+sessionId -> session data object} - this._inboundGroupSessions = {}; - this._inboundGroupSessionsWithheld = {}; - // Opaque device data object - this._deviceData = null; - // roomId -> Opaque roomInfo object - this._rooms = {}; - // Set of {senderCurve25519Key+'/'+sessionId} - this._sessionsNeedingBackup = {}; - // roomId -> array of [senderKey, sessionId] - this._sharedHistoryInboundGroupSessions = {}; - } + private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {}; + private sessionProblems: { [deviceKey: string]: IProblem[] } = {}; + private notifiedErrorDevices: { [userId: string]: { [deviceId: string]: boolean } } = {}; + private inboundGroupSessions: { [sessionKey: string]: InboundGroupSessionData } = {}; + private inboundGroupSessionsWithheld: Record = {}; + // Opaque device data object + private deviceData: IDeviceData = null; + private rooms: { [roomId: string]: IRoomEncryption } = {}; + private sessionsNeedingBackup: { [sessionKey: string]: boolean } = {}; + private sharedHistoryInboundGroupSessions: { [roomId: string]: [senderKey: string, sessionId: string][] } = {}; /** * Ensure the database exists and is up-to-date. @@ -62,7 +66,7 @@ export class MemoryCryptoStore { * * @return {Promise} resolves to the store. */ - async startup() { + public async startup(): Promise { // No startup work to do for the memory store. return this; } @@ -72,7 +76,7 @@ export class MemoryCryptoStore { * * @returns {Promise} Promise which resolves when the store has been cleared. */ - deleteAllData() { + public deleteAllData(): Promise { return Promise.resolve(); } @@ -86,7 +90,7 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the * same instance as passed in, or the existing one. */ - getOrAddOutgoingRoomKeyRequest(request) { + public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { const requestBody = request.requestBody; return utils.promiseTry(() => { @@ -109,7 +113,7 @@ export class MemoryCryptoStore { `enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id, ); - this._outgoingRoomKeyRequests.push(request); + this.outgoingRoomKeyRequests.push(request); return request; }); } @@ -124,7 +128,7 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found */ - getOutgoingRoomKeyRequest(requestBody) { + public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { return Promise.resolve(this._getOutgoingRoomKeyRequest(requestBody)); } @@ -139,8 +143,9 @@ export class MemoryCryptoStore { * @return {module:crypto/store/base~OutgoingRoomKeyRequest?} * the matching request, or null if not found */ - _getOutgoingRoomKeyRequest(requestBody) { - for (const existing of this._outgoingRoomKeyRequests) { + // eslint-disable-next-line @typescript-eslint/naming-convention + private _getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): OutgoingRoomKeyRequest | null { + for (const existing of this.outgoingRoomKeyRequests) { if (utils.deepCompare(existing.requestBody, requestBody)) { return existing; } @@ -157,8 +162,8 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * there are no pending requests in those states */ - getOutgoingRoomKeyRequestByState(wantedStates) { - for (const req of this._outgoingRoomKeyRequests) { + public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise { + for (const req of this.outgoingRoomKeyRequests) { for (const state of wantedStates) { if (req.state === state) { return Promise.resolve(req); @@ -173,18 +178,22 @@ export class MemoryCryptoStore { * @param {Number} wantedState * @return {Promise>} All OutgoingRoomKeyRequests in state */ - getAllOutgoingRoomKeyRequestsByState(wantedState) { + public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise { return Promise.resolve( - this._outgoingRoomKeyRequests.filter( + this.outgoingRoomKeyRequests.filter( (r) => r.state == wantedState, ), ); } - getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + public getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise { const results = []; - for (const req of this._outgoingRoomKeyRequests) { + for (const req of this.outgoingRoomKeyRequests) { for (const state of wantedStates) { if (req.state === state && req.recipients.includes({ userId, deviceId })) { results.push(req); @@ -206,8 +215,12 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest} * updated request, or null if no matching row was found */ - updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { - for (const req of this._outgoingRoomKeyRequests) { + public updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise { + for (const req of this.outgoingRoomKeyRequests) { if (req.requestId !== requestId) { continue; } @@ -235,9 +248,12 @@ export class MemoryCryptoStore { * * @returns {Promise} resolves once the operation is completed */ - deleteOutgoingRoomKeyRequest(requestId, expectedState) { - for (let i = 0; i < this._outgoingRoomKeyRequests.length; i++) { - const req = this._outgoingRoomKeyRequests[i]; + public deleteOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + ): Promise { + for (let i = 0; i < this.outgoingRoomKeyRequests.length; i++) { + const req = this.outgoingRoomKeyRequests[i]; if (req.requestId !== requestId) { continue; @@ -251,7 +267,7 @@ export class MemoryCryptoStore { return Promise.resolve(null); } - this._outgoingRoomKeyRequests.splice(i, 1); + this.outgoingRoomKeyRequests.splice(i, 1); return Promise.resolve(req); } @@ -260,48 +276,57 @@ export class MemoryCryptoStore { // Olm Account - getAccount(txn, func) { - func(this._account); + public getAccount(txn: unknown, func: (accountPickle: string) => void) { + func(this.account); } - storeAccount(txn, newData) { - this._account = newData; + public storeAccount(txn: unknown, accountPickle: string): void { + this.account = accountPickle; } - getCrossSigningKeys(txn, func) { - func(this._crossSigningKeys); + public getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void { + func(this.crossSigningKeys); } - getSecretStorePrivateKey(txn, func, type) { - const result = this._privateKeys[type]; + public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T { + const result = this.privateKeys[type]; return func(result || null); } - storeCrossSigningKeys(txn, keys) { - this._crossSigningKeys = keys; + public storeCrossSigningKeys(txn: unknown, keys: Record): void { + this.crossSigningKeys = keys; } - storeSecretStorePrivateKey(txn, type, key) { - this._privateKeys[type] = key; + public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void { + this.privateKeys[type] = key; } // Olm Sessions - countEndToEndSessions(txn, func) { - return Object.keys(this._sessions).length; + public countEndToEndSessions(txn: unknown, func: (count: number) => void): void { + return Object.keys(this.sessions).length; } - getEndToEndSession(deviceKey, sessionId, txn, func) { - const deviceSessions = this._sessions[deviceKey] || {}; + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: unknown, + func: (session: ISessionInfo) => void, + ): void { + const deviceSessions = this.sessions[deviceKey] || {}; func(deviceSessions[sessionId] || null); } - getEndToEndSessions(deviceKey, txn, func) { - func(this._sessions[deviceKey] || {}); + public getEndToEndSessions( + deviceKey: string, + txn: unknown, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { + func(this.sessions[deviceKey] || {}); } - getAllEndToEndSessions(txn, func) { - Object.entries(this._sessions).forEach(([deviceKey, deviceSessions]) => { + public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void { + Object.entries(this.sessions).forEach(([deviceKey, deviceSessions]) => { Object.entries(deviceSessions).forEach(([sessionId, session]) => { func({ ...session, @@ -312,26 +337,25 @@ export class MemoryCryptoStore { }); } - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { - let deviceSessions = this._sessions[deviceKey]; + public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void { + let deviceSessions = this.sessions[deviceKey]; if (deviceSessions === undefined) { deviceSessions = {}; - this._sessions[deviceKey] = deviceSessions; + this.sessions[deviceKey] = deviceSessions; } deviceSessions[sessionId] = sessionInfo; } - async storeEndToEndSessionProblem(deviceKey, type, fixed) { - const problems = this._sessionProblems[deviceKey] - = this._sessionProblems[deviceKey] || []; + public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { + const problems = this.sessionProblems[deviceKey] = this.sessionProblems[deviceKey] || []; problems.push({ type, fixed, time: Date.now() }); problems.sort((a, b) => { return a.time - b.time; }); } - async getEndToEndSessionProblem(deviceKey, timestamp) { - const problems = this._sessionProblems[deviceKey] || []; + public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { + const problems = this.sessionProblems[deviceKey] || []; if (!problems.length) { return null; } @@ -348,9 +372,9 @@ export class MemoryCryptoStore { } } - async filterOutNotifiedErrorDevices(devices) { - const notifiedErrorDevices = this._notifiedErrorDevices; - const ret = []; + public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + const notifiedErrorDevices = this.notifiedErrorDevices; + const ret: IOlmDevice[] = []; for (const device of devices) { const { userId, deviceInfo } = device; @@ -370,16 +394,24 @@ export class MemoryCryptoStore { // Inbound Group Sessions - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: unknown, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { const k = senderCurve25519Key+'/'+sessionId; func( - this._inboundGroupSessions[k] || null, - this._inboundGroupSessionsWithheld[k] || null, + this.inboundGroupSessions[k] || null, + this.inboundGroupSessionsWithheld[k] || null, ); } - getAllEndToEndInboundGroupSessions(txn, func) { - for (const key of Object.keys(this._inboundGroupSessions)) { + public getAllEndToEndInboundGroupSessions( + txn: unknown, + func: (session: ISession | null) => void, + ): void { + for (const key of Object.keys(this.inboundGroupSessions)) { // we can't use split, as the components we are trying to split out // might themselves contain '/' characters. We rely on the // senderKey being a (32-byte) curve25519 key, base64-encoded @@ -388,58 +420,71 @@ export class MemoryCryptoStore { func({ senderKey: key.substr(0, 43), sessionId: key.substr(44), - sessionData: this._inboundGroupSessions[key], + sessionData: this.inboundGroupSessions[key], }); } func(null); } - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { const k = senderCurve25519Key+'/'+sessionId; - if (this._inboundGroupSessions[k] === undefined) { - this._inboundGroupSessions[k] = sessionData; + if (this.inboundGroupSessions[k] === undefined) { + this.inboundGroupSessions[k] = sessionData; } } - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { - this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData; + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { + this.inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData; } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: unknown, + ): void { const k = senderCurve25519Key+'/'+sessionId; - this._inboundGroupSessionsWithheld[k] = sessionData; + this.inboundGroupSessionsWithheld[k] = sessionData; } // Device Data - getEndToEndDeviceData(txn, func) { - func(this._deviceData); + public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void { + func(this.deviceData); } - storeEndToEndDeviceData(deviceData, txn) { - this._deviceData = deviceData; + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void { + this.deviceData = deviceData; } // E2E rooms - storeEndToEndRoom(roomId, roomInfo, txn) { - this._rooms[roomId] = roomInfo; + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void { + this.rooms[roomId] = roomInfo; } - getEndToEndRooms(txn, func) { - func(this._rooms); + public getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void { + func(this.rooms); } - getSessionsNeedingBackup(limit) { - const sessions = []; - for (const session in this._sessionsNeedingBackup) { - if (this._inboundGroupSessions[session]) { + public getSessionsNeedingBackup(limit: number): Promise { + const sessions: ISession[] = []; + for (const session in this.sessionsNeedingBackup) { + if (this.inboundGroupSessions[session]) { sessions.push({ senderKey: session.substr(0, 43), sessionId: session.substr(44), - sessionData: this._inboundGroupSessions[session], + sessionData: this.inboundGroupSessions[session], }); if (limit && session.length >= limit) { break; @@ -449,39 +494,39 @@ export class MemoryCryptoStore { return Promise.resolve(sessions); } - countSessionsNeedingBackup() { - return Promise.resolve(Object.keys(this._sessionsNeedingBackup).length); + public countSessionsNeedingBackup(): Promise { + return Promise.resolve(Object.keys(this.sessionsNeedingBackup).length); } - unmarkSessionsNeedingBackup(sessions) { + public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise { for (const session of sessions) { const sessionKey = session.senderKey + '/' + session.sessionId; - delete this._sessionsNeedingBackup[sessionKey]; + delete this.sessionsNeedingBackup[sessionKey]; } return Promise.resolve(); } - markSessionsNeedingBackup(sessions) { + public markSessionsNeedingBackup(sessions: ISession[]): Promise { for (const session of sessions) { const sessionKey = session.senderKey + '/' + session.sessionId; - this._sessionsNeedingBackup[sessionKey] = true; + this.sessionsNeedingBackup[sessionKey] = true; } return Promise.resolve(); } - addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId) { - const sessions = this._sharedHistoryInboundGroupSessions[roomId] || []; + public addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string): void { + const sessions = this.sharedHistoryInboundGroupSessions[roomId] || []; sessions.push([senderKey, sessionId]); - this._sharedHistoryInboundGroupSessions[roomId] = sessions; + this.sharedHistoryInboundGroupSessions[roomId] = sessions; } - getSharedHistoryInboundGroupSessions(roomId) { - return Promise.resolve(this._sharedHistoryInboundGroupSessions[roomId] || []); + public getSharedHistoryInboundGroupSessions(roomId: string): Promise<[senderKey: string, sessionId: string][]> { + return Promise.resolve(this.sharedHistoryInboundGroupSessions[roomId] || []); } // Session key backups - doTxn(mode, stores, func) { + public doTxn(mode: Mode, stores: Iterable, func: (txn?: unknown) => T): Promise { return Promise.resolve(func(null)); } } diff --git a/src/indexeddb-helpers.js b/src/indexeddb-helpers.ts similarity index 88% rename from src/indexeddb-helpers.js rename to src/indexeddb-helpers.ts index 01123338d..84de7c0e9 100644 --- a/src/indexeddb-helpers.js +++ b/src/indexeddb-helpers.ts @@ -22,8 +22,8 @@ limitations under the License. * @param {string} dbName The database name to test for * @returns {boolean} Whether the database exists */ -export function exists(indexedDB, dbName) { - return new Promise((resolve, reject) => { +export function exists(indexedDB: IDBFactory, dbName: string): Promise { + return new Promise((resolve, reject) => { let exists = true; const req = indexedDB.open(dbName); req.onupgradeneeded = () => { @@ -31,7 +31,7 @@ export function exists(indexedDB, dbName) { // should only fire if the DB did not exist before at any version. exists = false; }; - req.onblocked = () => reject(); + req.onblocked = () => reject(req.error); req.onsuccess = () => { const db = req.result; db.close(); @@ -45,6 +45,6 @@ export function exists(indexedDB, dbName) { } resolve(exists); }; - req.onerror = ev => reject(ev.target.error); + req.onerror = ev => reject(req.error); }); } diff --git a/src/logger.ts b/src/logger.ts index bd1f98b25..d80fb2048 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -62,7 +62,7 @@ log.methodFactory = function(methodName, logLevel, loggerName) { export const logger: PrefixedLogger = log.getLogger(DEFAULT_NAMESPACE); logger.setLevel(log.levels.DEBUG); -interface PrefixedLogger extends Logger { +export interface PrefixedLogger extends Logger { withPrefix?: (prefix: string) => PrefixedLogger; prefix?: string; } diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index 9a7e803bb..c5c25fc19 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -117,7 +117,7 @@ function reqAsCursorPromise(req: IDBRequest): Promise { dbName = "matrix-js-sdk:" + (dbName || "default"); return IndexedDBHelpers.exists(indexedDB, dbName); } diff --git a/src/store/indexeddb.ts b/src/store/indexeddb.ts index f41a898c6..51fa88d5f 100644 --- a/src/store/indexeddb.ts +++ b/src/store/indexeddb.ts @@ -47,7 +47,7 @@ interface IOpts extends IBaseOpts { } export class IndexedDBStore extends MemoryStore { - static exists(indexedDB: IDBFactory, dbName: string): boolean { + static exists(indexedDB: IDBFactory, dbName: string): Promise { return LocalIndexedDBStoreBackend.exists(indexedDB, dbName); }