From 9fc8f8068dfda6720af4f511b80b4552fed3f9ae Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 25 Feb 2026 18:18:37 -0500 Subject: [PATCH] Matrix-js: remove register mode and require existing accounts --- docs/channels/matrix-js.md | 2 - extensions/matrix-js/src/channel.ts | 4 - extensions/matrix-js/src/cli.test.ts | 4 - extensions/matrix-js/src/cli.ts | 67 +------ extensions/matrix-js/src/config-schema.ts | 1 - .../matrix-js/src/matrix/client.test.ts | 174 ++++-------------- .../matrix-js/src/matrix/client/config.ts | 168 +---------------- .../src/matrix/client/register-mode.test.ts | 98 ---------- .../src/matrix/client/register-mode.ts | 125 ------------- .../matrix-js/src/matrix/client/types.ts | 1 - extensions/matrix-js/src/onboarding.ts | 14 +- extensions/matrix-js/src/types.ts | 2 - 12 files changed, 45 insertions(+), 615 deletions(-) delete mode 100644 extensions/matrix-js/src/matrix/client/register-mode.test.ts delete mode 100644 extensions/matrix-js/src/matrix/client/register-mode.ts diff --git a/docs/channels/matrix-js.md b/docs/channels/matrix-js.md index f943bcc3c1d..5ed1036565c 100644 --- a/docs/channels/matrix-js.md +++ b/docs/channels/matrix-js.md @@ -81,7 +81,6 @@ Environment variable equivalents (used when the config key is not set): - `MATRIX_PASSWORD` - `MATRIX_DEVICE_ID` - `MATRIX_DEVICE_NAME` -- `MATRIX_REGISTER` ## Configuration example @@ -271,7 +270,6 @@ See [Groups](/channels/groups) for mention-gating and allowlist behavior. - `userId`: full Matrix user ID, for example `@bot:example.org`. - `accessToken`: access token for token-based auth. - `password`: password for password-based login. -- `register`: auto-register if login fails and homeserver allows registration. - `deviceId`: explicit Matrix device ID. - `deviceName`: device display name for password login. - `initialSyncLimit`: startup sync event limit. diff --git a/extensions/matrix-js/src/channel.ts b/extensions/matrix-js/src/channel.ts index a510856f6c0..8e1d2533af9 100644 --- a/extensions/matrix-js/src/channel.ts +++ b/extensions/matrix-js/src/channel.ts @@ -70,7 +70,6 @@ function buildMatrixConfigUpdate( userId?: string; accessToken?: string; password?: string; - register?: boolean; deviceName?: string; initialSyncLimit?: number; }, @@ -93,7 +92,6 @@ function buildMatrixConfigUpdate( ...(input.userId ? { userId: input.userId } : {}), ...(input.accessToken ? { accessToken: input.accessToken } : {}), ...(input.password ? { password: input.password } : {}), - ...(typeof input.register === "boolean" ? { register: input.register } : {}), ...(input.deviceName ? { deviceName: input.deviceName } : {}), ...(typeof input.initialSyncLimit === "number" ? { initialSyncLimit: input.initialSyncLimit } @@ -115,7 +113,6 @@ function buildMatrixConfigUpdate( ...(input.userId ? { userId: input.userId } : {}), ...(input.accessToken ? { accessToken: input.accessToken } : {}), ...(input.password ? { password: input.password } : {}), - ...(typeof input.register === "boolean" ? { register: input.register } : {}), ...(input.deviceName ? { deviceName: input.deviceName } : {}), ...(typeof input.initialSyncLimit === "number" ? { initialSyncLimit: input.initialSyncLimit } @@ -168,7 +165,6 @@ export const matrixPlugin: ChannelPlugin = { "userId", "accessToken", "password", - "register", "deviceName", "initialSyncLimit", ], diff --git a/extensions/matrix-js/src/cli.test.ts b/extensions/matrix-js/src/cli.test.ts index 12acdbce77c..d3f6df4d243 100644 --- a/extensions/matrix-js/src/cli.test.ts +++ b/extensions/matrix-js/src/cli.test.ts @@ -161,8 +161,6 @@ describe("matrix-js CLI verification commands", () => { "@ops:example.org", "--password", "secret", - "--register", - "on", ], { from: "user" }, ); @@ -184,7 +182,6 @@ describe("matrix-js CLI verification commands", () => { accounts: { ops: expect.objectContaining({ homeserver: "https://matrix.example.org", - register: true, }), }, }, @@ -192,7 +189,6 @@ describe("matrix-js CLI verification commands", () => { }), ); expect(console.log).toHaveBeenCalledWith("Saved matrix-js account: ops"); - expect(console.log).toHaveBeenCalledWith("Register mode: on"); expect(console.log).toHaveBeenCalledWith( "Bind this account to an agent: openclaw agents bind --agent --bind matrix-js:ops", ); diff --git a/extensions/matrix-js/src/cli.ts b/extensions/matrix-js/src/cli.ts index f35643d8fd6..ceafa075129 100644 --- a/extensions/matrix-js/src/cli.ts +++ b/extensions/matrix-js/src/cli.ts @@ -76,64 +76,9 @@ function parseOptionalInt(value: string | undefined, fieldName: string): number return parsed; } -function parseToggle(value: string | undefined, fieldName: string): boolean | undefined { - const trimmed = value?.trim().toLowerCase(); - if (!trimmed) { - return undefined; - } - if (["on", "true", "1", "yes"].includes(trimmed)) { - return true; - } - if (["off", "false", "0", "no"].includes(trimmed)) { - return false; - } - throw new Error(`${fieldName} must be on|off`); -} - -function applyRegisterFlag( - cfg: CoreConfig, - accountId: string, - register: boolean | undefined, -): CoreConfig { - if (typeof register !== "boolean") { - return cfg; - } - const matrix = cfg.channels?.["matrix-js"] ?? {}; - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...cfg, - channels: { - ...(cfg.channels ?? {}), - "matrix-js": { - ...matrix, - register, - }, - }, - }; - } - const account = matrix.accounts?.[accountId] ?? {}; - return { - ...cfg, - channels: { - ...(cfg.channels ?? {}), - "matrix-js": { - ...matrix, - accounts: { - ...matrix.accounts, - [accountId]: { - ...account, - register, - }, - }, - }, - }, - }; -} - type MatrixCliAccountAddResult = { accountId: string; configPath: string; - registerMode: boolean | undefined; useEnv: boolean; }; @@ -147,12 +92,10 @@ async function addMatrixJsAccount(params: { deviceName?: string; initialSyncLimit?: string; useEnv?: boolean; - register?: string; }): Promise { const runtime = getMatrixRuntime(); const cfg = runtime.config.loadConfig() as CoreConfig; const accountId = normalizeAccountId(params.account); - const registerMode = parseToggle(params.register, "--register"); const setup = matrixPlugin.setup; if (!setup?.applyAccountConfig) { throw new Error("Matrix-js account setup is unavailable."); @@ -183,8 +126,7 @@ async function addMatrixJsAccount(params: { accountId, input, }) as CoreConfig; - const next = applyRegisterFlag(updated, accountId, registerMode); - await runtime.config.writeConfigFile(next as never); + await runtime.config.writeConfigFile(updated as never); return { accountId, @@ -192,7 +134,6 @@ async function addMatrixJsAccount(params: { accountId === DEFAULT_ACCOUNT_ID ? "channels.matrix-js" : `channels.matrix-js.accounts.${accountId}`, - registerMode, useEnv: input.useEnv === true, }; } @@ -506,7 +447,6 @@ export function registerMatrixJsCli(params: { program: Command }): void { .option("--device-name ", "Matrix device display name") .option("--initial-sync-limit ", "Matrix initial sync limit") .option("--use-env", "Use MATRIX_* env vars (default account only)") - .option("--register ", "Enable/disable register mode for password auth") .option("--verbose", "Show setup details") .option("--json", "Output as JSON") .action( @@ -520,7 +460,6 @@ export function registerMatrixJsCli(params: { program: Command }): void { deviceName?: string; initialSyncLimit?: string; useEnv?: boolean; - register?: string; verbose?: boolean; json?: boolean; }) => { @@ -538,14 +477,10 @@ export function registerMatrixJsCli(params: { program: Command }): void { deviceName: options.deviceName, initialSyncLimit: options.initialSyncLimit, useEnv: options.useEnv === true, - register: options.register, }), onText: (result) => { console.log(`Saved matrix-js account: ${result.accountId}`); console.log(`Config path: ${result.configPath}`); - if (typeof result.registerMode === "boolean") { - console.log(`Register mode: ${result.registerMode ? "on" : "off"}`); - } console.log( `Credentials source: ${result.useEnv ? "MATRIX_* env vars" : "inline config"}`, ); diff --git a/extensions/matrix-js/src/config-schema.ts b/extensions/matrix-js/src/config-schema.ts index ea684060a70..f3113218302 100644 --- a/extensions/matrix-js/src/config-schema.ts +++ b/extensions/matrix-js/src/config-schema.ts @@ -43,7 +43,6 @@ export const MatrixConfigSchema = z.object({ userId: z.string().optional(), accessToken: z.string().optional(), password: z.string().optional(), - register: z.boolean().optional(), deviceId: z.string().optional(), deviceName: z.string().optional(), initialSyncLimit: z.number().optional(), diff --git a/extensions/matrix-js/src/matrix/client.test.ts b/extensions/matrix-js/src/matrix/client.test.ts index 4f354312e48..08bb752c415 100644 --- a/extensions/matrix-js/src/matrix/client.test.ts +++ b/extensions/matrix-js/src/matrix/client.test.ts @@ -4,9 +4,7 @@ import { resolveMatrixAuth, resolveMatrixConfig } from "./client.js"; import * as credentialsModule from "./credentials.js"; import * as sdkModule from "./sdk.js"; -const saveMatrixCredentialsMock = vi.fn(); -const prepareMatrixRegisterModeMock = vi.fn(async () => null); -const finalizeMatrixRegisterConfigAfterSuccessMock = vi.fn(async () => false); +const saveMatrixCredentialsMock = vi.hoisted(() => vi.fn()); vi.mock("./credentials.js", () => ({ loadMatrixCredentials: vi.fn(() => null), @@ -15,12 +13,6 @@ vi.mock("./credentials.js", () => ({ touchMatrixCredentials: vi.fn(), })); -vi.mock("./client/register-mode.js", () => ({ - prepareMatrixRegisterMode: prepareMatrixRegisterModeMock, - finalizeMatrixRegisterConfigAfterSuccess: finalizeMatrixRegisterConfigAfterSuccessMock, - resetPreparedMatrixRegisterModesForTests: vi.fn(), -})); - describe("resolveMatrixConfig", () => { it("prefers config over env", () => { const cfg = { @@ -48,7 +40,6 @@ describe("resolveMatrixConfig", () => { userId: "@cfg:example.org", accessToken: "cfg-token", password: "cfg-pass", - register: false, deviceId: undefined, deviceName: "CfgDevice", initialSyncLimit: 5, @@ -71,32 +62,11 @@ describe("resolveMatrixConfig", () => { expect(resolved.userId).toBe("@env:example.org"); expect(resolved.accessToken).toBe("env-token"); expect(resolved.password).toBe("env-pass"); - expect(resolved.register).toBe(false); expect(resolved.deviceId).toBe("ENVDEVICE"); expect(resolved.deviceName).toBe("EnvDevice"); expect(resolved.initialSyncLimit).toBeUndefined(); expect(resolved.encryption).toBe(false); }); - - it("reads register flag from config and env", () => { - const cfg = { - channels: { - "matrix-js": { - register: true, - }, - }, - } as CoreConfig; - const resolvedFromCfg = resolveMatrixConfig(cfg, {} as NodeJS.ProcessEnv); - expect(resolvedFromCfg.register).toBe(true); - - const resolvedFromEnv = resolveMatrixConfig( - {} as CoreConfig, - { - MATRIX_REGISTER: "1", - } as NodeJS.ProcessEnv, - ); - expect(resolvedFromEnv.register).toBe(true); - }); }); describe("resolveMatrixAuth", () => { @@ -104,8 +74,6 @@ describe("resolveMatrixAuth", () => { vi.restoreAllMocks(); vi.unstubAllGlobals(); saveMatrixCredentialsMock.mockReset(); - prepareMatrixRegisterModeMock.mockReset(); - finalizeMatrixRegisterConfigAfterSuccessMock.mockReset(); }); it("uses the hardened client request path for password login and persists deviceId", async () => { @@ -158,88 +126,9 @@ describe("resolveMatrixAuth", () => { ); }); - it("can register account when password login fails and register mode is enabled", async () => { + it("surfaces password login errors when account credentials are invalid", async () => { const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest"); - doRequestSpy - .mockRejectedValueOnce(new Error("Invalid username or password")) - .mockResolvedValueOnce({ - access_token: "tok-registered", - user_id: "@newbot:example.org", - device_id: "REGDEVICE123", - }); - - const cfg = { - channels: { - "matrix-js": { - homeserver: "https://matrix.example.org", - userId: "@newbot:example.org", - password: "secret", - register: true, - encryption: true, - }, - }, - } as CoreConfig; - - const auth = await resolveMatrixAuth({ - cfg, - env: {} as NodeJS.ProcessEnv, - }); - - expect(doRequestSpy).toHaveBeenNthCalledWith( - 1, - "POST", - "/_matrix/client/v3/login", - undefined, - expect.objectContaining({ - type: "m.login.password", - device_id: undefined, - }), - ); - expect(doRequestSpy).toHaveBeenNthCalledWith( - 2, - "POST", - "/_matrix/client/v3/register", - undefined, - expect.objectContaining({ - username: "newbot", - auth: { type: "m.login.dummy" }, - }), - ); - expect(auth).toMatchObject({ - homeserver: "https://matrix.example.org", - userId: "@newbot:example.org", - accessToken: "tok-registered", - deviceId: "REGDEVICE123", - encryption: true, - }); - expect(prepareMatrixRegisterModeMock).toHaveBeenCalledWith({ - cfg, - homeserver: "https://matrix.example.org", - userId: "@newbot:example.org", - env: {} as NodeJS.ProcessEnv, - }); - expect(finalizeMatrixRegisterConfigAfterSuccessMock).toHaveBeenCalledWith({ - homeserver: "https://matrix.example.org", - userId: "@newbot:example.org", - deviceId: "REGDEVICE123", - }); - }); - - it("ignores cached credentials when matrix-js.register=true", async () => { - vi.mocked(credentialsModule.loadMatrixCredentials).mockReturnValue({ - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "cached-token", - deviceId: "CACHEDDEVICE", - createdAt: "2026-01-01T00:00:00.000Z", - }); - vi.mocked(credentialsModule.credentialsMatchConfig).mockReturnValue(true); - - const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({ - access_token: "tok-123", - user_id: "@bot:example.org", - device_id: "DEVICE123", - }); + doRequestSpy.mockRejectedValueOnce(new Error("Invalid username or password")); const cfg = { channels: { @@ -247,12 +136,16 @@ describe("resolveMatrixAuth", () => { homeserver: "https://matrix.example.org", userId: "@bot:example.org", password: "secret", - register: true, }, }, } as CoreConfig; - const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv }); + await expect( + resolveMatrixAuth({ + cfg, + env: {} as NodeJS.ProcessEnv, + }), + ).rejects.toThrow("Invalid username or password"); expect(doRequestSpy).toHaveBeenCalledWith( "POST", @@ -262,44 +155,41 @@ describe("resolveMatrixAuth", () => { type: "m.login.password", }), ); - expect(auth.accessToken).toBe("tok-123"); - expect(prepareMatrixRegisterModeMock).toHaveBeenCalledTimes(1); + expect(saveMatrixCredentialsMock).not.toHaveBeenCalled(); }); - it("requires matrix-js.password when matrix-js.register=true", async () => { + it("uses cached matching credentials when access token is not configured", async () => { + vi.mocked(credentialsModule.loadMatrixCredentials).mockReturnValue({ + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + accessToken: "cached-token", + deviceId: "CACHEDDEVICE", + createdAt: "2026-01-01T00:00:00.000Z", + }); + vi.mocked(credentialsModule.credentialsMatchConfig).mockReturnValue(true); + const cfg = { channels: { "matrix-js": { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - register: true, - }, - }, - } as CoreConfig; - - await expect(resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv })).rejects.toThrow( - "Matrix password is required when matrix-js.register=true", - ); - expect(prepareMatrixRegisterModeMock).not.toHaveBeenCalled(); - expect(finalizeMatrixRegisterConfigAfterSuccessMock).not.toHaveBeenCalled(); - }); - - it("requires matrix-js.userId when matrix-js.register=true", async () => { - const cfg = { - channels: { - "matrix-js": { - homeserver: "https://matrix.example.org", password: "secret", - register: true, }, }, } as CoreConfig; - await expect(resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv })).rejects.toThrow( - "Matrix userId is required when matrix-js.register=true", - ); - expect(prepareMatrixRegisterModeMock).not.toHaveBeenCalled(); - expect(finalizeMatrixRegisterConfigAfterSuccessMock).not.toHaveBeenCalled(); + const auth = await resolveMatrixAuth({ + cfg, + env: {} as NodeJS.ProcessEnv, + }); + + expect(auth).toMatchObject({ + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + accessToken: "cached-token", + deviceId: "CACHEDDEVICE", + }); + expect(saveMatrixCredentialsMock).not.toHaveBeenCalled(); }); it("falls back to config deviceId when cached credentials are missing it", async () => { diff --git a/extensions/matrix-js/src/matrix/client/config.ts b/extensions/matrix-js/src/matrix/client/config.ts index a98fa043cc9..1192f3f7d95 100644 --- a/extensions/matrix-js/src/matrix/client/config.ts +++ b/extensions/matrix-js/src/matrix/client/config.ts @@ -3,36 +3,12 @@ import { getMatrixRuntime } from "../../runtime.js"; import type { CoreConfig } from "../../types.js"; import { MatrixClient } from "../sdk.js"; import { ensureMatrixSdkLoggingConfigured } from "./logging.js"; -import { - finalizeMatrixRegisterConfigAfterSuccess, - prepareMatrixRegisterMode, -} from "./register-mode.js"; import type { MatrixAuth, MatrixResolvedConfig } from "./types.js"; function clean(value?: string): string { return value?.trim() ?? ""; } -function parseOptionalBoolean(value: unknown): boolean | undefined { - if (typeof value === "boolean") { - return value; - } - if (typeof value !== "string") { - return undefined; - } - const normalized = value.trim().toLowerCase(); - if (!normalized) { - return undefined; - } - if (["1", "true", "yes", "on"].includes(normalized)) { - return true; - } - if (["0", "false", "no", "off"].includes(normalized)) { - return false; - } - return undefined; -} - function findAccountConfig(cfg: CoreConfig, accountId: string): Record { const accounts = cfg.channels?.["matrix-js"]?.accounts; if (!accounts || typeof accounts !== "object") { @@ -54,70 +30,6 @@ function findAccountConfig(cfg: CoreConfig, accountId: string): Record { - const registerClient = new MatrixClient(params.homeserver, ""); - const payload = { - username: resolveMatrixLocalpart(params.userId), - password: params.password, - inhibit_login: false, - device_id: params.deviceId, - initial_device_display_name: params.deviceName ?? "OpenClaw Gateway", - }; - - let firstError: unknown = null; - try { - return (await registerClient.doRequest("POST", "/_matrix/client/v3/register", undefined, { - ...payload, - auth: { type: "m.login.dummy" }, - })) as { - access_token?: string; - user_id?: string; - device_id?: string; - }; - } catch (err) { - firstError = err; - } - - try { - return (await registerClient.doRequest( - "POST", - "/_matrix/client/v3/register", - undefined, - payload, - )) as { - access_token?: string; - user_id?: string; - device_id?: string; - }; - } catch (err) { - const firstMessage = firstError instanceof Error ? firstError.message : String(firstError); - const secondMessage = err instanceof Error ? err.message : String(err); - throw new Error( - `Matrix registration failed (dummy auth: ${firstMessage}; plain registration: ${secondMessage})`, - ); - } -} - export function resolveMatrixConfig( cfg: CoreConfig = getMatrixRuntime().config.loadConfig() as CoreConfig, env: NodeJS.ProcessEnv = process.env, @@ -127,8 +39,6 @@ export function resolveMatrixConfig( const userId = clean(matrix.userId) || clean(env.MATRIX_USER_ID); const accessToken = clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined; const password = clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined; - const register = - parseOptionalBoolean(matrix.register) ?? parseOptionalBoolean(env.MATRIX_REGISTER) ?? false; const deviceId = clean(matrix.deviceId) || clean(env.MATRIX_DEVICE_ID) || undefined; const deviceName = clean(matrix.deviceName) || clean(env.MATRIX_DEVICE_NAME) || undefined; const initialSyncLimit = @@ -141,7 +51,6 @@ export function resolveMatrixConfig( userId, accessToken, password, - register, deviceId, deviceName, initialSyncLimit, @@ -180,11 +89,6 @@ export function resolveMatrixConfigForAccount( accountAccessToken || clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined; const password = accountPassword || clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined; - const register = - parseOptionalBoolean(account.register) ?? - parseOptionalBoolean(matrix.register) ?? - parseOptionalBoolean(env.MATRIX_REGISTER) ?? - false; const deviceId = accountDeviceId || clean(matrix.deviceId) || clean(env.MATRIX_DEVICE_ID) || undefined; const deviceName = @@ -207,7 +111,6 @@ export function resolveMatrixConfigForAccount( userId, accessToken, password, - register, deviceId, deviceName, initialSyncLimit, @@ -226,7 +129,6 @@ export async function resolveMatrixAuth(params?: { const resolved = accountId ? resolveMatrixConfigForAccount(cfg, accountId, env) : resolveMatrixConfig(cfg, env); - const registerFromConfig = resolved.register === true; if (!resolved.homeserver) { throw new Error("Matrix homeserver is required (matrix-js.homeserver)"); } @@ -248,23 +150,8 @@ export async function resolveMatrixAuth(params?: { ? cached : null; - if (registerFromConfig) { - if (!resolved.userId) { - throw new Error("Matrix userId is required when matrix-js.register=true"); - } - if (!resolved.password) { - throw new Error("Matrix password is required when matrix-js.register=true"); - } - await prepareMatrixRegisterMode({ - cfg, - homeserver: resolved.homeserver, - userId: resolved.userId, - env, - }); - } - // If we have an access token, we can fetch userId via whoami if not provided - if (resolved.accessToken && !registerFromConfig) { + if (resolved.accessToken) { let userId = resolved.userId; const hasMatchingCachedToken = cachedCredentials?.accessToken === resolved.accessToken; let knownDeviceId = hasMatchingCachedToken @@ -322,7 +209,7 @@ export async function resolveMatrixAuth(params?: { }; } - if (cachedCredentials && !registerFromConfig) { + if (cachedCredentials) { touchMatrixCredentials(env, accountId); return { homeserver: cachedCredentials.homeserver, @@ -351,48 +238,21 @@ export async function resolveMatrixAuth(params?: { // Login with password using the same hardened request path as other Matrix HTTP calls. ensureMatrixSdkLoggingConfigured(); const loginClient = new MatrixClient(resolved.homeserver, ""); - let login: { + const login = (await loginClient.doRequest("POST", "/_matrix/client/v3/login", undefined, { + type: "m.login.password", + identifier: { type: "m.id.user", user: resolved.userId }, + password: resolved.password, + device_id: resolved.deviceId, + initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway", + })) as { access_token?: string; user_id?: string; device_id?: string; }; - try { - login = (await loginClient.doRequest("POST", "/_matrix/client/v3/login", undefined, { - type: "m.login.password", - identifier: { type: "m.id.user", user: resolved.userId }, - password: resolved.password, - device_id: resolved.deviceId, - initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway", - })) as { - access_token?: string; - user_id?: string; - device_id?: string; - }; - } catch (loginErr) { - if (!resolved.register) { - throw loginErr; - } - try { - login = await registerMatrixPasswordAccount({ - homeserver: resolved.homeserver, - userId: resolved.userId, - password: resolved.password, - deviceId: resolved.deviceId, - deviceName: resolved.deviceName, - }); - } catch (registerErr) { - const loginMessage = loginErr instanceof Error ? loginErr.message : String(loginErr); - const registerMessage = - registerErr instanceof Error ? registerErr.message : String(registerErr); - throw new Error( - `Matrix login failed (${loginMessage}) and account registration failed (${registerMessage})`, - ); - } - } const accessToken = login.access_token?.trim(); if (!accessToken) { - throw new Error("Matrix login/registration did not return an access token"); + throw new Error("Matrix login did not return an access token"); } const auth: MatrixAuth = { @@ -417,13 +277,5 @@ export async function resolveMatrixAuth(params?: { accountId, ); - if (registerFromConfig) { - await finalizeMatrixRegisterConfigAfterSuccess({ - homeserver: auth.homeserver, - userId: auth.userId, - deviceId: auth.deviceId, - }); - } - return auth; } diff --git a/extensions/matrix-js/src/matrix/client/register-mode.test.ts b/extensions/matrix-js/src/matrix/client/register-mode.test.ts deleted file mode 100644 index 7106ac3209a..00000000000 --- a/extensions/matrix-js/src/matrix/client/register-mode.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import * as runtimeModule from "../../runtime.js"; -import type { CoreConfig } from "../../types.js"; -import { - finalizeMatrixRegisterConfigAfterSuccess, - prepareMatrixRegisterMode, - resetPreparedMatrixRegisterModesForTests, -} from "./register-mode.js"; - -describe("matrix register mode helpers", () => { - const tempDirs: string[] = []; - - afterEach(() => { - resetPreparedMatrixRegisterModesForTests(); - for (const dir of tempDirs.splice(0)) { - fs.rmSync(dir, { recursive: true, force: true }); - } - vi.restoreAllMocks(); - }); - - it("moves existing matrix state into a .bak snapshot before fresh registration", async () => { - const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "matrix-register-mode-")); - tempDirs.push(stateDir); - const credentialsDir = path.join(stateDir, "credentials", "matrix-js"); - const accountsDir = path.join(credentialsDir, "accounts"); - fs.mkdirSync(accountsDir, { recursive: true }); - fs.writeFileSync(path.join(credentialsDir, "credentials.json"), '{"accessToken":"old"}\n'); - fs.writeFileSync(path.join(accountsDir, "dummy.txt"), "old-state\n"); - - const cfg = { - channels: { - "matrix-js": { - userId: "@pinguini:matrix.gumadeiras.com", - register: true, - encryption: true, - }, - }, - } as CoreConfig; - - const backupDir = await prepareMatrixRegisterMode({ - cfg, - homeserver: "https://matrix.gumadeiras.com", - userId: "@pinguini:matrix.gumadeiras.com", - env: { OPENCLAW_STATE_DIR: stateDir } as NodeJS.ProcessEnv, - }); - - expect(backupDir).toBeTruthy(); - expect(fs.existsSync(path.join(credentialsDir, "credentials.json"))).toBe(false); - expect(fs.existsSync(path.join(credentialsDir, "accounts"))).toBe(false); - expect(fs.existsSync(path.join(backupDir as string, "credentials.json"))).toBe(true); - expect(fs.existsSync(path.join(backupDir as string, "accounts", "dummy.txt"))).toBe(true); - expect(fs.existsSync(path.join(backupDir as string, "matrix-config.json"))).toBe(true); - }); - - it("updates matrix config after successful register mode auth", async () => { - const writeConfigFile = vi.fn(async () => {}); - vi.spyOn(runtimeModule, "getMatrixRuntime").mockReturnValue({ - config: { - loadConfig: () => - ({ - channels: { - "matrix-js": { - register: true, - accessToken: "stale-token", - userId: "@pinguini:matrix.gumadeiras.com", - }, - }, - }) as CoreConfig, - writeConfigFile, - }, - } as never); - - const updated = await finalizeMatrixRegisterConfigAfterSuccess({ - homeserver: "https://matrix.gumadeiras.com", - userId: "@pinguini:matrix.gumadeiras.com", - deviceId: "DEVICE123", - }); - expect(updated).toBe(true); - expect(writeConfigFile).toHaveBeenCalledWith( - expect.objectContaining({ - channels: expect.objectContaining({ - "matrix-js": expect.objectContaining({ - register: false, - homeserver: "https://matrix.gumadeiras.com", - userId: "@pinguini:matrix.gumadeiras.com", - deviceId: "DEVICE123", - }), - }), - }), - ); - const firstCall = (writeConfigFile.mock.calls as unknown[][])[0]; - const written = (firstCall?.[0] ?? {}) as CoreConfig; - expect(written.channels?.["matrix-js"]?.accessToken).toBeUndefined(); - }); -}); diff --git a/extensions/matrix-js/src/matrix/client/register-mode.ts b/extensions/matrix-js/src/matrix/client/register-mode.ts deleted file mode 100644 index 4475ffb3cc6..00000000000 --- a/extensions/matrix-js/src/matrix/client/register-mode.ts +++ /dev/null @@ -1,125 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { getMatrixRuntime } from "../../runtime.js"; -import type { CoreConfig } from "../../types.js"; -import { resolveMatrixCredentialsDir } from "../credentials.js"; - -const preparedRegisterKeys = new Set(); - -function resolveStateDirFromEnv(env: NodeJS.ProcessEnv): string { - try { - return getMatrixRuntime().state.resolveStateDir(env, os.homedir); - } catch { - // fall through to deterministic fallback for tests/early init - } - const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); - if (override) { - if (override.startsWith("~")) { - const expanded = override.replace(/^~(?=$|[\\/])/, os.homedir()); - return path.resolve(expanded); - } - return path.resolve(override); - } - return path.join(os.homedir(), ".openclaw"); -} - -function buildRegisterKey(params: { homeserver: string; userId: string }): string { - return `${params.homeserver.trim().toLowerCase()}|${params.userId.trim().toLowerCase()}`; -} - -function buildBackupDirName(now = new Date()): string { - const ts = now.toISOString().replace(/[:.]/g, "-"); - const suffix = Math.random().toString(16).slice(2, 8); - return `${ts}-${suffix}`; -} - -export async function prepareMatrixRegisterMode(params: { - cfg: CoreConfig; - homeserver: string; - userId: string; - env?: NodeJS.ProcessEnv; -}): Promise { - const env = params.env ?? process.env; - const registerKey = buildRegisterKey({ - homeserver: params.homeserver, - userId: params.userId, - }); - if (preparedRegisterKeys.has(registerKey)) { - return null; - } - - const stateDir = resolveStateDirFromEnv(env); - const credentialsDir = resolveMatrixCredentialsDir(env, stateDir); - if (!fs.existsSync(credentialsDir)) { - return null; - } - - const entries = fs.readdirSync(credentialsDir).filter((name) => name !== ".bak"); - if (entries.length === 0) { - return null; - } - - const backupRoot = path.join(credentialsDir, ".bak"); - fs.mkdirSync(backupRoot, { recursive: true }); - const backupDir = path.join(backupRoot, buildBackupDirName()); - fs.mkdirSync(backupDir, { recursive: true }); - - const matrixConfig = params.cfg.channels?.["matrix-js"] ?? {}; - fs.writeFileSync( - path.join(backupDir, "matrix-config.json"), - JSON.stringify(matrixConfig, null, 2).trimEnd().concat("\n"), - "utf-8", - ); - - for (const entry of entries) { - fs.renameSync(path.join(credentialsDir, entry), path.join(backupDir, entry)); - } - - preparedRegisterKeys.add(registerKey); - return backupDir; -} - -export async function finalizeMatrixRegisterConfigAfterSuccess(params: { - homeserver: string; - userId: string; - deviceId?: string; -}): Promise { - let runtime: ReturnType | null = null; - try { - runtime = getMatrixRuntime(); - } catch { - return false; - } - - const cfg = runtime.config.loadConfig() as CoreConfig; - if (cfg.channels?.["matrix-js"]?.register !== true) { - return false; - } - - const matrixCfg = cfg.channels?.["matrix-js"] ?? {}; - const nextMatrix: Record = { - ...matrixCfg, - register: false, - homeserver: params.homeserver, - userId: params.userId, - ...(params.deviceId?.trim() ? { deviceId: params.deviceId.trim() } : {}), - }; - // Registration mode should continue relying on password + cached credentials, not stale inline token. - delete nextMatrix.accessToken; - - const next: CoreConfig = { - ...cfg, - channels: { - ...(cfg.channels ?? {}), - "matrix-js": nextMatrix as NonNullable["matrix-js"], - }, - }; - - await runtime.config.writeConfigFile(next as never); - return true; -} - -export function resetPreparedMatrixRegisterModesForTests(): void { - preparedRegisterKeys.clear(); -} diff --git a/extensions/matrix-js/src/matrix/client/types.ts b/extensions/matrix-js/src/matrix/client/types.ts index 438a16e4243..4a6bac48a40 100644 --- a/extensions/matrix-js/src/matrix/client/types.ts +++ b/extensions/matrix-js/src/matrix/client/types.ts @@ -4,7 +4,6 @@ export type MatrixResolvedConfig = { accessToken?: string; deviceId?: string; password?: string; - register?: boolean; deviceName?: string; initialSyncLimit?: number; encryption?: boolean; diff --git a/extensions/matrix-js/src/onboarding.ts b/extensions/matrix-js/src/onboarding.ts index e4f4121d841..9b06a487312 100644 --- a/extensions/matrix-js/src/onboarding.ts +++ b/extensions/matrix-js/src/onboarding.ts @@ -41,9 +41,8 @@ async function noteMatrixAuthHelp(prompter: WizardPrompter): Promise { await prompter.note( [ "Matrix requires a homeserver URL.", - "Use an access token (recommended), password login, or account registration.", + "Use an access token (recommended) or password login to an existing account.", "With access token: user ID is fetched automatically.", - "Password + register mode can create an account on homeservers with open registration.", "Env vars supported: MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_ACCESS_TOKEN, MATRIX_PASSWORD.", `Docs: ${formatDocsLink("/channels/matrix", "channels/matrix")}`, ].join("\n"), @@ -266,7 +265,6 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { let accessToken = existing.accessToken ?? ""; let password = existing.password ?? ""; let userId = existing.userId ?? ""; - let register = existing.register === true; if (accessToken || password) { const keep = await prompter.confirm({ @@ -277,7 +275,6 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { accessToken = ""; password = ""; userId = ""; - register = false; } } @@ -288,10 +285,6 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { options: [ { value: "token", label: "Access token (user ID fetched automatically)" }, { value: "password", label: "Password (requires user ID)" }, - { - value: "register", - label: "Register account (open homeserver registration required)", - }, ], }); @@ -305,9 +298,8 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { // With access token, we can fetch the userId automatically - don't prompt for it // The client.ts will use whoami() to get it userId = ""; - register = false; } else { - // Password auth and registration mode require user ID upfront + // Password auth requires user ID upfront. userId = String( await prompter.text({ message: "Matrix user ID", @@ -333,7 +325,6 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { validate: (value) => (value?.trim() ? undefined : "Required"), }), ).trim(); - register = authMode === "register"; } } @@ -361,7 +352,6 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { userId: userId || undefined, accessToken: accessToken || undefined, password: password || undefined, - register, deviceName: deviceName || undefined, encryption: enableEncryption || undefined, }, diff --git a/extensions/matrix-js/src/types.ts b/extensions/matrix-js/src/types.ts index 8c2db558be8..94dc13549d7 100644 --- a/extensions/matrix-js/src/types.ts +++ b/extensions/matrix-js/src/types.ts @@ -58,8 +58,6 @@ export type MatrixConfig = { accessToken?: string; /** Matrix password (used only to fetch access token). */ password?: string; - /** Auto-register account when password login fails (open registration homeservers). */ - register?: boolean; /** Optional Matrix device id (recommended when using access tokens + E2EE). */ deviceId?: string; /** Optional device name when logging in via password. */