diff --git a/extensions/matrix/src/matrix/credentials.test.ts b/extensions/matrix/src/matrix/credentials.test.ts index c5a2b2c7b46..8fbc0ae3e4c 100644 --- a/extensions/matrix/src/matrix/credentials.test.ts +++ b/extensions/matrix/src/matrix/credentials.test.ts @@ -14,6 +14,13 @@ import { touchMatrixCredentials, } from "./credentials.js"; +const DEFAULT_LEGACY_CREDENTIALS = { + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + accessToken: "legacy-token", + createdAt: "2026-03-01T10:00:00.000Z", +}; + describe("matrix credentials storage", () => { const tempDirs: string[] = []; @@ -37,6 +44,19 @@ describe("matrix credentials storage", () => { return dir; } + function setupLegacyCredentialsFile(params: { + cfg: Record; + accountId: string; + credentials?: Record; + }) { + const stateDir = setupStateDir(params.cfg); + const legacyPath = path.join(stateDir, "credentials", "matrix", "credentials.json"); + const currentPath = resolveMatrixCredentialsPath({}, params.accountId); + fs.mkdirSync(path.dirname(legacyPath), { recursive: true }); + fs.writeFileSync(legacyPath, JSON.stringify(params.credentials ?? DEFAULT_LEGACY_CREDENTIALS)); + return { stateDir, legacyPath, currentPath }; + } + it("writes credentials atomically with secure file permissions", async () => { const stateDir = setupStateDir(); await saveMatrixCredentials( @@ -216,27 +236,18 @@ describe("matrix credentials storage", () => { }); it("migrates legacy matrix credential files on read", async () => { - const stateDir = setupStateDir({ - channels: { - matrix: { - accounts: { - ops: {}, + const { legacyPath, currentPath } = setupLegacyCredentialsFile({ + cfg: { + channels: { + matrix: { + accounts: { + ops: {}, + }, }, }, }, + accountId: "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.example.org", - userId: "@bot:example.org", - accessToken: "legacy-token", - createdAt: "2026-03-01T10:00:00.000Z", - }), - ); const loaded = loadMatrixCredentials({}, "ops"); @@ -246,33 +257,24 @@ describe("matrix credentials storage", () => { }); it("returns migrated credentials when another process moves the legacy file mid-read", () => { - const stateDir = setupStateDir({ - channels: { - matrix: { - accounts: { - ops: {}, + const { legacyPath, currentPath } = setupLegacyCredentialsFile({ + cfg: { + channels: { + matrix: { + accounts: { + ops: {}, + }, }, }, }, + accountId: "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.example.org", - userId: "@bot:example.org", - accessToken: "legacy-token", - createdAt: "2026-03-01T10:00:00.000Z", - }), - ); const originalReadFileSync = fs.readFileSync.bind(fs); let moved = false; const readFileSpy = vi.spyOn(fs, "readFileSync").mockImplementation((( filePath: fs.PathOrFileDescriptor, - options?: fs.ObjectEncodingOptions | BufferEncoding | null, + options?: Parameters[1], ) => { if (!moved && filePath === legacyPath) { fs.renameSync(legacyPath, currentPath); @@ -293,27 +295,18 @@ describe("matrix credentials storage", () => { }); it("does not rename the legacy path after falling back to already-migrated current credentials", () => { - const stateDir = setupStateDir({ - channels: { - matrix: { - accounts: { - ops: {}, + const { legacyPath, currentPath } = setupLegacyCredentialsFile({ + cfg: { + channels: { + matrix: { + accounts: { + ops: {}, + }, }, }, }, + accountId: "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.example.org", - userId: "@bot:example.org", - accessToken: "legacy-token", - createdAt: "2026-03-01T10:00:00.000Z", - }), - ); const originalReadFileSync = fs.readFileSync.bind(fs); const originalRenameSync = fs.renameSync.bind(fs); @@ -321,7 +314,7 @@ describe("matrix credentials storage", () => { let migrated = false; const readFileSpy = vi.spyOn(fs, "readFileSync").mockImplementation((( filePath: fs.PathOrFileDescriptor, - options?: fs.ObjectEncodingOptions | BufferEncoding | null, + options?: Parameters[1], ) => { if (!migrated && filePath === legacyPath && fs.existsSync(legacyPath)) { originalRenameSync(legacyPath, currentPath); @@ -374,32 +367,29 @@ describe("matrix credentials storage", () => { }); 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", + const { legacyPath, currentPath } = setupLegacyCredentialsFile({ + cfg: { + channels: { + matrix: { + defaultAccount: "default", + accounts: { + default: { + homeserver: "https://matrix.default.example.org", + accessToken: "default-token", + }, + ops: {}, }, - 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({ + accountId: "ops", + credentials: { homeserver: "https://matrix.default.example.org", userId: "@default:example.org", accessToken: "default-token", createdAt: "2026-03-01T10:00:00.000Z", - }), - ); + }, + }); const loaded = loadMatrixCredentials({}, "ops"); @@ -409,31 +399,28 @@ describe("matrix credentials storage", () => { }); it("migrates legacy credentials to the named account when top-level auth is only a shared default", () => { - const stateDir = setupStateDir({ - channels: { - matrix: { - accessToken: "shared-token", - accounts: { - ops: { - homeserver: "https://matrix.example.org", - accessToken: "ops-token", + const { legacyPath, currentPath } = setupLegacyCredentialsFile({ + cfg: { + channels: { + matrix: { + accessToken: "shared-token", + accounts: { + ops: { + homeserver: "https://matrix.example.org", + accessToken: "ops-token", + }, }, }, }, }, - }); - 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({ + accountId: "ops", + credentials: { homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "legacy-token", createdAt: "2026-03-01T10:00:00.000Z", - }), - ); + }, + }); const loaded = loadMatrixCredentials({}, "ops");