mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:20:44 +00:00
Matrix: refresh crypto bootstrap state
Refresh published cross-signing keys before bootstrap imports secret-storage keys, add sync-filter plumbing for QA E2EE clients, and document the remaining upstream key-backup cache noise without suppressing SDK logs.
This commit is contained in:
@@ -1,12 +1,8 @@
|
||||
import { logger as matrixJsSdkRootLogger } from "matrix-js-sdk/lib/logger.js";
|
||||
import { ConsoleLogger, LogService, setMatrixConsoleLogging } from "../sdk/logger.js";
|
||||
|
||||
let matrixSdkLoggingConfigured = false;
|
||||
let matrixSdkLogMode: "default" | "quiet" = "default";
|
||||
const matrixSdkBaseLogger = new ConsoleLogger();
|
||||
const matrixSdkSilentMethodFactory = () => () => {};
|
||||
let matrixSdkRootMethodFactory: unknown;
|
||||
let matrixSdkRootLoggerInitialized = false;
|
||||
|
||||
type MatrixJsSdkLogger = {
|
||||
trace: (...messageOrObject: unknown[]) => void;
|
||||
@@ -52,22 +48,7 @@ export function createMatrixJsSdkClientLogger(prefix = "matrix"): MatrixJsSdkLog
|
||||
return createMatrixJsSdkLoggerInstance(prefix);
|
||||
}
|
||||
|
||||
function applyMatrixJsSdkRootLoggerMode(): void {
|
||||
const rootLogger = matrixJsSdkRootLogger as {
|
||||
methodFactory?: unknown;
|
||||
rebuild?: () => void;
|
||||
};
|
||||
if (!matrixSdkRootLoggerInitialized) {
|
||||
matrixSdkRootMethodFactory = rootLogger.methodFactory;
|
||||
matrixSdkRootLoggerInitialized = true;
|
||||
}
|
||||
rootLogger.methodFactory =
|
||||
matrixSdkLogMode === "quiet" ? matrixSdkSilentMethodFactory : matrixSdkRootMethodFactory;
|
||||
rootLogger.rebuild?.();
|
||||
}
|
||||
|
||||
function applyMatrixSdkLogger(): void {
|
||||
applyMatrixJsSdkRootLoggerMode();
|
||||
if (matrixSdkLogMode === "quiet") {
|
||||
LogService.setLogger({
|
||||
trace: () => {},
|
||||
|
||||
@@ -1404,6 +1404,26 @@ describe("MatrixClient crypto bootstrapping", () => {
|
||||
expect(logger?.getChild).toBeTypeOf("function");
|
||||
});
|
||||
|
||||
it("passes a custom sync filter to matrix-js-sdk startup", async () => {
|
||||
const client = new MatrixClient("https://matrix.example.org", "token", {
|
||||
userId: "@bot:example.org",
|
||||
syncFilter: { room: { ephemeral: { not_types: ["m.receipt"] } } },
|
||||
});
|
||||
|
||||
await client.start();
|
||||
|
||||
const startOpts = matrixJsClient.startClient.mock.calls[0]?.[0] as
|
||||
| { filter?: { getDefinition?: () => unknown } }
|
||||
| undefined;
|
||||
expect(startOpts?.filter?.getDefinition?.()).toEqual({
|
||||
room: {
|
||||
ephemeral: {
|
||||
not_types: ["m.receipt"],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("schedules periodic crypto snapshot persistence with fake timers", async () => {
|
||||
vi.useFakeTimers();
|
||||
const databasesSpy = vi.spyOn(indexedDB, "databases").mockResolvedValue([]);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import {
|
||||
ClientEvent,
|
||||
Filter,
|
||||
MatrixEventEvent,
|
||||
Preset,
|
||||
createClient as createMatrixJsClient,
|
||||
type IFilterDefinition,
|
||||
type MatrixClient as MatrixJsClient,
|
||||
type MatrixEvent,
|
||||
} from "matrix-js-sdk/lib/matrix.js";
|
||||
@@ -216,6 +218,7 @@ export class MatrixClient {
|
||||
private readonly httpClient: MatrixAuthedHttpClient;
|
||||
private readonly localTimeoutMs: number;
|
||||
private readonly initialSyncLimit?: number;
|
||||
private readonly syncFilter?: IFilterDefinition;
|
||||
private readonly encryptionEnabled: boolean;
|
||||
private readonly password?: string;
|
||||
private readonly syncStore?: FileBackedMatrixSyncStore;
|
||||
@@ -258,6 +261,7 @@ export class MatrixClient {
|
||||
localTimeoutMs?: number;
|
||||
encryption?: boolean;
|
||||
initialSyncLimit?: number;
|
||||
syncFilter?: IFilterDefinition;
|
||||
storagePath?: string;
|
||||
recoveryKeyPath?: string;
|
||||
idbSnapshotPath?: string;
|
||||
@@ -275,6 +279,7 @@ export class MatrixClient {
|
||||
});
|
||||
this.localTimeoutMs = Math.max(1, opts.localTimeoutMs ?? 60_000);
|
||||
this.initialSyncLimit = opts.initialSyncLimit;
|
||||
this.syncFilter = opts.syncFilter;
|
||||
this.encryptionEnabled = opts.encryption === true;
|
||||
this.password = opts.password;
|
||||
this.syncStore = opts.storagePath ? new FileBackedMatrixSyncStore(opts.storagePath) : undefined;
|
||||
@@ -496,6 +501,7 @@ export class MatrixClient {
|
||||
|
||||
await this.client.startClient({
|
||||
initialSyncLimit: this.initialSyncLimit,
|
||||
...(this.syncFilter ? { filter: Filter.fromJson(this.selfUserId, "", this.syncFilter) } : {}),
|
||||
});
|
||||
await this.waitForInitialSyncReady({
|
||||
abortSignal: opts.abortSignal,
|
||||
@@ -1674,6 +1680,12 @@ export class MatrixClient {
|
||||
"MatrixClientLite",
|
||||
"No room key backup version found on server, creating one via secret storage bootstrap",
|
||||
);
|
||||
// matrix-js-sdk 41.3.0 can log a transient PerSessionKeyBackupDownloader
|
||||
// "current backup version ... undefined" warning while setupNewKeyBackup creates
|
||||
// the backup: resetKeyBackup emits key-backup cache events before its async
|
||||
// checkKeyBackupAndEnable pass has populated active backup state. Keep the
|
||||
// explicit server re-check below and do not hide the SDK logs; if this needs
|
||||
// fixing in code, upstream a minimal Matrix SDK repro instead of patching here.
|
||||
await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
||||
setupNewKeyBackup: true,
|
||||
});
|
||||
|
||||
@@ -222,6 +222,26 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("refreshes published cross-signing keys before importing private keys from secret storage", async () => {
|
||||
const bootstrapCrossSigning = vi.fn(async () => {});
|
||||
const userHasCrossSigningKeys = vi.fn(async () => true);
|
||||
const { bootstrapper, crypto } = createBootstrapperHarness({
|
||||
bootstrapCrossSigning,
|
||||
getDeviceVerificationStatus: vi.fn(async () => createVerifiedDeviceStatus()),
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys,
|
||||
});
|
||||
|
||||
await bootstrapper.bootstrap(crypto, {
|
||||
allowAutomaticCrossSigningReset: false,
|
||||
});
|
||||
|
||||
expect(userHasCrossSigningKeys).toHaveBeenCalledWith("@bot:example.org", true);
|
||||
expect(userHasCrossSigningKeys.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
bootstrapCrossSigning.mock.invocationCallOrder[0],
|
||||
);
|
||||
});
|
||||
|
||||
it("passes explicit secret-storage repair allowance only when requested", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const crypto = createCryptoApi({
|
||||
|
||||
@@ -142,6 +142,16 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const refreshPublishedCrossSigningKeys = async (): Promise<void> => {
|
||||
if (typeof crypto.userHasCrossSigningKeys !== "function") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await crypto.userHasCrossSigningKeys(userId, true);
|
||||
} catch {
|
||||
// The normal bootstrap flow below handles missing or unavailable keys.
|
||||
}
|
||||
};
|
||||
const isCrossSigningReady = async (): Promise<boolean> => {
|
||||
if (typeof crypto.isCrossSigningReady !== "function") {
|
||||
return true;
|
||||
@@ -212,6 +222,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
|
||||
// First pass: preserve existing cross-signing identity and ensure public keys are uploaded.
|
||||
try {
|
||||
await refreshPublishedCrossSigningKeys();
|
||||
await crypto.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user