Skip to content

Commit

Permalink
Use mapped types for account data content (#4590)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
t3chguy and richvdh authored Dec 19, 2024
1 parent bcf3d56 commit 3fcc566
Show file tree
Hide file tree
Showing 18 changed files with 235 additions and 120 deletions.
7 changes: 7 additions & 0 deletions spec/integ/matrix-client-syncing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
7 changes: 7 additions & 0 deletions spec/integ/sliding-sync-sdk.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions spec/unit/crypto/secrets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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");
Expand Down
6 changes: 6 additions & 0 deletions spec/unit/matrix-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
12 changes: 12 additions & 0 deletions spec/unit/rust-crypto/secret-storage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
28 changes: 21 additions & 7 deletions spec/unit/secret-storage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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}`);
}
Expand All @@ -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}`);
}
Expand Down Expand Up @@ -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}`);
}
Expand Down
34 changes: 34 additions & 0 deletions src/@types/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
25 changes: 16 additions & 9 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ import {
UpdateDelayedEventAction,
} from "./@types/requests.ts";
import {
AccountDataEvents,
EventType,
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
MSC3912_RELATION_BASED_REDACTIONS_PROP,
Expand Down Expand Up @@ -232,6 +233,7 @@ import {
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
import {
AddSecretStorageKeyOpts,
SecretStorageKey,
SecretStorageKeyDescription,
ServerSideSecretStorage,
ServerSideSecretStorageImpl,
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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,
Expand All @@ -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);
}

Expand All @@ -4263,15 +4268,17 @@ 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) {
return null;
}
// 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!,
Expand All @@ -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) {
Expand All @@ -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"]);
}
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/crypto/CrossSigning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 3fcc566

Please sign in to comment.