You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +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 { IActionsObject } from "../../src/pushprocessor";
|
||||||
import { KnownMembership } from "../../src/@types/membership";
|
import { KnownMembership } from "../../src/@types/membership";
|
||||||
|
|
||||||
|
declare module "../../src/@types/event" {
|
||||||
|
interface AccountDataEvents {
|
||||||
|
a: {};
|
||||||
|
b: {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("MatrixClient syncing", () => {
|
describe("MatrixClient syncing", () => {
|
||||||
const selfUserId = "@alice:localhost";
|
const selfUserId = "@alice:localhost";
|
||||||
const selfAccessToken = "aseukfgwef";
|
const selfAccessToken = "aseukfgwef";
|
||||||
|
@@ -45,6 +45,13 @@ import { emitPromise } from "../test-utils/test-utils";
|
|||||||
import { defer } from "../../src/utils";
|
import { defer } from "../../src/utils";
|
||||||
import { KnownMembership } from "../../src/@types/membership";
|
import { KnownMembership } from "../../src/@types/membership";
|
||||||
|
|
||||||
|
declare module "../../src/@types/event" {
|
||||||
|
interface AccountDataEvents {
|
||||||
|
global_test: {};
|
||||||
|
tester: {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("SlidingSyncSdk", () => {
|
describe("SlidingSyncSdk", () => {
|
||||||
let client: MatrixClient | undefined;
|
let client: MatrixClient | undefined;
|
||||||
let httpBackend: MockHttpBackend | 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 { SecretStorageKeyDescription, SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
||||||
import { decodeBase64 } from "../../../src/base64";
|
import { decodeBase64 } from "../../../src/base64";
|
||||||
import { CrossSigningKeyInfo } from "../../../src/crypto-api";
|
import { CrossSigningKeyInfo } from "../../../src/crypto-api";
|
||||||
|
import { SecretInfo } from "../../../src/secret-storage.ts";
|
||||||
|
|
||||||
async function makeTestClient(
|
async function makeTestClient(
|
||||||
userInfo: { userId: string; deviceId: string },
|
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 () {
|
describe("Secrets", function () {
|
||||||
if (!globalThis.Olm) {
|
if (!globalThis.Olm) {
|
||||||
logger.warn("Not running megolm backup unit tests: libolm not present");
|
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)]));
|
return new Map(Object.entries(queryDict).map(([k, v]) => [k, String(v)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "../../src/@types/event" {
|
||||||
|
interface AccountDataEvents {
|
||||||
|
"im.vector.test": {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type HttpLookup = {
|
type HttpLookup = {
|
||||||
method: string;
|
method: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
@@ -30,6 +30,7 @@ import fetchMock from "fetch-mock-jest";
|
|||||||
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
|
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
|
||||||
import { initRustCrypto } from "../../../src/rust-crypto";
|
import { initRustCrypto } from "../../../src/rust-crypto";
|
||||||
import {
|
import {
|
||||||
|
AccountDataEvents,
|
||||||
Device,
|
Device,
|
||||||
DeviceVerification,
|
DeviceVerification,
|
||||||
encodeBase64,
|
encodeBase64,
|
||||||
@@ -1924,11 +1925,13 @@ class DummyAccountDataClient
|
|||||||
super();
|
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);
|
const ret = this.storage.get(eventType);
|
||||||
|
|
||||||
if (eventType) {
|
if (eventType) {
|
||||||
return ret as T;
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,18 @@ import {
|
|||||||
secretStorageContainsCrossSigningKeys,
|
secretStorageContainsCrossSigningKeys,
|
||||||
} from "../../../src/rust-crypto/secret-storage";
|
} from "../../../src/rust-crypto/secret-storage";
|
||||||
import { ServerSideSecretStorage } from "../../../src/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("secret-storage", () => {
|
||||||
describe("secretStorageContainsCrossSigningKeys", () => {
|
describe("secretStorageContainsCrossSigningKeys", () => {
|
||||||
|
@@ -27,6 +27,14 @@ import {
|
|||||||
trimTrailingEquals,
|
trimTrailingEquals,
|
||||||
} from "../../src/secret-storage";
|
} from "../../src/secret-storage";
|
||||||
import { randomString } from "../../src/randomstring";
|
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("ServerSideSecretStorageImpl", function () {
|
||||||
describe(".addKey", function () {
|
describe(".addKey", function () {
|
||||||
@@ -117,9 +125,11 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
||||||
|
|
||||||
const storedKey = { iv: "iv", mac: "mac" } as SecretStorageKeyDescriptionAesV1;
|
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") {
|
if (eventType === "m.secret_storage.key.my_key") {
|
||||||
return storedKey as unknown as T;
|
return storedKey as any;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected eventType ${eventType}`);
|
throw new Error(`unexpected eventType ${eventType}`);
|
||||||
}
|
}
|
||||||
@@ -135,11 +145,13 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
||||||
|
|
||||||
const storedKey = { iv: "iv", mac: "mac" } as SecretStorageKeyDescriptionAesV1;
|
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") {
|
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") {
|
} else if (eventType === "m.secret_storage.key.default_key_id") {
|
||||||
return storedKey as unknown as T;
|
return storedKey as any;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected eventType ${eventType}`);
|
throw new Error(`unexpected eventType ${eventType}`);
|
||||||
}
|
}
|
||||||
@@ -236,9 +248,11 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
|
|
||||||
// stub out getAccountData to return a key with an unknown algorithm
|
// stub out getAccountData to return a key with an unknown algorithm
|
||||||
const storedKey = { algorithm: "badalg" } as SecretStorageKeyDescriptionCommon;
|
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") {
|
if (eventType === "m.secret_storage.key.keyid") {
|
||||||
return storedKey as unknown as T;
|
return storedKey as any;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected eventType ${eventType}`);
|
throw new Error(`unexpected eventType ${eventType}`);
|
||||||
}
|
}
|
||||||
|
@@ -58,6 +58,10 @@ import {
|
|||||||
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types.ts";
|
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types.ts";
|
||||||
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls.ts";
|
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls.ts";
|
||||||
import { SessionMembershipData } from "../matrixrtc/CallMembership.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 {
|
export enum EventType {
|
||||||
// Room state events
|
// Room state events
|
||||||
@@ -368,3 +372,33 @@ export interface StateEvents {
|
|||||||
// MSC3672
|
// MSC3672
|
||||||
[M_BEACON_INFO.name]: MBeaconInfoEventContent;
|
[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,
|
UpdateDelayedEventAction,
|
||||||
} from "./@types/requests.ts";
|
} from "./@types/requests.ts";
|
||||||
import {
|
import {
|
||||||
|
AccountDataEvents,
|
||||||
EventType,
|
EventType,
|
||||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||||
MSC3912_RELATION_BASED_REDACTIONS_PROP,
|
MSC3912_RELATION_BASED_REDACTIONS_PROP,
|
||||||
@@ -232,6 +233,7 @@ import {
|
|||||||
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
|
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
|
||||||
import {
|
import {
|
||||||
AddSecretStorageKeyOpts,
|
AddSecretStorageKeyOpts,
|
||||||
|
SecretStorageKey,
|
||||||
SecretStorageKeyDescription,
|
SecretStorageKeyDescription,
|
||||||
ServerSideSecretStorage,
|
ServerSideSecretStorage,
|
||||||
ServerSideSecretStorageImpl,
|
ServerSideSecretStorageImpl,
|
||||||
@@ -3070,7 +3072,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
*
|
*
|
||||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#isStored}.
|
* @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);
|
return this.secretStorage.isStored(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4236,7 +4238,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @returns Promise which resolves: an empty object
|
* @returns Promise which resolves: an empty object
|
||||||
* @returns Rejects: with an error response.
|
* @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", {
|
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||||
$userId: this.credentials.userId!,
|
$userId: this.credentials.userId!,
|
||||||
$type: eventType,
|
$type: eventType,
|
||||||
@@ -4251,7 +4256,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @param eventType - The event type
|
* @param eventType - The event type
|
||||||
* @returns The contents of the given account data event
|
* @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);
|
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 Promise which resolves: The contents of the given account data event.
|
||||||
* @returns Rejects: with an error response.
|
* @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()) {
|
if (this.isInitialSyncComplete()) {
|
||||||
const event = this.store.getAccountData(eventType);
|
const event = this.store.getAccountData(eventType);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
@@ -4271,7 +4278,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
}
|
}
|
||||||
// The network version below returns just the content, so this branch
|
// The network version below returns just the content, so this branch
|
||||||
// does the same to match.
|
// does the same to match.
|
||||||
return event.getContent<T>();
|
return event.getContent<AccountDataEvents[K]>();
|
||||||
}
|
}
|
||||||
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||||
$userId: this.credentials.userId!,
|
$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);
|
const msc3391DeleteAccountDataServerSupport = this.canSupport.get(Feature.AccountDataDeletion);
|
||||||
// if deletion is not supported overwrite with empty content
|
// if deletion is not supported overwrite with empty content
|
||||||
if (msc3391DeleteAccountDataServerSupport === ServerSupport.Unsupported) {
|
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)
|
* @returns The array of users that are ignored (empty if none)
|
||||||
*/
|
*/
|
||||||
public getIgnoredUsers(): string[] {
|
public getIgnoredUsers(): string[] {
|
||||||
const event = this.getAccountData("m.ignored_user_list");
|
const event = this.getAccountData(EventType.IgnoredUserList);
|
||||||
if (!event?.getContent()["ignored_users"]) return [];
|
if (!event?.getContent()["ignored_users"]) return [];
|
||||||
return Object.keys(event.getContent()["ignored_users"]);
|
return Object.keys(event.getContent()["ignored_users"]);
|
||||||
}
|
}
|
||||||
@@ -4326,7 +4333,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
userIds.forEach((u) => {
|
userIds.forEach((u) => {
|
||||||
content.ignored_users[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,
|
deviceId: string,
|
||||||
notificationSettings: LocalNotificationSettings,
|
notificationSettings: LocalNotificationSettings,
|
||||||
): Promise<{}> {
|
): Promise<{}> {
|
||||||
const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`;
|
const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}` as const;
|
||||||
return this.setAccountData(key, notificationSettings);
|
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}`)) || {});
|
intersect((await secretStorage.isStored(`m.cross_signing.${type}`)) || {});
|
||||||
}
|
}
|
||||||
return Object.keys(stored).length ? stored : null;
|
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 { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||||
import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage.ts";
|
import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage.ts";
|
||||||
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo } from "../crypto-api/index.ts";
|
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo } from "../crypto-api/index.ts";
|
||||||
|
import { AccountDataEvents } from "../@types/event.ts";
|
||||||
|
|
||||||
interface ICrossSigningKeys {
|
interface ICrossSigningKeys {
|
||||||
authUpload: BootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
|
authUpload: BootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
|
||||||
@@ -111,7 +112,10 @@ export class EncryptionSetupBuilder {
|
|||||||
userSignatures[deviceId] = signature;
|
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);
|
await this.accountDataClientAdapter.setAccountData(type, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +164,7 @@ export class EncryptionSetupOperation {
|
|||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly accountData: Map<string, object>,
|
private readonly accountData: Map<keyof AccountDataEvents, MatrixEvent>,
|
||||||
private readonly crossSigningKeys?: ICrossSigningKeys,
|
private readonly crossSigningKeys?: ICrossSigningKeys,
|
||||||
private readonly keyBackupInfo?: IKeyBackupInfo,
|
private readonly keyBackupInfo?: IKeyBackupInfo,
|
||||||
private readonly keySignatures?: KeySignatures,
|
private readonly keySignatures?: KeySignatures,
|
||||||
@@ -190,7 +194,7 @@ export class EncryptionSetupOperation {
|
|||||||
// set account data
|
// set account data
|
||||||
if (this.accountData) {
|
if (this.accountData) {
|
||||||
for (const [type, content] of 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
|
// upload first cross-signing signatures with the new key
|
||||||
@@ -236,7 +240,7 @@ class AccountDataClientAdapter
|
|||||||
implements AccountDataClient
|
implements AccountDataClient
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
public readonly values = new Map<string, MatrixEvent>();
|
public readonly values = new Map<keyof AccountDataEvents, MatrixEvent>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param existingValues - existing account data
|
* @param existingValues - existing account data
|
||||||
@@ -248,33 +252,26 @@ class AccountDataClientAdapter
|
|||||||
/**
|
/**
|
||||||
* @returns the content of the account data
|
* @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));
|
return Promise.resolve(this.getAccountData(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the content of the account data
|
* @returns the content of the account data
|
||||||
*/
|
*/
|
||||||
public getAccountData<T extends { [k: string]: any }>(type: string): T | null {
|
public getAccountData<K extends keyof AccountDataEvents>(type: K): AccountDataEvents[K] | null {
|
||||||
const modifiedValue = this.values.get(type);
|
const event = this.values.get(type) ?? this.existingValues.get(type);
|
||||||
if (modifiedValue) {
|
return event?.getContent<AccountDataEvents[K]>() ?? null;
|
||||||
return modifiedValue as unknown as T;
|
|
||||||
}
|
|
||||||
const existingValue = this.existingValues.get(type);
|
|
||||||
if (existingValue) {
|
|
||||||
return existingValue.getContent<T>();
|
|
||||||
}
|
|
||||||
return 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);
|
const lastEvent = this.values.get(type);
|
||||||
this.values.set(type, content);
|
this.values.set(type, event);
|
||||||
// ensure accountData is emitted on the next tick,
|
// ensure accountData is emitted on the next tick,
|
||||||
// as SecretStorage listens for it while calling this method
|
// as SecretStorage listens for it while calling this method
|
||||||
// and it seems to rely on this.
|
// and it seems to rely on this.
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
const event = new MatrixEvent({ type, content });
|
|
||||||
this.emit(ClientEvent.AccountData, event, lastEvent);
|
this.emit(ClientEvent.AccountData, event, lastEvent);
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
@@ -25,12 +25,12 @@ import {
|
|||||||
AccountDataClient,
|
AccountDataClient,
|
||||||
ServerSideSecretStorage,
|
ServerSideSecretStorage,
|
||||||
ServerSideSecretStorageImpl,
|
ServerSideSecretStorageImpl,
|
||||||
|
SecretStorageKey,
|
||||||
} from "../secret-storage.ts";
|
} from "../secret-storage.ts";
|
||||||
import { ISecretRequest, SecretSharing } from "./SecretSharing.ts";
|
import { ISecretRequest, SecretSharing } from "./SecretSharing.ts";
|
||||||
|
|
||||||
/* re-exports for backwards compatibility */
|
/* re-exports for backwards compatibility */
|
||||||
export type {
|
export type {
|
||||||
AccountDataClient as IAccountDataClient,
|
|
||||||
SecretStorageKeyTuple,
|
SecretStorageKeyTuple,
|
||||||
SecretStorageKeyObject,
|
SecretStorageKeyObject,
|
||||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
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
|
* 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);
|
return this.storageImpl.store(name, secret, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a secret from storage.
|
* Get a secret from storage.
|
||||||
*/
|
*/
|
||||||
public get(name: string): Promise<string | undefined> {
|
public get(name: SecretStorageKey): Promise<string | undefined> {
|
||||||
return this.storageImpl.get(name);
|
return this.storageImpl.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a secret is stored on the server.
|
* 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);
|
return this.storageImpl.isStored(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -77,6 +77,7 @@ import {
|
|||||||
AddSecretStorageKeyOpts,
|
AddSecretStorageKeyOpts,
|
||||||
calculateKeyCheck,
|
calculateKeyCheck,
|
||||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||||
|
SecretStorageKey,
|
||||||
SecretStorageKeyDescription,
|
SecretStorageKeyDescription,
|
||||||
SecretStorageKeyObject,
|
SecretStorageKeyObject,
|
||||||
SecretStorageKeyTuple,
|
SecretStorageKeyTuple,
|
||||||
@@ -1194,21 +1195,21 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
/**
|
/**
|
||||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#store}.
|
* @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);
|
return this.secretStorage.store(name, secret, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#get}.
|
* @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);
|
return this.secretStorage.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link MatrixClient#secretStorage} and {@link SecretStorage.ServerSideSecretStorage#isStored}.
|
* @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);
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UnstableValue } from "matrix-events-sdk";
|
|
||||||
|
|
||||||
import { MatrixClient } from "../client.ts";
|
import { MatrixClient } from "../client.ts";
|
||||||
import { IContent, MatrixEvent } from "./event.ts";
|
import { IContent, MatrixEvent } from "./event.ts";
|
||||||
import { EventTimeline } from "./event-timeline.ts";
|
import { EventTimeline } from "./event-timeline.ts";
|
||||||
@@ -23,47 +21,14 @@ import { Preset } from "../@types/partials.ts";
|
|||||||
import { globToRegexp } from "../utils.ts";
|
import { globToRegexp } from "../utils.ts";
|
||||||
import { Room } from "./room.ts";
|
import { Room } from "./room.ts";
|
||||||
import { EventType, StateEvents } from "../@types/event.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.
|
export { IGNORE_INVITES_ACCOUNT_EVENT_KEY, POLICIES_ACCOUNT_EVENT_TYPE, PolicyRecommendation, PolicyScope };
|
||||||
///
|
|
||||||
/// 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",
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopeToEventTypeMap: Record<PolicyScope, keyof StateEvents> = {
|
const scopeToEventTypeMap: Record<PolicyScope, keyof StateEvents> = {
|
||||||
[PolicyScope.User]: EventType.PolicyRuleUser,
|
[PolicyScope.User]: EventType.PolicyRuleUser,
|
||||||
|
@@ -71,7 +71,7 @@ import {
|
|||||||
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts";
|
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts";
|
||||||
import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts";
|
import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts";
|
||||||
import { Device, DeviceMap } from "../models/device.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 { CrossSigningIdentity } from "./CrossSigningIdentity.ts";
|
||||||
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts";
|
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts";
|
||||||
import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts";
|
import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts";
|
||||||
@@ -770,7 +770,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
|||||||
*/
|
*/
|
||||||
public async isSecretStorageReady(): Promise<boolean> {
|
public async isSecretStorageReady(): Promise<boolean> {
|
||||||
// make sure that the cross-signing keys are stored
|
// make sure that the cross-signing keys are stored
|
||||||
const secretsToCheck = [
|
const secretsToCheck: SecretStorageKey[] = [
|
||||||
"m.cross_signing.master",
|
"m.cross_signing.master",
|
||||||
"m.cross_signing.user_signing",
|
"m.cross_signing.user_signing",
|
||||||
"m.cross_signing.self_signing",
|
"m.cross_signing.self_signing",
|
||||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
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.
|
* 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(
|
export async function secretStorageCanAccessSecrets(
|
||||||
secretStorage: ServerSideSecretStorage,
|
secretStorage: ServerSideSecretStorage,
|
||||||
secretNames: string[],
|
secretNames: SecretStorageKey[],
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const defaultKeyId = await secretStorage.getDefaultKeyId();
|
const defaultKeyId = await secretStorage.getDefaultKeyId();
|
||||||
if (!defaultKeyId) return false;
|
if (!defaultKeyId) return false;
|
||||||
|
@@ -28,6 +28,7 @@ import { logger } from "./logger.ts";
|
|||||||
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts";
|
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts";
|
||||||
import decryptAESSecretStorageItem from "./utils/decryptAESSecretStorageItem.ts";
|
import decryptAESSecretStorageItem from "./utils/decryptAESSecretStorageItem.ts";
|
||||||
import { AESEncryptedSecretStoragePayload } from "./@types/AESEncryptedSecretStoragePayload.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";
|
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
|
* @param eventType - The type of account data
|
||||||
* @returns The contents of the given account data event, or `null` if the event is not found
|
* @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
|
* 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
|
* @param content - the content object to be set
|
||||||
* @returns an empty object
|
* @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>;
|
) => 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: {
|
encrypted: {
|
||||||
[keyId: string]: AESEncryptedSecretStoragePayload;
|
[keyId: string]: AESEncryptedSecretStoragePayload;
|
||||||
};
|
};
|
||||||
@@ -293,7 +304,7 @@ export interface ServerSideSecretStorage {
|
|||||||
* with, or null if it is not present or not encrypted with a trusted
|
* with, or null if it is not present or not encrypted with a trusted
|
||||||
* key
|
* 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.
|
* 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
|
* @returns The default key ID or null if no default key ID is set
|
||||||
*/
|
*/
|
||||||
public async getDefaultKeyId(): Promise<string | null> {
|
public async getDefaultKeyId(): Promise<string | null> {
|
||||||
const defaultKey = await this.accountDataAdapter.getAccountDataFromServer<{ key: string }>(
|
const defaultKey = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.default_key");
|
||||||
"m.secret_storage.default_key",
|
|
||||||
);
|
|
||||||
if (!defaultKey) return null;
|
if (!defaultKey) return null;
|
||||||
return defaultKey.key;
|
return defaultKey.key ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -409,11 +418,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
if (!keyId) {
|
if (!keyId) {
|
||||||
do {
|
do {
|
||||||
keyId = randomString(32);
|
keyId = randomString(32);
|
||||||
} while (
|
} while (await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`));
|
||||||
await this.accountDataAdapter.getAccountDataFromServer<SecretStorageKeyDescription>(
|
|
||||||
`m.secret_storage.key.${keyId}`,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.accountDataAdapter.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo);
|
await this.accountDataAdapter.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo);
|
||||||
@@ -441,9 +446,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretStorageKeyDescriptionAesV1>(
|
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
|
||||||
"m.secret_storage.key." + keyId,
|
|
||||||
);
|
|
||||||
return keyInfo ? [keyId, keyInfo] : null;
|
return keyInfo ? [keyId, keyInfo] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +495,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
* @param secret - The secret contents.
|
* @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.
|
* @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> = {};
|
const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};
|
||||||
|
|
||||||
if (!keys) {
|
if (!keys) {
|
||||||
@@ -509,9 +512,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
|
|
||||||
for (const keyId of keys) {
|
for (const keyId of keys) {
|
||||||
// get key information from key storage
|
// get key information from key storage
|
||||||
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretStorageKeyDescriptionAesV1>(
|
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
|
||||||
"m.secret_storage.key." + keyId,
|
|
||||||
);
|
|
||||||
if (!keyInfo) {
|
if (!keyInfo) {
|
||||||
throw new Error("Unknown key: " + keyId);
|
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
|
* @returns the decrypted contents of the secret, or "undefined" if `name` is not found in
|
||||||
* the user's account data.
|
* the user's account data.
|
||||||
*/
|
*/
|
||||||
public async get(name: string): Promise<string | undefined> {
|
public async get(name: SecretStorageKey): Promise<string | undefined> {
|
||||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretInfo>(name);
|
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);
|
||||||
if (!secretInfo) {
|
if (!secretInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -555,9 +556,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
const keys: Record<string, SecretStorageKeyDescriptionAesV1> = {};
|
const keys: Record<string, SecretStorageKeyDescriptionAesV1> = {};
|
||||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
||||||
// get key information from key storage
|
// get key information from key storage
|
||||||
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretStorageKeyDescriptionAesV1>(
|
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
|
||||||
"m.secret_storage.key." + keyId,
|
|
||||||
);
|
|
||||||
const encInfo = secretInfo.encrypted[keyId];
|
const encInfo = secretInfo.encrypted[keyId];
|
||||||
// only use keys we understand the encryption algorithm of
|
// only use keys we understand the encryption algorithm of
|
||||||
if (keyInfo?.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
|
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
|
* with, or null if it is not present or not encrypted with a trusted
|
||||||
* key
|
* 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
|
// check if secret exists
|
||||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretInfo>(name);
|
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);
|
||||||
if (!secretInfo?.encrypted) return null;
|
if (!secretInfo?.encrypted) return null;
|
||||||
|
|
||||||
const ret: Record<string, SecretStorageKeyDescriptionAesV1> = {};
|
const ret: Record<string, SecretStorageKeyDescriptionAesV1> = {};
|
||||||
@@ -600,9 +599,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
// filter secret encryption keys with supported algorithm
|
// filter secret encryption keys with supported algorithm
|
||||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
||||||
// get key information from key storage
|
// get key information from key storage
|
||||||
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<SecretStorageKeyDescriptionAesV1>(
|
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
|
||||||
"m.secret_storage.key." + keyId,
|
|
||||||
);
|
|
||||||
if (!keyInfo) continue;
|
if (!keyInfo) continue;
|
||||||
const encInfo = secretInfo.encrypted[keyId];
|
const encInfo = secretInfo.encrypted[keyId];
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user