From efc07dc6ca52b4fb16095f5ae8f4e720a62df210 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Thu, 12 Mar 2026 10:05:27 +0000 Subject: [PATCH] Matrix: scope legacy credential migration --- .../matrix/src/matrix/credentials.test.ts | 66 ++++++++++++++++++- extensions/matrix/src/matrix/credentials.ts | 17 ++++- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/extensions/matrix/src/matrix/credentials.test.ts b/extensions/matrix/src/matrix/credentials.test.ts index bb4da24748f..eb05a1ed2d2 100644 --- a/extensions/matrix/src/matrix/credentials.test.ts +++ b/extensions/matrix/src/matrix/credentials.test.ts @@ -21,10 +21,19 @@ describe("matrix credentials storage", () => { } }); - function setupStateDir(): string { + function setupStateDir( + cfg: Record = { + channels: { + matrix: {}, + }, + }, + ): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-creds-")); tempDirs.push(dir); setMatrixRuntime({ + config: { + loadConfig: () => cfg, + }, state: { resolveStateDir: () => dir, }, @@ -82,7 +91,15 @@ describe("matrix credentials storage", () => { }); it("migrates legacy matrix credential files on read", async () => { - const stateDir = setupStateDir(); + const stateDir = setupStateDir({ + channels: { + matrix: { + accounts: { + ops: {}, + }, + }, + }, + }); const legacyPath = path.join(stateDir, "credentials", "matrix", "credentials.json"); const currentPath = resolveMatrixCredentialsPath({}, "ops"); fs.mkdirSync(path.dirname(legacyPath), { recursive: true }); @@ -103,8 +120,51 @@ describe("matrix credentials storage", () => { expect(fs.existsSync(currentPath)).toBe(true); }); + it("does not migrate legacy default credentials during a non-selected account read", () => { + const stateDir = setupStateDir({ + channels: { + matrix: { + defaultAccount: "default", + accounts: { + default: { + homeserver: "https://matrix.default.example.org", + accessToken: "default-token", + }, + ops: {}, + }, + }, + }, + }); + const legacyPath = path.join(stateDir, "credentials", "matrix", "credentials.json"); + const currentPath = resolveMatrixCredentialsPath({}, "ops"); + fs.mkdirSync(path.dirname(legacyPath), { recursive: true }); + fs.writeFileSync( + legacyPath, + JSON.stringify({ + homeserver: "https://matrix.default.example.org", + userId: "@default:example.org", + accessToken: "default-token", + createdAt: "2026-03-01T10:00:00.000Z", + }), + ); + + const loaded = loadMatrixCredentials({}, "ops"); + + expect(loaded).toBeNull(); + expect(fs.existsSync(legacyPath)).toBe(true); + expect(fs.existsSync(currentPath)).toBe(false); + }); + it("clears both current and legacy credential paths", () => { - const stateDir = setupStateDir(); + const stateDir = setupStateDir({ + channels: { + matrix: { + accounts: { + ops: {}, + }, + }, + }, + }); const currentPath = resolveMatrixCredentialsPath({}, "ops"); const legacyPath = path.join(stateDir, "credentials", "matrix", "credentials.json"); fs.mkdirSync(path.dirname(currentPath), { recursive: true }); diff --git a/extensions/matrix/src/matrix/credentials.ts b/extensions/matrix/src/matrix/credentials.ts index 70697d2db89..3a492774c74 100644 --- a/extensions/matrix/src/matrix/credentials.ts +++ b/extensions/matrix/src/matrix/credentials.ts @@ -3,6 +3,8 @@ import os from "node:os"; import path from "node:path"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id"; import { + requiresExplicitMatrixDefaultAccount, + resolveMatrixDefaultOrOnlyAccountId, resolveMatrixCredentialsDir as resolveSharedMatrixCredentialsDir, resolveMatrixCredentialsPath as resolveSharedMatrixCredentialsPath, writeJsonFileAtomically, @@ -26,12 +28,23 @@ function resolveLegacyMatrixCredentialsPath(env: NodeJS.ProcessEnv): string | nu return path.join(resolveMatrixCredentialsDir(env), "credentials.json"); } +function shouldReadLegacyCredentialsForAccount(accountId?: string | null): boolean { + const normalizedAccountId = normalizeAccountId(accountId); + const cfg = getMatrixRuntime().config.loadConfig(); + if (!cfg.channels?.matrix || typeof cfg.channels.matrix !== "object") { + return normalizedAccountId === DEFAULT_ACCOUNT_ID; + } + if (requiresExplicitMatrixDefaultAccount(cfg)) { + return false; + } + return normalizeAccountId(resolveMatrixDefaultOrOnlyAccountId(cfg)) === normalizedAccountId; +} + function resolveLegacyMigrationSourcePath( env: NodeJS.ProcessEnv, accountId?: string | null, ): string | null { - const normalized = normalizeAccountId(accountId); - if (normalized === DEFAULT_ACCOUNT_ID) { + if (!shouldReadLegacyCredentialsForAccount(accountId)) { return null; } const legacyPath = resolveLegacyMatrixCredentialsPath(env);