mirror of https://github.com/openclaw/openclaw.git
Matrix: isolate named account auth
This commit is contained in:
parent
b29af9acad
commit
4edd5c8eeb
|
|
@ -262,6 +262,59 @@ describe("resolveMatrixConfig", () => {
|
|||
expect(resolved.userId).toBe("");
|
||||
});
|
||||
|
||||
it("does not inherit base or global auth secrets for non-default accounts", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
homeserver: "https://base.example.org",
|
||||
accessToken: "base-token",
|
||||
password: "base-pass", // pragma: allowlist secret
|
||||
deviceId: "BASEDEVICE",
|
||||
accounts: {
|
||||
ops: {
|
||||
homeserver: "https://ops.example.org",
|
||||
userId: "@ops:example.org",
|
||||
password: "ops-pass", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
const env = {
|
||||
MATRIX_ACCESS_TOKEN: "global-token",
|
||||
MATRIX_PASSWORD: "global-pass",
|
||||
MATRIX_DEVICE_ID: "GLOBALDEVICE",
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
||||
expect(resolved.accessToken).toBeUndefined();
|
||||
expect(resolved.password).toBe("ops-pass");
|
||||
expect(resolved.deviceId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not inherit a base password for non-default accounts", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
homeserver: "https://base.example.org",
|
||||
password: "base-pass", // pragma: allowlist secret
|
||||
accounts: {
|
||||
ops: {
|
||||
homeserver: "https://ops.example.org",
|
||||
userId: "@ops:example.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
const env = {
|
||||
MATRIX_PASSWORD: "global-pass",
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
||||
expect(resolved.password).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects insecure public http Matrix homeservers", () => {
|
||||
expect(() => validateMatrixHomeserverUrl("http://matrix.example.org")).toThrow(
|
||||
"Matrix homeserver must use https:// unless it targets a private or loopback host",
|
||||
|
|
@ -479,6 +532,56 @@ describe("resolveMatrixAuth", () => {
|
|||
expect(auth.deviceId).toBe("OPSDEVICE");
|
||||
});
|
||||
|
||||
it("uses named-account password auth instead of inheriting the base access token", async () => {
|
||||
vi.mocked(credentialsModule.loadMatrixCredentials).mockReturnValue(null);
|
||||
vi.mocked(credentialsModule.credentialsMatchConfig).mockReturnValue(false);
|
||||
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
|
||||
access_token: "ops-token",
|
||||
user_id: "@ops:example.org",
|
||||
device_id: "OPSDEVICE",
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "legacy-token",
|
||||
accounts: {
|
||||
ops: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@ops:example.org",
|
||||
password: "ops-pass", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
|
||||
const auth = await resolveMatrixAuth({
|
||||
cfg,
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
accountId: "ops",
|
||||
});
|
||||
|
||||
expect(doRequestSpy).toHaveBeenCalledWith(
|
||||
"POST",
|
||||
"/_matrix/client/v3/login",
|
||||
undefined,
|
||||
expect.objectContaining({
|
||||
type: "m.login.password",
|
||||
identifier: { type: "m.id.user", user: "@ops:example.org" },
|
||||
password: "ops-pass",
|
||||
}),
|
||||
);
|
||||
expect(auth).toMatchObject({
|
||||
accountId: "ops",
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@ops:example.org",
|
||||
accessToken: "ops-token",
|
||||
deviceId: "OPSDEVICE",
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves missing whoami identity fields for token auth", async () => {
|
||||
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
|
||||
user_id: "@bot:example.org",
|
||||
|
|
|
|||
|
|
@ -67,16 +67,35 @@ function resolveMatrixStringField(params: {
|
|||
accountValue?: string;
|
||||
scopedEnvValue?: string;
|
||||
globalEnvValue?: string;
|
||||
includeBaseConfig?: boolean;
|
||||
includeGlobalEnv?: boolean;
|
||||
}): string {
|
||||
return (
|
||||
params.accountValue ||
|
||||
params.scopedEnvValue ||
|
||||
readMatrixBaseConfigField(params.matrix, params.field) ||
|
||||
params.globalEnvValue ||
|
||||
(params.includeBaseConfig === false
|
||||
? ""
|
||||
: readMatrixBaseConfigField(params.matrix, params.field)) ||
|
||||
(params.includeGlobalEnv === false ? "" : params.globalEnvValue) ||
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMatrixAccountAuthField(params: {
|
||||
matrix: ReturnType<typeof resolveMatrixBaseConfig>;
|
||||
field: Extract<MatrixConfigStringField, "userId" | "accessToken" | "password" | "deviceId">;
|
||||
accountValue?: string;
|
||||
scopedEnvValue?: string;
|
||||
globalEnvValue?: string;
|
||||
isDefaultAccount: boolean;
|
||||
}): string {
|
||||
return resolveMatrixStringField({
|
||||
...params,
|
||||
includeBaseConfig: params.isDefaultAccount,
|
||||
includeGlobalEnv: params.isDefaultAccount,
|
||||
});
|
||||
}
|
||||
|
||||
function clampMatrixInitialSyncLimit(value: unknown): number | undefined {
|
||||
return typeof value === "number" ? Math.max(0, Math.floor(value)) : undefined;
|
||||
}
|
||||
|
|
@ -248,36 +267,42 @@ export function resolveMatrixConfigForAccount(
|
|||
scopedEnvValue: scopedEnv.homeserver,
|
||||
globalEnvValue: globalEnv.homeserver,
|
||||
});
|
||||
const userIdSource =
|
||||
accountField("userId") ||
|
||||
scopedEnv.userId ||
|
||||
(normalizedAccountId === DEFAULT_ACCOUNT_ID
|
||||
? readMatrixBaseConfigField(matrix, "userId") || globalEnv.userId || ""
|
||||
: "");
|
||||
const userId = userIdSource;
|
||||
const isDefaultAccount = normalizedAccountId === DEFAULT_ACCOUNT_ID;
|
||||
const userId = resolveMatrixAccountAuthField({
|
||||
matrix,
|
||||
field: "userId",
|
||||
accountValue: accountField("userId"),
|
||||
scopedEnvValue: scopedEnv.userId,
|
||||
globalEnvValue: globalEnv.userId,
|
||||
isDefaultAccount,
|
||||
});
|
||||
const accessToken =
|
||||
resolveMatrixStringField({
|
||||
resolveMatrixAccountAuthField({
|
||||
matrix,
|
||||
field: "accessToken",
|
||||
accountValue: accountField("accessToken"),
|
||||
scopedEnvValue: scopedEnv.accessToken,
|
||||
globalEnvValue: globalEnv.accessToken,
|
||||
isDefaultAccount,
|
||||
}) || undefined;
|
||||
const password =
|
||||
resolveMatrixStringField({
|
||||
resolveMatrixAccountAuthField({
|
||||
matrix,
|
||||
field: "password",
|
||||
accountValue: accountField("password"),
|
||||
scopedEnvValue: scopedEnv.password,
|
||||
globalEnvValue: globalEnv.password,
|
||||
isDefaultAccount,
|
||||
}) || undefined;
|
||||
const deviceId =
|
||||
resolveMatrixAccountAuthField({
|
||||
matrix,
|
||||
field: "deviceId",
|
||||
accountValue: accountField("deviceId"),
|
||||
scopedEnvValue: scopedEnv.deviceId,
|
||||
globalEnvValue: globalEnv.deviceId,
|
||||
isDefaultAccount,
|
||||
}) || undefined;
|
||||
const deviceIdSource =
|
||||
accountField("deviceId") ||
|
||||
scopedEnv.deviceId ||
|
||||
(normalizedAccountId === DEFAULT_ACCOUNT_ID
|
||||
? readMatrixBaseConfigField(matrix, "deviceId") || globalEnv.deviceId || ""
|
||||
: "");
|
||||
const deviceId = deviceIdSource || undefined;
|
||||
const deviceName =
|
||||
resolveMatrixStringField({
|
||||
matrix,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ describe("ensureMatrixCryptoRuntime", () => {
|
|||
it("rethrows non-crypto module errors without bootstrapping", async () => {
|
||||
const runCommand = vi.fn();
|
||||
const requireFn = vi.fn(() => {
|
||||
throw new Error("Cannot find module '@vector-im/matrix-bot-sdk'");
|
||||
throw new Error("Cannot find module 'not-the-matrix-crypto-runtime'");
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
|
@ -66,7 +66,7 @@ describe("ensureMatrixCryptoRuntime", () => {
|
|||
resolveFn: () => "/tmp/download-lib.js",
|
||||
nodeExecutable: "/usr/bin/node",
|
||||
}),
|
||||
).rejects.toThrow("Cannot find module '@vector-im/matrix-bot-sdk'");
|
||||
).rejects.toThrow("Cannot find module 'not-the-matrix-crypto-runtime'");
|
||||
|
||||
expect(runCommand).not.toHaveBeenCalled();
|
||||
expect(requireFn).toHaveBeenCalledTimes(1);
|
||||
|
|
|
|||
Loading…
Reference in New Issue