mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
matrix-js: enable key backup creation in verify bootstrap
This commit is contained in:
@@ -54,15 +54,13 @@ function printVerificationStatus(status: {
|
||||
console.log("Verified: yes");
|
||||
console.log(`User: ${status.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${status.deviceId ?? "unknown"}`);
|
||||
if (status.backupVersion) {
|
||||
console.log(`Backup version: ${status.backupVersion}`);
|
||||
}
|
||||
} else {
|
||||
console.log("Verified: no");
|
||||
console.log(`User: ${status.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${status.deviceId ?? "unknown"}`);
|
||||
console.log("Run 'openclaw matrix-js verify device <key>' to verify this device.");
|
||||
}
|
||||
console.log(`Backup version: ${status.backupVersion ?? "none"}`);
|
||||
console.log(`Recovery key stored: ${status.recoveryKeyStored ? "yes" : "no"}`);
|
||||
printTimestamp("Recovery key created at", status.recoveryKeyCreatedAt);
|
||||
console.log(`Pending verifications: ${status.pendingVerifications}`);
|
||||
@@ -143,6 +141,7 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
console.log(
|
||||
`Cross-signing published: ${result.crossSigning.published ? "yes" : "no"} (master=${result.crossSigning.masterKeyPublished ? "yes" : "no"}, self=${result.crossSigning.selfSigningKeyPublished ? "yes" : "no"}, user=${result.crossSigning.userSigningKeyPublished ? "yes" : "no"})`,
|
||||
);
|
||||
console.log(`Backup version: ${result.verification.backupVersion ?? "none"}`);
|
||||
printTimestamp("Recovery key created at", result.verification.recoveryKeyCreatedAt);
|
||||
console.log(`Pending verifications: ${result.pendingVerifications}`);
|
||||
if (!result.success) {
|
||||
@@ -179,9 +178,7 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
console.log("Device verification completed successfully.");
|
||||
console.log(`User: ${result.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${result.deviceId ?? "unknown"}`);
|
||||
if (result.backupVersion) {
|
||||
console.log(`Backup version: ${result.backupVersion}`);
|
||||
}
|
||||
console.log(`Backup version: ${result.backupVersion ?? "none"}`);
|
||||
printTimestamp("Recovery key created at", result.recoveryKeyCreatedAt);
|
||||
printTimestamp("Verified at", result.verifiedAt);
|
||||
} else {
|
||||
|
||||
@@ -948,6 +948,100 @@ describe("MatrixClient crypto bootstrapping", () => {
|
||||
expect(result.cryptoBootstrap).not.toBeNull();
|
||||
});
|
||||
|
||||
it("creates a key backup during bootstrap when none exists on the server", async () => {
|
||||
matrixJsClient.getUserId = vi.fn(() => "@bot:example.org");
|
||||
matrixJsClient.getDeviceId = vi.fn(() => "DEVICE123");
|
||||
const bootstrapSecretStorage = vi.fn(async () => {});
|
||||
matrixJsClient.getCrypto = vi.fn(() => ({
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage,
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
localVerified: true,
|
||||
crossSigningVerified: true,
|
||||
signedByOwner: true,
|
||||
})),
|
||||
}));
|
||||
|
||||
const client = new MatrixClient("https://matrix.example.org", "token", undefined, undefined, {
|
||||
encryption: true,
|
||||
});
|
||||
vi.spyOn(client, "getOwnCrossSigningPublicationStatus").mockResolvedValue({
|
||||
userId: "@bot:example.org",
|
||||
masterKeyPublished: true,
|
||||
selfSigningKeyPublished: true,
|
||||
userSigningKeyPublished: true,
|
||||
published: true,
|
||||
});
|
||||
let backupChecks = 0;
|
||||
vi.spyOn(client, "doRequest").mockImplementation(async (_method, endpoint) => {
|
||||
if (String(endpoint).includes("/room_keys/version")) {
|
||||
backupChecks += 1;
|
||||
return backupChecks >= 2 ? { version: "7" } : {};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const result = await client.bootstrapOwnDeviceVerification();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.verification.backupVersion).toBe("7");
|
||||
expect(bootstrapSecretStorage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ setupNewKeyBackup: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not recreate key backup during bootstrap when one already exists", async () => {
|
||||
matrixJsClient.getUserId = vi.fn(() => "@bot:example.org");
|
||||
matrixJsClient.getDeviceId = vi.fn(() => "DEVICE123");
|
||||
const bootstrapSecretStorage = vi.fn(async () => {});
|
||||
matrixJsClient.getCrypto = vi.fn(() => ({
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage,
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
localVerified: true,
|
||||
crossSigningVerified: true,
|
||||
signedByOwner: true,
|
||||
})),
|
||||
}));
|
||||
|
||||
const client = new MatrixClient("https://matrix.example.org", "token", undefined, undefined, {
|
||||
encryption: true,
|
||||
});
|
||||
vi.spyOn(client, "getOwnCrossSigningPublicationStatus").mockResolvedValue({
|
||||
userId: "@bot:example.org",
|
||||
masterKeyPublished: true,
|
||||
selfSigningKeyPublished: true,
|
||||
userSigningKeyPublished: true,
|
||||
published: true,
|
||||
});
|
||||
vi.spyOn(client, "doRequest").mockImplementation(async (_method, endpoint) => {
|
||||
if (String(endpoint).includes("/room_keys/version")) {
|
||||
return { version: "9" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const result = await client.bootstrapOwnDeviceVerification();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.verification.backupVersion).toBe("9");
|
||||
expect(
|
||||
bootstrapSecretStorage.mock.calls.some(([opts]) =>
|
||||
Boolean((opts as { setupNewKeyBackup?: boolean } | undefined)?.setupNewKeyBackup),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not report bootstrap errors when final verification state is healthy", async () => {
|
||||
matrixJsClient.getUserId = vi.fn(() => "@bot:example.org");
|
||||
matrixJsClient.getDeviceId = vi.fn(() => "DEVICE123");
|
||||
|
||||
@@ -723,6 +723,7 @@ export class MatrixClient {
|
||||
forceResetCrossSigning: params?.forceResetCrossSigning === true,
|
||||
strict: true,
|
||||
});
|
||||
await this.ensureRoomKeyBackupEnabled(crypto);
|
||||
} catch (err) {
|
||||
bootstrapError = err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
@@ -755,6 +756,25 @@ export class MatrixClient {
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureRoomKeyBackupEnabled(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
const existingVersion = await this.resolveRoomKeyBackupVersion();
|
||||
if (existingVersion) {
|
||||
return;
|
||||
}
|
||||
LogService.info(
|
||||
"MatrixClientLite",
|
||||
"No room key backup version found on server, creating one via secret storage bootstrap",
|
||||
);
|
||||
await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
||||
setupNewKeyBackup: true,
|
||||
});
|
||||
const createdVersion = await this.resolveRoomKeyBackupVersion();
|
||||
if (!createdVersion) {
|
||||
throw new Error("Matrix room key backup is still missing after bootstrap");
|
||||
}
|
||||
LogService.info("MatrixClientLite", `Room key backup enabled (version ${createdVersion})`);
|
||||
}
|
||||
|
||||
private registerBridge(): void {
|
||||
if (this.bridgeRegistered) {
|
||||
return;
|
||||
|
||||
@@ -126,7 +126,10 @@ export class MatrixRecoveryKeyStore {
|
||||
return this.getRecoveryKeySummary() ?? {};
|
||||
}
|
||||
|
||||
async bootstrapSecretStorageWithRecoveryKey(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
async bootstrapSecretStorageWithRecoveryKey(
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
options: { setupNewKeyBackup?: boolean } = {},
|
||||
): Promise<void> {
|
||||
let status: MatrixSecretStorageStatus | null = null;
|
||||
if (typeof crypto.getSecretStorageStatus === "function") {
|
||||
try {
|
||||
@@ -193,7 +196,7 @@ export class MatrixRecoveryKeyStore {
|
||||
setupNewSecretStorage?: boolean;
|
||||
setupNewKeyBackup?: boolean;
|
||||
} = {
|
||||
setupNewKeyBackup: false,
|
||||
setupNewKeyBackup: options.setupNewKeyBackup === true,
|
||||
};
|
||||
|
||||
if (shouldRecreateSecretStorage) {
|
||||
|
||||
Reference in New Issue
Block a user