You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
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>
This commit is contained in:
committed by
GitHub
parent
bcf3d56bd5
commit
3fcc56601b
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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<T extends IObject | ICurve25519AuthData>(
|
||||
};
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -94,6 +94,12 @@ function convertQueryDictToMap(queryDict?: QueryDict): Map<string, string> {
|
||||
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;
|
||||
|
@ -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<T extends Record<string, any>>(eventType: string): Promise<T | null> {
|
||||
public async getAccountDataFromServer<K extends keyof AccountDataEvents>(
|
||||
eventType: K,
|
||||
): Promise<AccountDataEvents[K] | null> {
|
||||
const ret = this.storage.get(eventType);
|
||||
|
||||
if (eventType) {
|
||||
return ret as T;
|
||||
return ret;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -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", () => {
|
||||
|
@ -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<T extends Record<string, any>>(eventType: string): Promise<T | null> {
|
||||
async function mockGetAccountData<K extends keyof AccountDataEvents>(
|
||||
eventType: string,
|
||||
): Promise<AccountDataEvents[K] | null> {
|
||||
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<T extends Record<string, any>>(eventType: string): Promise<T | null> {
|
||||
async function mockGetAccountData<K extends keyof AccountDataEvents>(
|
||||
eventType: string,
|
||||
): Promise<AccountDataEvents[K] | null> {
|
||||
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<T extends Record<string, any>>(eventType: string): Promise<T | null> {
|
||||
async function mockGetAccountData<K extends keyof AccountDataEvents>(
|
||||
eventType: string,
|
||||
): Promise<AccountDataEvents[K] | null> {
|
||||
if (eventType === "m.secret_storage.key.keyid") {
|
||||
return storedKey as unknown as T;
|
||||
return storedKey as any;
|
||||
} else {
|
||||
throw new Error(`unexpected eventType ${eventType}`);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#isStored}.
|
||||
*/
|
||||
public isSecretStored(name: string): Promise<Record<string, SecretStorageKeyDescription> | null> {
|
||||
public isSecretStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescription> | null> {
|
||||
return this.secretStorage.isStored(name);
|
||||
}
|
||||
|
||||
@ -4236,7 +4238,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns Promise which resolves: an empty object
|
||||
* @returns Rejects: with an error response.
|
||||
*/
|
||||
public setAccountData(eventType: EventType | string, content: IContent): Promise<{}> {
|
||||
public setAccountData<K extends keyof AccountDataEvents>(
|
||||
eventType: K,
|
||||
content: AccountDataEvents[K] | Record<string, never>,
|
||||
): 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<EmittedEvents, ClientEventHa
|
||||
* @param eventType - The event type
|
||||
* @returns The contents of the given account data event
|
||||
*/
|
||||
public getAccountData(eventType: string): MatrixEvent | undefined {
|
||||
public getAccountData<K extends keyof AccountDataEvents>(eventType: K): MatrixEvent | undefined {
|
||||
return this.store.getAccountData(eventType);
|
||||
}
|
||||
|
||||
@ -4263,7 +4268,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns Promise which resolves: The contents of the given account data event.
|
||||
* @returns Rejects: with an error response.
|
||||
*/
|
||||
public async getAccountDataFromServer<T extends { [k: string]: any }>(eventType: string): Promise<T | null> {
|
||||
public async getAccountDataFromServer<K extends keyof AccountDataEvents>(
|
||||
eventType: K,
|
||||
): Promise<AccountDataEvents[K] | null> {
|
||||
if (this.isInitialSyncComplete()) {
|
||||
const event = this.store.getAccountData(eventType);
|
||||
if (!event) {
|
||||
@ -4271,7 +4278,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
// The network version below returns just the content, so this branch
|
||||
// does the same to match.
|
||||
return event.getContent<T>();
|
||||
return event.getContent<AccountDataEvents[K]>();
|
||||
}
|
||||
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||
$userId: this.credentials.userId!,
|
||||
@ -4287,7 +4294,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteAccountData(eventType: string): Promise<void> {
|
||||
public async deleteAccountData(eventType: keyof AccountDataEvents): Promise<void> {
|
||||
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<EmittedEvents, ClientEventHa
|
||||
* @returns The array of users that are ignored (empty if none)
|
||||
*/
|
||||
public getIgnoredUsers(): string[] {
|
||||
const event = this.getAccountData("m.ignored_user_list");
|
||||
const event = this.getAccountData(EventType.IgnoredUserList);
|
||||
if (!event?.getContent()["ignored_users"]) return [];
|
||||
return Object.keys(event.getContent()["ignored_users"]);
|
||||
}
|
||||
@ -4326,7 +4333,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
userIds.forEach((u) => {
|
||||
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<EmittedEvents, ClientEventHa
|
||||
deviceId: string,
|
||||
notificationSettings: LocalNotificationSettings,
|
||||
): Promise<{}> {
|
||||
const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`;
|
||||
const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}` as const;
|
||||
return this.setAccountData(key, notificationSettings);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<void> {
|
||||
public async setAccountData<K extends keyof AccountDataEvents>(
|
||||
type: K,
|
||||
content: AccountDataEvents[K],
|
||||
): Promise<void> {
|
||||
await this.accountDataClientAdapter.setAccountData(type, content);
|
||||
}
|
||||
|
||||
@ -160,7 +164,7 @@ export class EncryptionSetupOperation {
|
||||
/**
|
||||
*/
|
||||
public constructor(
|
||||
private readonly accountData: Map<string, object>,
|
||||
private readonly accountData: Map<keyof AccountDataEvents, MatrixEvent>,
|
||||
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<string, MatrixEvent>();
|
||||
public readonly values = new Map<keyof AccountDataEvents, MatrixEvent>();
|
||||
|
||||
/**
|
||||
* @param existingValues - existing account data
|
||||
@ -248,33 +252,26 @@ class AccountDataClientAdapter
|
||||
/**
|
||||
* @returns the content of the account data
|
||||
*/
|
||||
public getAccountDataFromServer<T extends { [k: string]: any }>(type: string): Promise<T | null> {
|
||||
public getAccountDataFromServer<K extends keyof AccountDataEvents>(type: K): Promise<AccountDataEvents[K] | null> {
|
||||
return Promise.resolve(this.getAccountData(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the content of the account data
|
||||
*/
|
||||
public getAccountData<T extends { [k: string]: any }>(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<T>();
|
||||
}
|
||||
return null;
|
||||
public getAccountData<K extends keyof AccountDataEvents>(type: K): AccountDataEvents[K] | null {
|
||||
const event = this.values.get(type) ?? this.existingValues.get(type);
|
||||
return event?.getContent<AccountDataEvents[K]>() ?? null;
|
||||
}
|
||||
|
||||
public setAccountData(type: string, content: any): Promise<{}> {
|
||||
public setAccountData<K extends keyof AccountDataEvents>(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 {};
|
||||
});
|
||||
|
@ -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<B extends MatrixClient | undefined = MatrixClient> im
|
||||
/**
|
||||
* Store an encrypted secret on the server
|
||||
*/
|
||||
public store(name: string, secret: string, keys?: string[] | null): Promise<void> {
|
||||
public store(name: SecretStorageKey, secret: string, keys?: string[] | null): Promise<void> {
|
||||
return this.storageImpl.store(name, secret, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a secret from storage.
|
||||
*/
|
||||
public get(name: string): Promise<string | undefined> {
|
||||
public get(name: SecretStorageKey): Promise<string | undefined> {
|
||||
return this.storageImpl.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a secret is stored on the server.
|
||||
*/
|
||||
public async isStored(name: string): Promise<Record<string, SecretStorageKeyDescription> | null> {
|
||||
public async isStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescription> | null> {
|
||||
return this.storageImpl.isStored(name);
|
||||
}
|
||||
|
||||
|
@ -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<CryptoEvent, CryptoEventHandlerMap
|
||||
/**
|
||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#store}.
|
||||
*/
|
||||
public storeSecret(name: string, secret: string, keys?: string[]): Promise<void> {
|
||||
public storeSecret(name: SecretStorageKey, secret: string, keys?: string[]): Promise<void> {
|
||||
return this.secretStorage.store(name, secret, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#get}.
|
||||
*/
|
||||
public getSecret(name: string): Promise<string | undefined> {
|
||||
public getSecret(name: SecretStorageKey): Promise<string | undefined> {
|
||||
return this.secretStorage.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#isStored}.
|
||||
*/
|
||||
public isSecretStored(name: string): Promise<Record<string, SecretStorageKeyDescription> | null> {
|
||||
public isSecretStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescription> | null> {
|
||||
return this.secretStorage.isStored(name);
|
||||
}
|
||||
|
||||
|
58
src/models/invites-ignorer-types.ts
Normal file
58
src/models/invites-ignorer-types.ts
Normal file
@ -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",
|
||||
}
|
@ -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, keyof StateEvents> = {
|
||||
[PolicyScope.User]: EventType.PolicyRuleUser,
|
||||
|
@ -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<RustCryptoEvents, CryptoEventH
|
||||
*/
|
||||
public async isSecretStorageReady(): Promise<boolean> {
|
||||
// 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",
|
||||
|
@ -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<boolean> {
|
||||
const defaultKeyId = await secretStorage.getDefaultKeyId();
|
||||
if (!defaultKeyId) return false;
|
||||
|
@ -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<ClientEvent.Account
|
||||
* @param eventType - The type of account data
|
||||
* @returns The contents of the given account data event, or `null` if the event is not found
|
||||
*/
|
||||
getAccountDataFromServer: <T extends Record<string, any>>(eventType: string) => Promise<T | null>;
|
||||
getAccountDataFromServer: <K extends keyof AccountDataEvents>(eventType: K) => Promise<AccountDataEvents[K] | null>;
|
||||
|
||||
/**
|
||||
* Set account data event for the current user, with retries
|
||||
@ -147,7 +148,7 @@ export interface AccountDataClient extends TypedEventEmitter<ClientEvent.Account
|
||||
* @param content - the content object to be set
|
||||
* @returns an empty object
|
||||
*/
|
||||
setAccountData: (eventType: string, content: any) => Promise<{}>;
|
||||
setAccountData: <K extends keyof AccountDataEvents>(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<Record<string, SecretStorageKeyDescriptionAesV1> | null>;
|
||||
isStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescriptionAesV1> | 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<string | null> {
|
||||
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<SecretStorageKeyDescription>(
|
||||
`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<SecretStorageKeyDescriptionAesV1>(
|
||||
"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<void> {
|
||||
public async store(name: SecretStorageKey, secret: string, keys?: string[] | null): Promise<void> {
|
||||
const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};
|
||||
|
||||
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<SecretStorageKeyDescriptionAesV1>(
|
||||
"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<string | undefined> {
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretInfo>(name);
|
||||
public async get(name: SecretStorageKey): Promise<string | undefined> {
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);
|
||||
if (!secretInfo) {
|
||||
return;
|
||||
}
|
||||
@ -555,9 +556,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
||||
const keys: Record<string, SecretStorageKeyDescriptionAesV1> = {};
|
||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
||||
// get key information from key storage
|
||||
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretStorageKeyDescriptionAesV1>(
|
||||
"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<Record<string, SecretStorageKeyDescriptionAesV1> | null> {
|
||||
public async isStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescriptionAesV1> | null> {
|
||||
// check if secret exists
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretInfo>(name);
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);
|
||||
if (!secretInfo?.encrypted) return null;
|
||||
|
||||
const ret: Record<string, SecretStorageKeyDescriptionAesV1> = {};
|
||||
@ -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<SecretStorageKeyDescriptionAesV1>(
|
||||
"m.secret_storage.key." + keyId,
|
||||
);
|
||||
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
|
||||
if (!keyInfo) continue;
|
||||
const encInfo = secretInfo.encrypted[keyId];
|
||||
|
||||
|
Reference in New Issue
Block a user