From 3fcc56601b890a858794ca84da25fc8b752126ee Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 19 Dec 2024 22:53:58 +0000 Subject: [PATCH] Use mapped types for account data content (#4590) * Use mapped types around account data events Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Harden types for reading account data too Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Correct empty object type Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update src/secret-storage.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- spec/integ/matrix-client-syncing.spec.ts | 7 +++ spec/integ/sliding-sync-sdk.spec.ts | 7 +++ spec/unit/crypto/secrets.spec.ts | 7 +++ spec/unit/matrix-client.spec.ts | 6 ++ spec/unit/rust-crypto/rust-crypto.spec.ts | 7 ++- spec/unit/rust-crypto/secret-storage.spec.ts | 12 ++++ spec/unit/secret-storage.spec.ts | 28 +++++++--- src/@types/event.ts | 34 ++++++++++++ src/client.ts | 25 ++++++--- src/crypto/CrossSigning.ts | 2 +- src/crypto/EncryptionSetup.ts | 33 +++++------ src/crypto/SecretStorage.ts | 8 +-- src/crypto/index.ts | 7 ++- src/models/invites-ignorer-types.ts | 58 ++++++++++++++++++++ src/models/invites-ignorer.ts | 49 +++-------------- src/rust-crypto/rust-crypto.ts | 4 +- src/rust-crypto/secret-storage.ts | 4 +- src/secret-storage.ts | 57 +++++++++---------- 18 files changed, 235 insertions(+), 120 deletions(-) create mode 100644 src/models/invites-ignorer-types.ts diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index 2c0ab315bed..e8b9d6e52f2 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -50,6 +50,13 @@ import { THREAD_RELATION_TYPE } from "../../src/models/thread"; import { IActionsObject } from "../../src/pushprocessor"; import { KnownMembership } from "../../src/@types/membership"; +declare module "../../src/@types/event" { + interface AccountDataEvents { + a: {}; + b: {}; + } +} + describe("MatrixClient syncing", () => { const selfUserId = "@alice:localhost"; const selfAccessToken = "aseukfgwef"; diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index 7b080351a6f..643f4f7e1ba 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -45,6 +45,13 @@ import { emitPromise } from "../test-utils/test-utils"; import { defer } from "../../src/utils"; import { KnownMembership } from "../../src/@types/membership"; +declare module "../../src/@types/event" { + interface AccountDataEvents { + global_test: {}; + tester: {}; + } +} + describe("SlidingSyncSdk", () => { let client: MatrixClient | undefined; let httpBackend: MockHttpBackend | undefined; diff --git a/spec/unit/crypto/secrets.spec.ts b/spec/unit/crypto/secrets.spec.ts index 88b011870f0..097ee2b1b19 100644 --- a/spec/unit/crypto/secrets.spec.ts +++ b/spec/unit/crypto/secrets.spec.ts @@ -30,6 +30,7 @@ import { ICurve25519AuthData } from "../../../src/crypto/keybackup"; import { SecretStorageKeyDescription, SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage"; import { decodeBase64 } from "../../../src/base64"; import { CrossSigningKeyInfo } from "../../../src/crypto-api"; +import { SecretInfo } from "../../../src/secret-storage.ts"; async function makeTestClient( userInfo: { userId: string; deviceId: string }, @@ -68,6 +69,12 @@ function sign( }; } +declare module "../../../src/@types/event" { + interface SecretStorageAccountDataEvents { + foo: SecretInfo; + } +} + describe("Secrets", function () { if (!globalThis.Olm) { logger.warn("Not running megolm backup unit tests: libolm not present"); diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 6096090414e..8e55a0c65e5 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -94,6 +94,12 @@ function convertQueryDictToMap(queryDict?: QueryDict): Map { return new Map(Object.entries(queryDict).map(([k, v]) => [k, String(v)])); } +declare module "../../src/@types/event" { + interface AccountDataEvents { + "im.vector.test": {}; + } +} + type HttpLookup = { method: string; path: string; diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 8e177097bfe..1b56dfccbc3 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -30,6 +30,7 @@ import fetchMock from "fetch-mock-jest"; import { RustCrypto } from "../../../src/rust-crypto/rust-crypto"; import { initRustCrypto } from "../../../src/rust-crypto"; import { + AccountDataEvents, Device, DeviceVerification, encodeBase64, @@ -1924,11 +1925,13 @@ class DummyAccountDataClient super(); } - public async getAccountDataFromServer>(eventType: string): Promise { + public async getAccountDataFromServer( + eventType: K, + ): Promise { const ret = this.storage.get(eventType); if (eventType) { - return ret as T; + return ret; } else { return null; } diff --git a/spec/unit/rust-crypto/secret-storage.spec.ts b/spec/unit/rust-crypto/secret-storage.spec.ts index 0667dbd72e5..e54461a26e4 100644 --- a/spec/unit/rust-crypto/secret-storage.spec.ts +++ b/spec/unit/rust-crypto/secret-storage.spec.ts @@ -19,6 +19,18 @@ import { secretStorageContainsCrossSigningKeys, } from "../../../src/rust-crypto/secret-storage"; import { ServerSideSecretStorage } from "../../../src/secret-storage"; +import { SecretInfo } from "../../../src/secret-storage.ts"; + +declare module "../../../src/@types/event" { + interface SecretStorageAccountDataEvents { + secretA: SecretInfo; + secretB: SecretInfo; + secretC: SecretInfo; + secretD: SecretInfo; + secretE: SecretInfo; + Unknown: SecretInfo; + } +} describe("secret-storage", () => { describe("secretStorageContainsCrossSigningKeys", () => { diff --git a/spec/unit/secret-storage.spec.ts b/spec/unit/secret-storage.spec.ts index b2346d88e6c..6299141829e 100644 --- a/spec/unit/secret-storage.spec.ts +++ b/spec/unit/secret-storage.spec.ts @@ -27,6 +27,14 @@ import { trimTrailingEquals, } from "../../src/secret-storage"; import { randomString } from "../../src/randomstring"; +import { SecretInfo } from "../../src/secret-storage.ts"; +import { AccountDataEvents } from "../../src"; + +declare module "../../src/@types/event" { + interface SecretStorageAccountDataEvents { + mysecret: SecretInfo; + } +} describe("ServerSideSecretStorageImpl", function () { describe(".addKey", function () { @@ -117,9 +125,11 @@ describe("ServerSideSecretStorageImpl", function () { const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {}); const storedKey = { iv: "iv", mac: "mac" } as SecretStorageKeyDescriptionAesV1; - async function mockGetAccountData>(eventType: string): Promise { + async function mockGetAccountData( + eventType: string, + ): Promise { if (eventType === "m.secret_storage.key.my_key") { - return storedKey as unknown as T; + return storedKey as any; } else { throw new Error(`unexpected eventType ${eventType}`); } @@ -135,11 +145,13 @@ describe("ServerSideSecretStorageImpl", function () { const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {}); const storedKey = { iv: "iv", mac: "mac" } as SecretStorageKeyDescriptionAesV1; - async function mockGetAccountData>(eventType: string): Promise { + async function mockGetAccountData( + eventType: string, + ): Promise { if (eventType === "m.secret_storage.default_key") { - return { key: "default_key_id" } as unknown as T; + return { key: "default_key_id" } as any; } else if (eventType === "m.secret_storage.key.default_key_id") { - return storedKey as unknown as T; + return storedKey as any; } else { throw new Error(`unexpected eventType ${eventType}`); } @@ -236,9 +248,11 @@ describe("ServerSideSecretStorageImpl", function () { // stub out getAccountData to return a key with an unknown algorithm const storedKey = { algorithm: "badalg" } as SecretStorageKeyDescriptionCommon; - async function mockGetAccountData>(eventType: string): Promise { + async function mockGetAccountData( + eventType: string, + ): Promise { if (eventType === "m.secret_storage.key.keyid") { - return storedKey as unknown as T; + return storedKey as any; } else { throw new Error(`unexpected eventType ${eventType}`); } diff --git a/src/@types/event.ts b/src/@types/event.ts index 0d28b38fc32..fe7150652f5 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -58,6 +58,10 @@ import { import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types.ts"; import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls.ts"; import { SessionMembershipData } from "../matrixrtc/CallMembership.ts"; +import { LocalNotificationSettings } from "./local_notifications.ts"; +import { IPushRules } from "./PushRules.ts"; +import { SecretInfo, SecretStorageKeyDescription } from "../secret-storage.ts"; +import { POLICIES_ACCOUNT_EVENT_TYPE } from "../models/invites-ignorer-types.ts"; export enum EventType { // Room state events @@ -368,3 +372,33 @@ export interface StateEvents { // MSC3672 [M_BEACON_INFO.name]: MBeaconInfoEventContent; } + +/** + * Mapped type from event type to content type for all specified global account_data events. + */ +export interface AccountDataEvents extends SecretStorageAccountDataEvents { + [EventType.PushRules]: IPushRules; + [EventType.Direct]: { [userId: string]: string[] }; + [EventType.IgnoredUserList]: { [userId: string]: {} }; + "m.secret_storage.default_key": { key: string }; + "m.identity_server": { base_url: string | null }; + [key: `${typeof LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${string}`]: LocalNotificationSettings; + [key: `m.secret_storage.key.${string}`]: SecretStorageKeyDescription; + + // Invites-ignorer events + [POLICIES_ACCOUNT_EVENT_TYPE.name]: { [key: string]: any }; + [POLICIES_ACCOUNT_EVENT_TYPE.altName]: { [key: string]: any }; +} + +/** + * Mapped type from event type to content type for all specified global events encrypted by secret storage. + * + * See https://spec.matrix.org/v1.13/client-server-api/#msecret_storagev1aes-hmac-sha2-1 + */ +export interface SecretStorageAccountDataEvents { + "m.megolm_backup.v1": SecretInfo; + "m.cross_signing.master": SecretInfo; + "m.cross_signing.self_signing": SecretInfo; + "m.cross_signing.user_signing": SecretInfo; + "org.matrix.msc3814": SecretInfo; +} diff --git a/src/client.ts b/src/client.ts index 54f53094282..eea8faa708f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -136,6 +136,7 @@ import { UpdateDelayedEventAction, } from "./@types/requests.ts"; import { + AccountDataEvents, EventType, LOCAL_NOTIFICATION_SETTINGS_PREFIX, MSC3912_RELATION_BASED_REDACTIONS_PROP, @@ -232,6 +233,7 @@ import { import { DeviceInfoMap } from "./crypto/DeviceList.ts"; import { AddSecretStorageKeyOpts, + SecretStorageKey, SecretStorageKeyDescription, ServerSideSecretStorage, ServerSideSecretStorageImpl, @@ -3070,7 +3072,7 @@ export class MatrixClient extends TypedEventEmitter | null> { + public isSecretStored(name: SecretStorageKey): Promise | null> { return this.secretStorage.isStored(name); } @@ -4236,7 +4238,10 @@ export class MatrixClient extends TypedEventEmitter { + public setAccountData( + eventType: K, + content: AccountDataEvents[K] | Record, + ): Promise<{}> { const path = utils.encodeUri("/user/$userId/account_data/$type", { $userId: this.credentials.userId!, $type: eventType, @@ -4251,7 +4256,7 @@ export class MatrixClient extends TypedEventEmitter(eventType: K): MatrixEvent | undefined { return this.store.getAccountData(eventType); } @@ -4263,7 +4268,9 @@ export class MatrixClient extends TypedEventEmitter(eventType: string): Promise { + public async getAccountDataFromServer( + eventType: K, + ): Promise { if (this.isInitialSyncComplete()) { const event = this.store.getAccountData(eventType); if (!event) { @@ -4271,7 +4278,7 @@ export class MatrixClient extends TypedEventEmitter(); + return event.getContent(); } const path = utils.encodeUri("/user/$userId/account_data/$type", { $userId: this.credentials.userId!, @@ -4287,7 +4294,7 @@ export class MatrixClient extends TypedEventEmitter { + public async deleteAccountData(eventType: keyof AccountDataEvents): Promise { const msc3391DeleteAccountDataServerSupport = this.canSupport.get(Feature.AccountDataDeletion); // if deletion is not supported overwrite with empty content if (msc3391DeleteAccountDataServerSupport === ServerSupport.Unsupported) { @@ -4310,7 +4317,7 @@ export class MatrixClient extends TypedEventEmitter { content.ignored_users[u] = {}; }); - return this.setAccountData("m.ignored_user_list", content); + return this.setAccountData(EventType.IgnoredUserList, content); } /** @@ -9264,7 +9271,7 @@ export class MatrixClient extends TypedEventEmitter { - const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`; + const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}` as const; return this.setAccountData(key, notificationSettings); } diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 77abb6b2545..b3c5183f535 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -184,7 +184,7 @@ export class CrossSigningInfo { } } } - for (const type of ["self_signing", "user_signing"]) { + for (const type of ["self_signing", "user_signing"] as const) { intersect((await secretStorage.isStored(`m.cross_signing.${type}`)) || {}); } return Object.keys(stored).length ? stored : null; diff --git a/src/crypto/EncryptionSetup.ts b/src/crypto/EncryptionSetup.ts index bc51dd5ef3b..84831617775 100644 --- a/src/crypto/EncryptionSetup.ts +++ b/src/crypto/EncryptionSetup.ts @@ -25,6 +25,7 @@ import { IKeyBackupInfo } from "./keybackup.ts"; import { TypedEventEmitter } from "../models/typed-event-emitter.ts"; import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage.ts"; import { BootstrapCrossSigningOpts, CrossSigningKeyInfo } from "../crypto-api/index.ts"; +import { AccountDataEvents } from "../@types/event.ts"; interface ICrossSigningKeys { authUpload: BootstrapCrossSigningOpts["authUploadDeviceSigningKeys"]; @@ -111,7 +112,10 @@ export class EncryptionSetupBuilder { userSignatures[deviceId] = signature; } - public async setAccountData(type: string, content: object): Promise { + public async setAccountData( + type: K, + content: AccountDataEvents[K], + ): Promise { await this.accountDataClientAdapter.setAccountData(type, content); } @@ -160,7 +164,7 @@ export class EncryptionSetupOperation { /** */ public constructor( - private readonly accountData: Map, + private readonly accountData: Map, private readonly crossSigningKeys?: ICrossSigningKeys, private readonly keyBackupInfo?: IKeyBackupInfo, private readonly keySignatures?: KeySignatures, @@ -190,7 +194,7 @@ export class EncryptionSetupOperation { // set account data if (this.accountData) { for (const [type, content] of this.accountData) { - await baseApis.setAccountData(type, content); + await baseApis.setAccountData(type, content.getContent()); } } // upload first cross-signing signatures with the new key @@ -236,7 +240,7 @@ class AccountDataClientAdapter implements AccountDataClient { // - public readonly values = new Map(); + public readonly values = new Map(); /** * @param existingValues - existing account data @@ -248,33 +252,26 @@ class AccountDataClientAdapter /** * @returns the content of the account data */ - public getAccountDataFromServer(type: string): Promise { + public getAccountDataFromServer(type: K): Promise { return Promise.resolve(this.getAccountData(type)); } /** * @returns the content of the account data */ - public getAccountData(type: string): T | null { - const modifiedValue = this.values.get(type); - if (modifiedValue) { - return modifiedValue as unknown as T; - } - const existingValue = this.existingValues.get(type); - if (existingValue) { - return existingValue.getContent(); - } - return null; + public getAccountData(type: K): AccountDataEvents[K] | null { + const event = this.values.get(type) ?? this.existingValues.get(type); + return event?.getContent() ?? null; } - public setAccountData(type: string, content: any): Promise<{}> { + public setAccountData(type: K, content: AccountDataEvents[K]): Promise<{}> { + const event = new MatrixEvent({ type, content }); const lastEvent = this.values.get(type); - this.values.set(type, content); + this.values.set(type, event); // ensure accountData is emitted on the next tick, // as SecretStorage listens for it while calling this method // and it seems to rely on this. return Promise.resolve().then(() => { - const event = new MatrixEvent({ type, content }); this.emit(ClientEvent.AccountData, event, lastEvent); return {}; }); diff --git a/src/crypto/SecretStorage.ts b/src/crypto/SecretStorage.ts index 6bd653dd0c0..adfe63efcd2 100644 --- a/src/crypto/SecretStorage.ts +++ b/src/crypto/SecretStorage.ts @@ -25,12 +25,12 @@ import { AccountDataClient, ServerSideSecretStorage, ServerSideSecretStorageImpl, + SecretStorageKey, } from "../secret-storage.ts"; import { ISecretRequest, SecretSharing } from "./SecretSharing.ts"; /* re-exports for backwards compatibility */ export type { - AccountDataClient as IAccountDataClient, SecretStorageKeyTuple, SecretStorageKeyObject, SECRET_STORAGE_ALGORITHM_V1_AES, @@ -101,21 +101,21 @@ export class SecretStorage im /** * Store an encrypted secret on the server */ - public store(name: string, secret: string, keys?: string[] | null): Promise { + public store(name: SecretStorageKey, secret: string, keys?: string[] | null): Promise { return this.storageImpl.store(name, secret, keys); } /** * Get a secret from storage. */ - public get(name: string): Promise { + public get(name: SecretStorageKey): Promise { return this.storageImpl.get(name); } /** * Check if a secret is stored on the server. */ - public async isStored(name: string): Promise | null> { + public async isStored(name: SecretStorageKey): Promise | null> { return this.storageImpl.isStored(name); } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 557c1f5cad0..3af8d864364 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -77,6 +77,7 @@ import { AddSecretStorageKeyOpts, calculateKeyCheck, SECRET_STORAGE_ALGORITHM_V1_AES, + SecretStorageKey, SecretStorageKeyDescription, SecretStorageKeyObject, SecretStorageKeyTuple, @@ -1194,21 +1195,21 @@ export class Crypto extends TypedEventEmitter { + public storeSecret(name: SecretStorageKey, secret: string, keys?: string[]): Promise { return this.secretStorage.store(name, secret, keys); } /** * @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#get}. */ - public getSecret(name: string): Promise { + public getSecret(name: SecretStorageKey): Promise { return this.secretStorage.get(name); } /** * @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#isStored}. */ - public isSecretStored(name: string): Promise | null> { + public isSecretStored(name: SecretStorageKey): Promise | null> { return this.secretStorage.isStored(name); } diff --git a/src/models/invites-ignorer-types.ts b/src/models/invites-ignorer-types.ts new file mode 100644 index 00000000000..34d58432f0b --- /dev/null +++ b/src/models/invites-ignorer-types.ts @@ -0,0 +1,58 @@ +/* +Copyright 2022 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 { UnstableValue } from "matrix-events-sdk"; + +/// The event type storing the user's individual policies. +/// +/// Exported for testing purposes. +export const POLICIES_ACCOUNT_EVENT_TYPE = new UnstableValue("m.policies", "org.matrix.msc3847.policies"); + +/// The key within the user's individual policies storing the user's ignored invites. +/// +/// Exported for testing purposes. +export const IGNORE_INVITES_ACCOUNT_EVENT_KEY = new UnstableValue( + "m.ignore.invites", + "org.matrix.msc3847.ignore.invites", +); + +/// The types of recommendations understood. +export enum PolicyRecommendation { + Ban = "m.ban", +} + +/** + * The various scopes for policies. + */ +export enum PolicyScope { + /** + * The policy deals with an individual user, e.g. reject invites + * from this user. + */ + User = "m.policy.user", + + /** + * The policy deals with a room, e.g. reject invites towards + * a specific room. + */ + Room = "m.policy.room", + + /** + * The policy deals with a server, e.g. reject invites from + * this server. + */ + Server = "m.policy.server", +} diff --git a/src/models/invites-ignorer.ts b/src/models/invites-ignorer.ts index 31d54244330..2702fd02c7b 100644 --- a/src/models/invites-ignorer.ts +++ b/src/models/invites-ignorer.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { UnstableValue } from "matrix-events-sdk"; - import { MatrixClient } from "../client.ts"; import { IContent, MatrixEvent } from "./event.ts"; import { EventTimeline } from "./event-timeline.ts"; @@ -23,47 +21,14 @@ import { Preset } from "../@types/partials.ts"; import { globToRegexp } from "../utils.ts"; import { Room } from "./room.ts"; import { EventType, StateEvents } from "../@types/event.ts"; +import { + IGNORE_INVITES_ACCOUNT_EVENT_KEY, + POLICIES_ACCOUNT_EVENT_TYPE, + PolicyRecommendation, + PolicyScope, +} from "./invites-ignorer-types.ts"; -/// The event type storing the user's individual policies. -/// -/// Exported for testing purposes. -export const POLICIES_ACCOUNT_EVENT_TYPE = new UnstableValue("m.policies", "org.matrix.msc3847.policies"); - -/// The key within the user's individual policies storing the user's ignored invites. -/// -/// Exported for testing purposes. -export const IGNORE_INVITES_ACCOUNT_EVENT_KEY = new UnstableValue( - "m.ignore.invites", - "org.matrix.msc3847.ignore.invites", -); - -/// The types of recommendations understood. -export enum PolicyRecommendation { - Ban = "m.ban", -} - -/** - * The various scopes for policies. - */ -export enum PolicyScope { - /** - * The policy deals with an individual user, e.g. reject invites - * from this user. - */ - User = "m.policy.user", - - /** - * The policy deals with a room, e.g. reject invites towards - * a specific room. - */ - Room = "m.policy.room", - - /** - * The policy deals with a server, e.g. reject invites from - * this server. - */ - Server = "m.policy.server", -} +export { IGNORE_INVITES_ACCOUNT_EVENT_KEY, POLICIES_ACCOUNT_EVENT_TYPE, PolicyRecommendation, PolicyScope }; const scopeToEventTypeMap: Record = { [PolicyScope.User]: EventType.PolicyRuleUser, diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index e586510440e..02fe6270a17 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -71,7 +71,7 @@ import { import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts"; import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts"; import { Device, DeviceMap } from "../models/device.ts"; -import { SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../secret-storage.ts"; +import { SECRET_STORAGE_ALGORITHM_V1_AES, SecretStorageKey, ServerSideSecretStorage } from "../secret-storage.ts"; import { CrossSigningIdentity } from "./CrossSigningIdentity.ts"; import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts"; import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts"; @@ -770,7 +770,7 @@ export class RustCrypto extends TypedEventEmitter { // make sure that the cross-signing keys are stored - const secretsToCheck = [ + const secretsToCheck: SecretStorageKey[] = [ "m.cross_signing.master", "m.cross_signing.user_signing", "m.cross_signing.self_signing", diff --git a/src/rust-crypto/secret-storage.ts b/src/rust-crypto/secret-storage.ts index 951eae762df..b4b1083d0f4 100644 --- a/src/rust-crypto/secret-storage.ts +++ b/src/rust-crypto/secret-storage.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ServerSideSecretStorage } from "../secret-storage.ts"; +import { SecretStorageKey, ServerSideSecretStorage } from "../secret-storage.ts"; /** * Check that the private cross signing keys (master, self signing, user signing) are stored in the secret storage and encrypted with the default secret storage key. @@ -44,7 +44,7 @@ export async function secretStorageContainsCrossSigningKeys(secretStorage: Serve */ export async function secretStorageCanAccessSecrets( secretStorage: ServerSideSecretStorage, - secretNames: string[], + secretNames: SecretStorageKey[], ): Promise { const defaultKeyId = await secretStorage.getDefaultKeyId(); if (!defaultKeyId) return false; diff --git a/src/secret-storage.ts b/src/secret-storage.ts index 2aa8a028aa7..2d6cd8a2793 100644 --- a/src/secret-storage.ts +++ b/src/secret-storage.ts @@ -28,6 +28,7 @@ import { logger } from "./logger.ts"; import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts"; import decryptAESSecretStorageItem from "./utils/decryptAESSecretStorageItem.ts"; import { AESEncryptedSecretStoragePayload } from "./@types/AESEncryptedSecretStoragePayload.ts"; +import { AccountDataEvents, SecretStorageAccountDataEvents } from "./@types/event.ts"; export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2"; @@ -138,7 +139,7 @@ export interface AccountDataClient extends TypedEventEmitter>(eventType: string) => Promise; + getAccountDataFromServer: (eventType: K) => Promise; /** * Set account data event for the current user, with retries @@ -147,7 +148,7 @@ export interface AccountDataClient extends TypedEventEmitter Promise<{}>; + setAccountData: (eventType: K, content: AccountDataEvents[K]) => Promise<{}>; } /** @@ -200,7 +201,17 @@ export interface SecretStorageCallbacks { ) => Promise<[string, Uint8Array] | null>; } -interface SecretInfo { +/** + * Account Data event types which can store secret-storage-encrypted information. + */ +export type SecretStorageKey = keyof SecretStorageAccountDataEvents; + +/** + * Account Data event content type for storing secret-storage-encrypted information. + * + * See https://spec.matrix.org/v1.13/client-server-api/#msecret_storagev1aes-hmac-sha2-1 + */ +export interface SecretInfo { encrypted: { [keyId: string]: AESEncryptedSecretStoragePayload; }; @@ -293,7 +304,7 @@ export interface ServerSideSecretStorage { * with, or null if it is not present or not encrypted with a trusted * key */ - isStored(name: string): Promise | null>; + isStored(name: SecretStorageKey): Promise | null>; /** * Get the current default key ID for encrypting secrets. @@ -340,11 +351,9 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { * @returns The default key ID or null if no default key ID is set */ public async getDefaultKeyId(): Promise { - const defaultKey = await this.accountDataAdapter.getAccountDataFromServer<{ key: string }>( - "m.secret_storage.default_key", - ); + const defaultKey = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.default_key"); if (!defaultKey) return null; - return defaultKey.key; + return defaultKey.key ?? null; } /** @@ -409,11 +418,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { if (!keyId) { do { keyId = randomString(32); - } while ( - await this.accountDataAdapter.getAccountDataFromServer( - `m.secret_storage.key.${keyId}`, - ) - ); + } while (await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`)); } await this.accountDataAdapter.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo); @@ -441,9 +446,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { return null; } - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( - "m.secret_storage.key." + keyId, - ); + const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`); return keyInfo ? [keyId, keyInfo] : null; } @@ -492,7 +495,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { * @param secret - The secret contents. * @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key. */ - public async store(name: string, secret: string, keys?: string[] | null): Promise { + public async store(name: SecretStorageKey, secret: string, keys?: string[] | null): Promise { const encrypted: Record = {}; if (!keys) { @@ -509,9 +512,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { for (const keyId of keys) { // get key information from key storage - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( - "m.secret_storage.key." + keyId, - ); + const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`); if (!keyInfo) { throw new Error("Unknown key: " + keyId); } @@ -542,8 +543,8 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { * @returns the decrypted contents of the secret, or "undefined" if `name` is not found in * the user's account data. */ - public async get(name: string): Promise { - const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); + public async get(name: SecretStorageKey): Promise { + const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); if (!secretInfo) { return; } @@ -555,9 +556,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { const keys: Record = {}; for (const keyId of Object.keys(secretInfo.encrypted)) { // get key information from key storage - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( - "m.secret_storage.key." + keyId, - ); + const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`); const encInfo = secretInfo.encrypted[keyId]; // only use keys we understand the encryption algorithm of if (keyInfo?.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { @@ -590,9 +589,9 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { * with, or null if it is not present or not encrypted with a trusted * key */ - public async isStored(name: string): Promise | null> { + public async isStored(name: SecretStorageKey): Promise | null> { // check if secret exists - const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); + const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); if (!secretInfo?.encrypted) return null; const ret: Record = {}; @@ -600,9 +599,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage { // filter secret encryption keys with supported algorithm for (const keyId of Object.keys(secretInfo.encrypted)) { // get key information from key storage - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( - "m.secret_storage.key." + keyId, - ); + const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`); if (!keyInfo) continue; const encInfo = secretInfo.encrypted[keyId];