@archipelagolab/lobi 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +164 -0
- package/ENDOFFILE +0 -0
- package/EOF +0 -0
- package/LICENSE +21 -0
- package/SPEC-SUPPORT.md +116 -0
- package/YAMLEND +0 -0
- package/api.ts +18 -0
- package/archipelagolab-lobi-1.0.0.tgz +0 -0
- package/auth-presence.ts +56 -0
- package/channel-plugin-api.ts +3 -0
- package/cli-metadata.ts +11 -0
- package/contract-api.ts +17 -0
- package/docs/CHECKLIST.md +83 -0
- package/docs/FORK_SDK_GUIDE.md +279 -0
- package/helper-api.ts +3 -0
- package/index.test.ts +61 -0
- package/index.ts +65 -0
- package/openclaw.plugin.json +23 -0
- package/package.json +52 -0
- package/plugin-entry.handlers.runtime.ts +1 -0
- package/runtime-api.ts +54 -0
- package/runtime-heavy-api.ts +1 -0
- package/scripts/migrate-to-lobi.sh +72 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +13 -0
- package/src/account-selection.test.ts +124 -0
- package/src/account-selection.ts +226 -0
- package/src/actions.account-propagation.test.ts +251 -0
- package/src/actions.test.ts +251 -0
- package/src/actions.ts +336 -0
- package/src/approval-auth.test.ts +23 -0
- package/src/approval-auth.ts +25 -0
- package/src/approval-handler.runtime.test.ts +46 -0
- package/src/approval-handler.runtime.ts +400 -0
- package/src/approval-ids.ts +6 -0
- package/src/approval-native.test.ts +329 -0
- package/src/approval-native.ts +336 -0
- package/src/approval-reactions.test.ts +107 -0
- package/src/approval-reactions.ts +158 -0
- package/src/auth-precedence.ts +61 -0
- package/src/channel-account-paths.ts +92 -0
- package/src/channel.account-paths.test.ts +102 -0
- package/src/channel.directory.test.ts +601 -0
- package/src/channel.resolve.test.ts +38 -0
- package/src/channel.runtime.ts +16 -0
- package/src/channel.setup.test.ts +269 -0
- package/src/channel.ts +570 -0
- package/src/cli-metadata.ts +19 -0
- package/src/cli.test.ts +1015 -0
- package/src/cli.ts +1198 -0
- package/src/config-adapter.ts +41 -0
- package/src/config-schema.test.ts +90 -0
- package/src/config-schema.ts +114 -0
- package/src/directory-live.test.ts +200 -0
- package/src/directory-live.ts +238 -0
- package/src/doctor-contract.ts +287 -0
- package/src/doctor.test.ts +440 -0
- package/src/doctor.ts +262 -0
- package/src/env-vars.ts +92 -0
- package/src/exec-approval-resolver.test.ts +68 -0
- package/src/exec-approval-resolver.ts +23 -0
- package/src/exec-approvals.test.ts +483 -0
- package/src/exec-approvals.ts +290 -0
- package/src/group-mentions.ts +41 -0
- package/src/legacy-crypto-inspector-availability.test.ts +81 -0
- package/src/legacy-crypto-inspector-availability.ts +60 -0
- package/src/legacy-crypto.test.ts +234 -0
- package/src/legacy-crypto.ts +549 -0
- package/src/legacy-state.test.ts +86 -0
- package/src/legacy-state.ts +156 -0
- package/src/matrix/account-config.ts +150 -0
- package/src/matrix/accounts.readiness.test.ts +27 -0
- package/src/matrix/accounts.test.ts +757 -0
- package/src/matrix/accounts.ts +194 -0
- package/src/matrix/actions/client.test.ts +215 -0
- package/src/matrix/actions/client.ts +31 -0
- package/src/matrix/actions/devices.test.ts +114 -0
- package/src/matrix/actions/devices.ts +34 -0
- package/src/matrix/actions/limits.test.ts +15 -0
- package/src/matrix/actions/limits.ts +6 -0
- package/src/matrix/actions/messages.test.ts +289 -0
- package/src/matrix/actions/messages.ts +123 -0
- package/src/matrix/actions/pins.test.ts +74 -0
- package/src/matrix/actions/pins.ts +64 -0
- package/src/matrix/actions/polls.test.ts +71 -0
- package/src/matrix/actions/polls.ts +109 -0
- package/src/matrix/actions/profile.test.ts +109 -0
- package/src/matrix/actions/profile.ts +37 -0
- package/src/matrix/actions/reactions.test.ts +135 -0
- package/src/matrix/actions/reactions.ts +59 -0
- package/src/matrix/actions/room.test.ts +79 -0
- package/src/matrix/actions/room.ts +71 -0
- package/src/matrix/actions/summary.test.ts +87 -0
- package/src/matrix/actions/summary.ts +88 -0
- package/src/matrix/actions/types.ts +82 -0
- package/src/matrix/actions/verification.test.ts +105 -0
- package/src/matrix/actions/verification.ts +237 -0
- package/src/matrix/actions.ts +37 -0
- package/src/matrix/active-client.ts +26 -0
- package/src/matrix/async-lock.ts +18 -0
- package/src/matrix/backup-health.ts +115 -0
- package/src/matrix/client/config-runtime-api.ts +14 -0
- package/src/matrix/client/config-secret-input.runtime.ts +1 -0
- package/src/matrix/client/config.ts +982 -0
- package/src/matrix/client/create-client.test.ts +115 -0
- package/src/matrix/client/create-client.ts +101 -0
- package/src/matrix/client/env-auth.ts +6 -0
- package/src/matrix/client/file-sync-store.test.ts +265 -0
- package/src/matrix/client/file-sync-store.ts +289 -0
- package/src/matrix/client/logging.ts +123 -0
- package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
- package/src/matrix/client/private-network-host.ts +56 -0
- package/src/matrix/client/runtime.ts +4 -0
- package/src/matrix/client/shared.test.ts +344 -0
- package/src/matrix/client/shared.ts +306 -0
- package/src/matrix/client/storage.test.ts +634 -0
- package/src/matrix/client/storage.ts +544 -0
- package/src/matrix/client/types.ts +50 -0
- package/src/matrix/client-bootstrap.test.ts +84 -0
- package/src/matrix/client-bootstrap.ts +164 -0
- package/src/matrix/client-resolver.test-helpers.ts +147 -0
- package/src/matrix/client.test.ts +1521 -0
- package/src/matrix/client.ts +23 -0
- package/src/matrix/config-paths.ts +31 -0
- package/src/matrix/config-update.test.ts +237 -0
- package/src/matrix/config-update.ts +291 -0
- package/src/matrix/credentials-read.ts +206 -0
- package/src/matrix/credentials-write.runtime.ts +26 -0
- package/src/matrix/credentials.test.ts +501 -0
- package/src/matrix/credentials.ts +95 -0
- package/src/matrix/deps.test.ts +74 -0
- package/src/matrix/deps.ts +225 -0
- package/src/matrix/device-health.test.ts +45 -0
- package/src/matrix/device-health.ts +31 -0
- package/src/matrix/direct-management.test.ts +350 -0
- package/src/matrix/direct-management.ts +347 -0
- package/src/matrix/direct-room.test.ts +61 -0
- package/src/matrix/direct-room.ts +128 -0
- package/src/matrix/draft-stream.test.ts +406 -0
- package/src/matrix/draft-stream.ts +216 -0
- package/src/matrix/encryption-guidance.ts +27 -0
- package/src/matrix/errors.ts +21 -0
- package/src/matrix/format.test.ts +340 -0
- package/src/matrix/format.ts +428 -0
- package/src/matrix/legacy-crypto-inspector.ts +95 -0
- package/src/matrix/media-errors.ts +20 -0
- package/src/matrix/media-text.ts +169 -0
- package/src/matrix/monitor/access-state.test.ts +45 -0
- package/src/matrix/monitor/access-state.ts +77 -0
- package/src/matrix/monitor/ack-config.test.ts +57 -0
- package/src/matrix/monitor/ack-config.ts +26 -0
- package/src/matrix/monitor/allowlist.test.ts +45 -0
- package/src/matrix/monitor/allowlist.ts +94 -0
- package/src/matrix/monitor/auto-join.test.ts +203 -0
- package/src/matrix/monitor/auto-join.ts +86 -0
- package/src/matrix/monitor/config.test.ts +197 -0
- package/src/matrix/monitor/config.ts +303 -0
- package/src/matrix/monitor/context-summary.ts +43 -0
- package/src/matrix/monitor/direct.test.ts +529 -0
- package/src/matrix/monitor/direct.ts +270 -0
- package/src/matrix/monitor/events.test.ts +1524 -0
- package/src/matrix/monitor/events.ts +213 -0
- package/src/matrix/monitor/handler.body-for-agent.test.ts +396 -0
- package/src/matrix/monitor/handler.group-history.test.ts +648 -0
- package/src/matrix/monitor/handler.media-failure.test.ts +267 -0
- package/src/matrix/monitor/handler.test-helpers.ts +308 -0
- package/src/matrix/monitor/handler.test.ts +2952 -0
- package/src/matrix/monitor/handler.thread-root-media.test.ts +82 -0
- package/src/matrix/monitor/handler.ts +1679 -0
- package/src/matrix/monitor/inbound-dedupe.test.ts +146 -0
- package/src/matrix/monitor/inbound-dedupe.ts +267 -0
- package/src/matrix/monitor/index.test.ts +920 -0
- package/src/matrix/monitor/index.ts +434 -0
- package/src/matrix/monitor/legacy-crypto-restore.test.ts +206 -0
- package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
- package/src/matrix/monitor/location.ts +100 -0
- package/src/matrix/monitor/media.test.ts +159 -0
- package/src/matrix/monitor/media.ts +119 -0
- package/src/matrix/monitor/mentions.test.ts +289 -0
- package/src/matrix/monitor/mentions.ts +177 -0
- package/src/matrix/monitor/reaction-events.test.ts +326 -0
- package/src/matrix/monitor/reaction-events.ts +187 -0
- package/src/matrix/monitor/recent-invite.test.ts +92 -0
- package/src/matrix/monitor/recent-invite.ts +30 -0
- package/src/matrix/monitor/replies.test.ts +265 -0
- package/src/matrix/monitor/replies.ts +136 -0
- package/src/matrix/monitor/reply-context.test.ts +276 -0
- package/src/matrix/monitor/reply-context.ts +92 -0
- package/src/matrix/monitor/room-history.test.ts +258 -0
- package/src/matrix/monitor/room-history.ts +301 -0
- package/src/matrix/monitor/room-info.test.ts +201 -0
- package/src/matrix/monitor/room-info.ts +126 -0
- package/src/matrix/monitor/rooms.test.ts +121 -0
- package/src/matrix/monitor/rooms.ts +52 -0
- package/src/matrix/monitor/route.test.ts +255 -0
- package/src/matrix/monitor/route.ts +178 -0
- package/src/matrix/monitor/runtime-api.ts +31 -0
- package/src/matrix/monitor/startup-verification.test.ts +294 -0
- package/src/matrix/monitor/startup-verification.ts +237 -0
- package/src/matrix/monitor/startup.test.ts +257 -0
- package/src/matrix/monitor/startup.ts +218 -0
- package/src/matrix/monitor/status.ts +111 -0
- package/src/matrix/monitor/sync-lifecycle.test.ts +224 -0
- package/src/matrix/monitor/sync-lifecycle.ts +91 -0
- package/src/matrix/monitor/task-runner.ts +38 -0
- package/src/matrix/monitor/thread-context.test.ts +149 -0
- package/src/matrix/monitor/thread-context.ts +108 -0
- package/src/matrix/monitor/threads.test.ts +68 -0
- package/src/matrix/monitor/threads.ts +85 -0
- package/src/matrix/monitor/types.ts +30 -0
- package/src/matrix/monitor/verification-events.ts +627 -0
- package/src/matrix/monitor/verification-utils.test.ts +47 -0
- package/src/matrix/monitor/verification-utils.ts +46 -0
- package/src/matrix/outbound-media-runtime.ts +1 -0
- package/src/matrix/poll-summary.ts +110 -0
- package/src/matrix/poll-types.test.ts +205 -0
- package/src/matrix/poll-types.ts +433 -0
- package/src/matrix/probe.runtime.ts +4 -0
- package/src/matrix/probe.test.ts +154 -0
- package/src/matrix/probe.ts +96 -0
- package/src/matrix/profile.test.ts +154 -0
- package/src/matrix/profile.ts +184 -0
- package/src/matrix/reaction-common.test.ts +96 -0
- package/src/matrix/reaction-common.ts +147 -0
- package/src/matrix/sdk/crypto-bootstrap.test.ts +505 -0
- package/src/matrix/sdk/crypto-bootstrap.ts +341 -0
- package/src/matrix/sdk/crypto-facade.test.ts +197 -0
- package/src/matrix/sdk/crypto-facade.ts +207 -0
- package/src/matrix/sdk/crypto-node.runtime.test.ts +27 -0
- package/src/matrix/sdk/crypto-node.runtime.ts +9 -0
- package/src/matrix/sdk/crypto-runtime.ts +11 -0
- package/src/matrix/sdk/decrypt-bridge.ts +356 -0
- package/src/matrix/sdk/event-helpers.test.ts +60 -0
- package/src/matrix/sdk/event-helpers.ts +71 -0
- package/src/matrix/sdk/http-client.test.ts +134 -0
- package/src/matrix/sdk/http-client.ts +87 -0
- package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
- package/src/matrix/sdk/idb-persistence.lock-order.test.ts +108 -0
- package/src/matrix/sdk/idb-persistence.test-helpers.ts +88 -0
- package/src/matrix/sdk/idb-persistence.test.ts +149 -0
- package/src/matrix/sdk/idb-persistence.ts +283 -0
- package/src/matrix/sdk/logger.test.ts +25 -0
- package/src/matrix/sdk/logger.ts +108 -0
- package/src/matrix/sdk/read-response-with-limit.ts +19 -0
- package/src/matrix/sdk/recovery-key-store.test.ts +385 -0
- package/src/matrix/sdk/recovery-key-store.ts +430 -0
- package/src/matrix/sdk/transport.test.ts +161 -0
- package/src/matrix/sdk/transport.ts +344 -0
- package/src/matrix/sdk/types.ts +236 -0
- package/src/matrix/sdk/verification-manager.test.ts +509 -0
- package/src/matrix/sdk/verification-manager.ts +694 -0
- package/src/matrix/sdk/verification-status.ts +23 -0
- package/src/matrix/sdk.test.ts +2568 -0
- package/src/matrix/sdk.ts +1789 -0
- package/src/matrix/send/client.test.ts +174 -0
- package/src/matrix/send/client.ts +90 -0
- package/src/matrix/send/formatting.ts +189 -0
- package/src/matrix/send/media.ts +244 -0
- package/src/matrix/send/targets.test.ts +254 -0
- package/src/matrix/send/targets.ts +104 -0
- package/src/matrix/send/types.ts +134 -0
- package/src/matrix/send.test.ts +958 -0
- package/src/matrix/send.ts +609 -0
- package/src/matrix/session-store-metadata.ts +108 -0
- package/src/matrix/startup-abort.ts +44 -0
- package/src/matrix/sync-state.ts +27 -0
- package/src/matrix/target-ids.ts +102 -0
- package/src/matrix/thread-bindings-shared.ts +201 -0
- package/src/matrix/thread-bindings.test.ts +673 -0
- package/src/matrix/thread-bindings.ts +577 -0
- package/src/matrix-migration.runtime.ts +9 -0
- package/src/migration-config.test.ts +228 -0
- package/src/migration-config.ts +243 -0
- package/src/migration-snapshot-backup.ts +117 -0
- package/src/migration-snapshot.test.ts +184 -0
- package/src/migration-snapshot.ts +55 -0
- package/src/onboarding.resolve.test.ts +55 -0
- package/src/onboarding.test-harness.ts +158 -0
- package/src/onboarding.test.ts +665 -0
- package/src/onboarding.ts +773 -0
- package/src/outbound.test.ts +173 -0
- package/src/outbound.ts +78 -0
- package/src/plugin-entry.runtime.js +159 -0
- package/src/plugin-entry.runtime.test.ts +108 -0
- package/src/plugin-entry.runtime.ts +68 -0
- package/src/profile-update.ts +68 -0
- package/src/record-shared.ts +3 -0
- package/src/resolve-targets.test.ts +178 -0
- package/src/resolve-targets.ts +175 -0
- package/src/resolver.ts +21 -0
- package/src/runtime-api.ts +144 -0
- package/src/runtime.ts +7 -0
- package/src/secret-contract.ts +174 -0
- package/src/session-route.test.ts +315 -0
- package/src/session-route.ts +113 -0
- package/src/setup-bootstrap.ts +94 -0
- package/src/setup-config.ts +222 -0
- package/src/setup-contract.ts +89 -0
- package/src/setup-core.test.ts +326 -0
- package/src/setup-core.ts +50 -0
- package/src/setup-surface.ts +4 -0
- package/src/startup-maintenance.test.ts +227 -0
- package/src/startup-maintenance.ts +114 -0
- package/src/storage-paths.ts +92 -0
- package/src/test-helpers.ts +42 -0
- package/src/test-mocks.ts +55 -0
- package/src/test-runtime.ts +72 -0
- package/src/test-support/monitor-route-test-support.ts +8 -0
- package/src/tool-actions.runtime.ts +1 -0
- package/src/tool-actions.test.ts +422 -0
- package/src/tool-actions.ts +498 -0
- package/src/types.ts +230 -0
- package/test-api.ts +2 -0
- package/thread-bindings-runtime.ts +4 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type { LookupFn } from "../../runtime-api.js";
|
|
6
|
+
import { installMatrixTestRuntime } from "../test-runtime.js";
|
|
7
|
+
import type { CoreConfig } from "../types.js";
|
|
8
|
+
|
|
9
|
+
function createLookupFn(addresses: Array<{ address: string; family: number }>): LookupFn {
|
|
10
|
+
return vi.fn(async (_hostname: string, options?: unknown) => {
|
|
11
|
+
if (typeof options === "number" || !options || !(options as { all?: boolean }).all) {
|
|
12
|
+
return addresses[0];
|
|
13
|
+
}
|
|
14
|
+
return addresses;
|
|
15
|
+
}) as unknown as LookupFn;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const saveMatrixCredentialsMock = vi.hoisted(() => vi.fn());
|
|
19
|
+
const saveBackfilledMatrixDeviceIdMock = vi.hoisted(() => vi.fn(async () => "saved"));
|
|
20
|
+
const touchMatrixCredentialsMock = vi.hoisted(() => vi.fn());
|
|
21
|
+
const repairCurrentTokenStorageMetaDeviceIdMock = vi.hoisted(() => vi.fn());
|
|
22
|
+
|
|
23
|
+
vi.mock("./credentials-read.js", () => ({
|
|
24
|
+
loadMatrixCredentials: vi.fn(() => null),
|
|
25
|
+
credentialsMatchConfig: vi.fn(() => false),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("./credentials-write.runtime.js", () => ({
|
|
29
|
+
saveBackfilledMatrixDeviceId: saveBackfilledMatrixDeviceIdMock,
|
|
30
|
+
saveMatrixCredentials: saveMatrixCredentialsMock,
|
|
31
|
+
touchMatrixCredentials: touchMatrixCredentialsMock,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("./client/storage.js", async () => {
|
|
35
|
+
const actual = await vi.importActual<typeof import("./client/storage.js")>("./client/storage.js");
|
|
36
|
+
return {
|
|
37
|
+
...actual,
|
|
38
|
+
repairCurrentTokenStorageMetaDeviceId: repairCurrentTokenStorageMetaDeviceIdMock,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
backfillMatrixAuthDeviceIdAfterStartup,
|
|
44
|
+
getMatrixScopedEnvVarNames,
|
|
45
|
+
resolveMatrixConfigForAccount,
|
|
46
|
+
resolveMatrixAuth,
|
|
47
|
+
resolveMatrixAuthContext,
|
|
48
|
+
setMatrixAuthClientDepsForTest,
|
|
49
|
+
resolveValidatedMatrixHomeserverUrl,
|
|
50
|
+
validateMatrixHomeserverUrl,
|
|
51
|
+
} = await import("./client/config.js");
|
|
52
|
+
|
|
53
|
+
let credentialsReadModule: typeof import("./credentials-read.js") | undefined;
|
|
54
|
+
const ensureMatrixSdkLoggingConfiguredMock = vi.fn();
|
|
55
|
+
const matrixDoRequestMock = vi.fn();
|
|
56
|
+
|
|
57
|
+
class MockMatrixClient {
|
|
58
|
+
async doRequest(...args: unknown[]) {
|
|
59
|
+
return await matrixDoRequestMock(...args);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function requireCredentialsReadModule(): typeof import("./credentials-read.js") {
|
|
64
|
+
if (!credentialsReadModule) {
|
|
65
|
+
throw new Error("credentials-read test module not initialized");
|
|
66
|
+
}
|
|
67
|
+
return credentialsReadModule;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveDefaultMatrixAuthContext(
|
|
71
|
+
cfg: CoreConfig,
|
|
72
|
+
env: NodeJS.ProcessEnv = {} as NodeJS.ProcessEnv,
|
|
73
|
+
) {
|
|
74
|
+
return resolveMatrixAuthContext({ cfg, env });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
installMatrixTestRuntime();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("Matrix auth/config live surfaces", () => {
|
|
82
|
+
it("prefers config over env", () => {
|
|
83
|
+
const cfg = {
|
|
84
|
+
channels: {
|
|
85
|
+
matrix: {
|
|
86
|
+
homeserver: "https://cfg.example.org",
|
|
87
|
+
userId: "@cfg:example.org",
|
|
88
|
+
accessToken: "cfg-token",
|
|
89
|
+
password: "cfg-pass",
|
|
90
|
+
deviceName: "CfgDevice",
|
|
91
|
+
initialSyncLimit: 5,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
} as CoreConfig;
|
|
95
|
+
const env = {
|
|
96
|
+
LOBI_HOMESERVER: "https://env.example.org",
|
|
97
|
+
LOBI_USER_ID: "@env:example.org",
|
|
98
|
+
LOBI_ACCESS_TOKEN: "env-token",
|
|
99
|
+
LOBI_PASSWORD: "env-pass",
|
|
100
|
+
LOBI_DEVICE_NAME: "EnvDevice",
|
|
101
|
+
} as NodeJS.ProcessEnv;
|
|
102
|
+
const resolved = resolveDefaultMatrixAuthContext(cfg, env).resolved;
|
|
103
|
+
expect(resolved).toEqual({
|
|
104
|
+
homeserver: "https://cfg.example.org",
|
|
105
|
+
userId: "@cfg:example.org",
|
|
106
|
+
accessToken: "cfg-token",
|
|
107
|
+
password: "cfg-pass",
|
|
108
|
+
deviceId: undefined,
|
|
109
|
+
deviceName: "CfgDevice",
|
|
110
|
+
initialSyncLimit: 5,
|
|
111
|
+
encryption: false,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("uses env when config is missing", () => {
|
|
116
|
+
const cfg = {} as CoreConfig;
|
|
117
|
+
const env = {
|
|
118
|
+
LOBI_HOMESERVER: "https://env.example.org",
|
|
119
|
+
LOBI_USER_ID: "@env:example.org",
|
|
120
|
+
LOBI_ACCESS_TOKEN: "env-token",
|
|
121
|
+
LOBI_PASSWORD: "env-pass",
|
|
122
|
+
LOBI_DEVICE_ID: "ENVDEVICE",
|
|
123
|
+
LOBI_DEVICE_NAME: "EnvDevice",
|
|
124
|
+
} as NodeJS.ProcessEnv;
|
|
125
|
+
const resolved = resolveDefaultMatrixAuthContext(cfg, env).resolved;
|
|
126
|
+
expect(resolved.homeserver).toBe("https://env.example.org");
|
|
127
|
+
expect(resolved.userId).toBe("@env:example.org");
|
|
128
|
+
expect(resolved.accessToken).toBe("env-token");
|
|
129
|
+
expect(resolved.password).toBe("env-pass");
|
|
130
|
+
expect(resolved.deviceId).toBe("ENVDEVICE");
|
|
131
|
+
expect(resolved.deviceName).toBe("EnvDevice");
|
|
132
|
+
expect(resolved.initialSyncLimit).toBeUndefined();
|
|
133
|
+
expect(resolved.encryption).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("resolves accessToken SecretRef against the provided env", () => {
|
|
137
|
+
const cfg = {
|
|
138
|
+
channels: {
|
|
139
|
+
matrix: {
|
|
140
|
+
homeserver: "https://cfg.example.org",
|
|
141
|
+
accessToken: { source: "env", provider: "default", id: "LOBI_ACCESS_TOKEN" },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
secrets: {
|
|
145
|
+
defaults: {
|
|
146
|
+
env: "default",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
} as CoreConfig;
|
|
150
|
+
const env = {
|
|
151
|
+
LOBI_ACCESS_TOKEN: "env-token",
|
|
152
|
+
} as NodeJS.ProcessEnv;
|
|
153
|
+
|
|
154
|
+
const resolved = resolveDefaultMatrixAuthContext(cfg, env).resolved;
|
|
155
|
+
expect(resolved.accessToken).toBe("env-token");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("resolves password SecretRef against the provided env", () => {
|
|
159
|
+
const cfg = {
|
|
160
|
+
channels: {
|
|
161
|
+
matrix: {
|
|
162
|
+
homeserver: "https://cfg.example.org",
|
|
163
|
+
userId: "@cfg:example.org",
|
|
164
|
+
password: { source: "env", provider: "default", id: "LOBI_PASSWORD" },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
secrets: {
|
|
168
|
+
defaults: {
|
|
169
|
+
env: "default",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
} as CoreConfig;
|
|
173
|
+
const env = {
|
|
174
|
+
LOBI_PASSWORD: "env-pass",
|
|
175
|
+
} as NodeJS.ProcessEnv;
|
|
176
|
+
|
|
177
|
+
const resolved = resolveDefaultMatrixAuthContext(cfg, env).resolved;
|
|
178
|
+
expect(resolved.password).toBe("env-pass");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("resolves account accessToken SecretRef against the provided env", () => {
|
|
182
|
+
const cfg = {
|
|
183
|
+
channels: {
|
|
184
|
+
matrix: {
|
|
185
|
+
accounts: {
|
|
186
|
+
ops: {
|
|
187
|
+
homeserver: "https://ops.example.org",
|
|
188
|
+
accessToken: { source: "env", provider: "default", id: "LOBI_OPS_ACCESS_TOKEN" },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
secrets: {
|
|
194
|
+
defaults: {
|
|
195
|
+
env: "default",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
} as CoreConfig;
|
|
199
|
+
const env = {
|
|
200
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
201
|
+
} as NodeJS.ProcessEnv;
|
|
202
|
+
|
|
203
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
|
204
|
+
expect(resolved.accessToken).toBe("ops-token");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("does not resolve account password SecretRefs when scoped token auth is configured", () => {
|
|
208
|
+
const cfg = {
|
|
209
|
+
channels: {
|
|
210
|
+
matrix: {
|
|
211
|
+
accounts: {
|
|
212
|
+
ops: {
|
|
213
|
+
homeserver: "https://ops.example.org",
|
|
214
|
+
password: { source: "env", provider: "default", id: "MATRIX_OPS_PASSWORD" },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
secrets: {
|
|
220
|
+
defaults: {
|
|
221
|
+
env: "default",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
} as CoreConfig;
|
|
225
|
+
const env = {
|
|
226
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
227
|
+
} as NodeJS.ProcessEnv;
|
|
228
|
+
|
|
229
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
|
230
|
+
expect(resolved.accessToken).toBe("ops-token");
|
|
231
|
+
expect(resolved.password).toBeUndefined();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("keeps unresolved accessToken SecretRef errors when env fallback is missing", () => {
|
|
235
|
+
const cfg = {
|
|
236
|
+
channels: {
|
|
237
|
+
matrix: {
|
|
238
|
+
homeserver: "https://cfg.example.org",
|
|
239
|
+
accessToken: { source: "env", provider: "default", id: "LOBI_ACCESS_TOKEN" },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
secrets: {
|
|
243
|
+
defaults: {
|
|
244
|
+
env: "default",
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
} as CoreConfig;
|
|
248
|
+
|
|
249
|
+
expect(() => resolveDefaultMatrixAuthContext(cfg, {} as NodeJS.ProcessEnv)).toThrow(
|
|
250
|
+
/channels\.matrix\.accessToken: unresolved SecretRef "env:default:LOBI_ACCESS_TOKEN"/i,
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("does not bypass env provider allowlists during startup fallback", () => {
|
|
255
|
+
const cfg = {
|
|
256
|
+
channels: {
|
|
257
|
+
matrix: {
|
|
258
|
+
homeserver: "https://cfg.example.org",
|
|
259
|
+
accessToken: { source: "env", provider: "matrix-env", id: "LOBI_ACCESS_TOKEN" },
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
secrets: {
|
|
263
|
+
providers: {
|
|
264
|
+
"matrix-env": {
|
|
265
|
+
source: "env",
|
|
266
|
+
allowlist: ["OTHER_LOBI_ACCESS_TOKEN"],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
} as CoreConfig;
|
|
271
|
+
|
|
272
|
+
expect(() =>
|
|
273
|
+
resolveDefaultMatrixAuthContext(cfg, {
|
|
274
|
+
LOBI_ACCESS_TOKEN: "env-token",
|
|
275
|
+
} as NodeJS.ProcessEnv),
|
|
276
|
+
).toThrow(/not allowlisted in secrets\.providers\.matrix-env\.allowlist/i);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("does not throw when accessToken uses a non-env SecretRef", () => {
|
|
280
|
+
const cfg = {
|
|
281
|
+
channels: {
|
|
282
|
+
matrix: {
|
|
283
|
+
homeserver: "https://cfg.example.org",
|
|
284
|
+
accessToken: { source: "file", provider: "matrix-file", id: "value" },
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
secrets: {
|
|
288
|
+
providers: {
|
|
289
|
+
"matrix-file": {
|
|
290
|
+
source: "file",
|
|
291
|
+
path: "/tmp/matrix-token",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
} as CoreConfig;
|
|
296
|
+
|
|
297
|
+
expect(
|
|
298
|
+
resolveDefaultMatrixAuthContext(cfg, {} as NodeJS.ProcessEnv).resolved.accessToken,
|
|
299
|
+
).toBeUndefined();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("uses account-scoped env vars for non-default accounts before global env", () => {
|
|
303
|
+
const cfg = {
|
|
304
|
+
channels: {
|
|
305
|
+
matrix: {
|
|
306
|
+
homeserver: "https://base.example.org",
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
} as CoreConfig;
|
|
310
|
+
const env = {
|
|
311
|
+
LOBI_HOMESERVER: "https://global.example.org",
|
|
312
|
+
LOBI_ACCESS_TOKEN: "global-token",
|
|
313
|
+
LOBI_OPS_HOMESERVER: "https://ops.example.org",
|
|
314
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
315
|
+
LOBI_OPS_DEVICE_NAME: "Ops Device",
|
|
316
|
+
} as NodeJS.ProcessEnv;
|
|
317
|
+
|
|
318
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
|
319
|
+
expect(resolved.homeserver).toBe("https://ops.example.org");
|
|
320
|
+
expect(resolved.accessToken).toBe("ops-token");
|
|
321
|
+
expect(resolved.deviceName).toBe("Ops Device");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("uses collision-free scoped env var names for normalized account ids", () => {
|
|
325
|
+
expect(getMatrixScopedEnvVarNames("ops-prod").accessToken).toBe(
|
|
326
|
+
"MATRIX_OPS_X2D_PROD_ACCESS_TOKEN",
|
|
327
|
+
);
|
|
328
|
+
expect(getMatrixScopedEnvVarNames("ops_prod").accessToken).toBe(
|
|
329
|
+
"MATRIX_OPS_X5F_PROD_ACCESS_TOKEN",
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("prefers channels.lobi.accounts.default over global env for the default account", () => {
|
|
334
|
+
const cfg = {
|
|
335
|
+
channels: {
|
|
336
|
+
matrix: {
|
|
337
|
+
accounts: {
|
|
338
|
+
default: {
|
|
339
|
+
homeserver: "https://matrix.gumadeiras.com",
|
|
340
|
+
userId: "@pinguini:matrix.gumadeiras.com",
|
|
341
|
+
password: "cfg-pass", // pragma: allowlist secret
|
|
342
|
+
deviceName: "OpenClaw Gateway Pinguini",
|
|
343
|
+
encryption: true,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
} as CoreConfig;
|
|
349
|
+
const env = {
|
|
350
|
+
LOBI_HOMESERVER: "https://env.example.org",
|
|
351
|
+
LOBI_USER_ID: "@env:example.org",
|
|
352
|
+
LOBI_PASSWORD: "env-pass",
|
|
353
|
+
LOBI_DEVICE_NAME: "EnvDevice",
|
|
354
|
+
} as NodeJS.ProcessEnv;
|
|
355
|
+
|
|
356
|
+
const resolved = resolveMatrixAuthContext({ cfg, env });
|
|
357
|
+
expect(resolved.accountId).toBe("default");
|
|
358
|
+
expect(resolved.resolved).toMatchObject({
|
|
359
|
+
homeserver: "https://matrix.gumadeiras.com",
|
|
360
|
+
userId: "@pinguini:matrix.gumadeiras.com",
|
|
361
|
+
password: "cfg-pass",
|
|
362
|
+
deviceName: "OpenClaw Gateway Pinguini",
|
|
363
|
+
encryption: true,
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("ignores typoed defaultAccount values that do not map to a real Matrix account", () => {
|
|
368
|
+
const cfg = {
|
|
369
|
+
channels: {
|
|
370
|
+
matrix: {
|
|
371
|
+
defaultAccount: "ops",
|
|
372
|
+
homeserver: "https://legacy.example.org",
|
|
373
|
+
accessToken: "legacy-token",
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
} as CoreConfig;
|
|
377
|
+
|
|
378
|
+
expect(resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv }).accountId).toBe(
|
|
379
|
+
"default",
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("requires explicit defaultAccount selection when multiple named Matrix accounts exist", () => {
|
|
384
|
+
const cfg = {
|
|
385
|
+
channels: {
|
|
386
|
+
matrix: {
|
|
387
|
+
accounts: {
|
|
388
|
+
assistant: {
|
|
389
|
+
homeserver: "https://matrix.assistant.example.org",
|
|
390
|
+
accessToken: "assistant-token",
|
|
391
|
+
},
|
|
392
|
+
ops: {
|
|
393
|
+
homeserver: "https://matrix.ops.example.org",
|
|
394
|
+
accessToken: "ops-token",
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
} as CoreConfig;
|
|
400
|
+
|
|
401
|
+
expect(() => resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv })).toThrow(
|
|
402
|
+
/channels\.matrix\.defaultAccount.*--account <id>/i,
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("does not materialize a default account from shared top-level defaults alone", () => {
|
|
407
|
+
const cfg = {
|
|
408
|
+
channels: {
|
|
409
|
+
matrix: {
|
|
410
|
+
name: "Shared Defaults",
|
|
411
|
+
accounts: {
|
|
412
|
+
ops: {
|
|
413
|
+
homeserver: "https://matrix.ops.example.org",
|
|
414
|
+
accessToken: "ops-token",
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
} as CoreConfig;
|
|
420
|
+
|
|
421
|
+
expect(resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv }).accountId).toBe("ops");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("does not materialize a default account from partial top-level auth defaults", () => {
|
|
425
|
+
const cfg = {
|
|
426
|
+
channels: {
|
|
427
|
+
matrix: {
|
|
428
|
+
accessToken: "shared-token",
|
|
429
|
+
accounts: {
|
|
430
|
+
ops: {
|
|
431
|
+
homeserver: "https://matrix.ops.example.org",
|
|
432
|
+
accessToken: "ops-token",
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
} as CoreConfig;
|
|
438
|
+
|
|
439
|
+
expect(resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv }).accountId).toBe("ops");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("honors injected env when implicit Matrix account selection becomes ambiguous", () => {
|
|
443
|
+
const cfg = {
|
|
444
|
+
channels: {
|
|
445
|
+
matrix: {},
|
|
446
|
+
},
|
|
447
|
+
} as CoreConfig;
|
|
448
|
+
const env = {
|
|
449
|
+
LOBI_HOMESERVER: "https://matrix.example.org",
|
|
450
|
+
LOBI_ACCESS_TOKEN: "default-token",
|
|
451
|
+
LOBI_OPS_HOMESERVER: "https://matrix.example.org",
|
|
452
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
453
|
+
} as NodeJS.ProcessEnv;
|
|
454
|
+
|
|
455
|
+
expect(() => resolveMatrixAuthContext({ cfg, env })).toThrow(
|
|
456
|
+
/channels\.matrix\.defaultAccount.*--account <id>/i,
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("does not materialize a default env account from partial global auth fields", () => {
|
|
461
|
+
const cfg = {
|
|
462
|
+
channels: {
|
|
463
|
+
matrix: {},
|
|
464
|
+
},
|
|
465
|
+
} as CoreConfig;
|
|
466
|
+
const env = {
|
|
467
|
+
LOBI_ACCESS_TOKEN: "shared-token",
|
|
468
|
+
LOBI_OPS_HOMESERVER: "https://matrix.example.org",
|
|
469
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
470
|
+
} as NodeJS.ProcessEnv;
|
|
471
|
+
|
|
472
|
+
expect(resolveMatrixAuthContext({ cfg, env }).accountId).toBe("ops");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("does not materialize a default account from top-level homeserver plus userId alone", () => {
|
|
476
|
+
const cfg = {
|
|
477
|
+
channels: {
|
|
478
|
+
matrix: {
|
|
479
|
+
homeserver: "https://matrix.example.org",
|
|
480
|
+
userId: "@default:example.org",
|
|
481
|
+
accounts: {
|
|
482
|
+
ops: {
|
|
483
|
+
homeserver: "https://matrix.example.org",
|
|
484
|
+
accessToken: "ops-token",
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
} as CoreConfig;
|
|
490
|
+
|
|
491
|
+
expect(resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv }).accountId).toBe("ops");
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it("does not materialize a default env account from global homeserver plus userId alone", () => {
|
|
495
|
+
const cfg = {
|
|
496
|
+
channels: {
|
|
497
|
+
matrix: {},
|
|
498
|
+
},
|
|
499
|
+
} as CoreConfig;
|
|
500
|
+
const env = {
|
|
501
|
+
LOBI_HOMESERVER: "https://matrix.example.org",
|
|
502
|
+
LOBI_USER_ID: "@default:example.org",
|
|
503
|
+
LOBI_OPS_HOMESERVER: "https://matrix.example.org",
|
|
504
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
505
|
+
} as NodeJS.ProcessEnv;
|
|
506
|
+
|
|
507
|
+
expect(resolveMatrixAuthContext({ cfg, env }).accountId).toBe("ops");
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("keeps implicit selection for env-backed accounts that can use cached credentials", () => {
|
|
511
|
+
const cfg = {
|
|
512
|
+
channels: {
|
|
513
|
+
matrix: {
|
|
514
|
+
homeserver: "https://matrix.example.org",
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
} as CoreConfig;
|
|
518
|
+
const env = {
|
|
519
|
+
MATRIX_OPS_USER_ID: "@ops:example.org",
|
|
520
|
+
} as NodeJS.ProcessEnv;
|
|
521
|
+
|
|
522
|
+
expect(resolveMatrixAuthContext({ cfg, env }).accountId).toBe("ops");
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("rejects explicit non-default account ids that are neither configured nor scoped in env", () => {
|
|
526
|
+
const cfg = {
|
|
527
|
+
channels: {
|
|
528
|
+
matrix: {
|
|
529
|
+
homeserver: "https://legacy.example.org",
|
|
530
|
+
accessToken: "legacy-token",
|
|
531
|
+
accounts: {
|
|
532
|
+
ops: {
|
|
533
|
+
homeserver: "https://ops.example.org",
|
|
534
|
+
accessToken: "ops-token",
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
} as CoreConfig;
|
|
540
|
+
|
|
541
|
+
expect(() =>
|
|
542
|
+
resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv, accountId: "typo" }),
|
|
543
|
+
).toThrow(/Matrix account "typo" is not configured/i);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("allows explicit non-default account ids backed only by scoped env vars", () => {
|
|
547
|
+
const cfg = {
|
|
548
|
+
channels: {
|
|
549
|
+
matrix: {
|
|
550
|
+
homeserver: "https://legacy.example.org",
|
|
551
|
+
accessToken: "legacy-token",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
} as CoreConfig;
|
|
555
|
+
const env = {
|
|
556
|
+
LOBI_OPS_HOMESERVER: "https://ops.example.org",
|
|
557
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
558
|
+
} as NodeJS.ProcessEnv;
|
|
559
|
+
|
|
560
|
+
expect(resolveMatrixAuthContext({ cfg, env, accountId: "ops" }).accountId).toBe("ops");
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("does not inherit the base deviceId for non-default accounts", () => {
|
|
564
|
+
const cfg = {
|
|
565
|
+
channels: {
|
|
566
|
+
matrix: {
|
|
567
|
+
homeserver: "https://base.example.org",
|
|
568
|
+
accessToken: "base-token",
|
|
569
|
+
deviceId: "BASEDEVICE",
|
|
570
|
+
accounts: {
|
|
571
|
+
ops: {
|
|
572
|
+
homeserver: "https://ops.example.org",
|
|
573
|
+
accessToken: "ops-token",
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
} as CoreConfig;
|
|
579
|
+
|
|
580
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", {} as NodeJS.ProcessEnv);
|
|
581
|
+
expect(resolved.deviceId).toBeUndefined();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it("does not inherit the base userId for non-default accounts", () => {
|
|
585
|
+
const cfg = {
|
|
586
|
+
channels: {
|
|
587
|
+
matrix: {
|
|
588
|
+
homeserver: "https://base.example.org",
|
|
589
|
+
userId: "@base:example.org",
|
|
590
|
+
accessToken: "base-token",
|
|
591
|
+
accounts: {
|
|
592
|
+
ops: {
|
|
593
|
+
homeserver: "https://ops.example.org",
|
|
594
|
+
accessToken: "ops-token",
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
} as CoreConfig;
|
|
600
|
+
|
|
601
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", {} as NodeJS.ProcessEnv);
|
|
602
|
+
expect(resolved.userId).toBe("");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it("does not inherit base or global auth secrets for non-default accounts", () => {
|
|
606
|
+
const cfg = {
|
|
607
|
+
channels: {
|
|
608
|
+
matrix: {
|
|
609
|
+
homeserver: "https://base.example.org",
|
|
610
|
+
accessToken: "base-token",
|
|
611
|
+
password: "base-pass", // pragma: allowlist secret
|
|
612
|
+
deviceId: "BASEDEVICE",
|
|
613
|
+
accounts: {
|
|
614
|
+
ops: {
|
|
615
|
+
homeserver: "https://ops.example.org",
|
|
616
|
+
userId: "@ops:example.org",
|
|
617
|
+
password: "ops-pass", // pragma: allowlist secret
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
} as CoreConfig;
|
|
623
|
+
const env = {
|
|
624
|
+
LOBI_ACCESS_TOKEN: "global-token",
|
|
625
|
+
LOBI_PASSWORD: "global-pass",
|
|
626
|
+
LOBI_DEVICE_ID: "GLOBALDEVICE",
|
|
627
|
+
} as NodeJS.ProcessEnv;
|
|
628
|
+
|
|
629
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
|
630
|
+
expect(resolved.accessToken).toBeUndefined();
|
|
631
|
+
expect(resolved.password).toBe("ops-pass");
|
|
632
|
+
expect(resolved.deviceId).toBeUndefined();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("does not inherit a base password for non-default accounts", () => {
|
|
636
|
+
const cfg = {
|
|
637
|
+
channels: {
|
|
638
|
+
matrix: {
|
|
639
|
+
homeserver: "https://base.example.org",
|
|
640
|
+
password: "base-pass", // pragma: allowlist secret
|
|
641
|
+
accounts: {
|
|
642
|
+
ops: {
|
|
643
|
+
homeserver: "https://ops.example.org",
|
|
644
|
+
userId: "@ops:example.org",
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
} as CoreConfig;
|
|
650
|
+
const env = {
|
|
651
|
+
LOBI_PASSWORD: "global-pass",
|
|
652
|
+
} as NodeJS.ProcessEnv;
|
|
653
|
+
|
|
654
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", env);
|
|
655
|
+
expect(resolved.password).toBeUndefined();
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it("rejects insecure public http Matrix homeservers", () => {
|
|
659
|
+
expect(() => validateMatrixHomeserverUrl("http://matrix.example.org")).toThrow(
|
|
660
|
+
"Matrix homeserver must use https:// unless it targets a private or loopback host",
|
|
661
|
+
);
|
|
662
|
+
expect(validateMatrixHomeserverUrl("http://127.0.0.1:8008")).toBe("http://127.0.0.1:8008");
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("accepts internal http homeservers only when private-network access is enabled", () => {
|
|
666
|
+
expect(() => validateMatrixHomeserverUrl("http://matrix-synapse:8008")).toThrow(
|
|
667
|
+
"Matrix homeserver must use https:// unless it targets a private or loopback host",
|
|
668
|
+
);
|
|
669
|
+
expect(
|
|
670
|
+
validateMatrixHomeserverUrl("http://matrix-synapse:8008", {
|
|
671
|
+
allowPrivateNetwork: true,
|
|
672
|
+
}),
|
|
673
|
+
).toBe("http://matrix-synapse:8008");
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it("resolves an explicit proxy dispatcher from top-level Matrix config", () => {
|
|
677
|
+
const cfg = {
|
|
678
|
+
channels: {
|
|
679
|
+
matrix: {
|
|
680
|
+
homeserver: "https://matrix.example.org",
|
|
681
|
+
accessToken: "tok-123",
|
|
682
|
+
proxy: "http://127.0.0.1:7890",
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
} as CoreConfig;
|
|
686
|
+
|
|
687
|
+
const resolved = resolveDefaultMatrixAuthContext(cfg, {} as NodeJS.ProcessEnv).resolved;
|
|
688
|
+
|
|
689
|
+
expect(resolved.dispatcherPolicy).toEqual({
|
|
690
|
+
mode: "explicit-proxy",
|
|
691
|
+
proxyUrl: "http://127.0.0.1:7890",
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("prefers account proxy overrides over top-level Matrix proxy config", () => {
|
|
696
|
+
const cfg = {
|
|
697
|
+
channels: {
|
|
698
|
+
matrix: {
|
|
699
|
+
homeserver: "https://matrix.example.org",
|
|
700
|
+
accessToken: "base-token",
|
|
701
|
+
proxy: "http://127.0.0.1:7890",
|
|
702
|
+
accounts: {
|
|
703
|
+
ops: {
|
|
704
|
+
homeserver: "https://matrix.ops.example.org",
|
|
705
|
+
accessToken: "ops-token",
|
|
706
|
+
proxy: "http://127.0.0.1:7891",
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
} as CoreConfig;
|
|
712
|
+
|
|
713
|
+
const resolved = resolveMatrixConfigForAccount(cfg, "ops", {} as NodeJS.ProcessEnv);
|
|
714
|
+
|
|
715
|
+
expect(resolved.dispatcherPolicy).toEqual({
|
|
716
|
+
mode: "explicit-proxy",
|
|
717
|
+
proxyUrl: "http://127.0.0.1:7891",
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it("rejects public http homeservers even when private-network access is enabled", async () => {
|
|
722
|
+
await expect(
|
|
723
|
+
resolveValidatedMatrixHomeserverUrl("http://matrix.example.org:8008", {
|
|
724
|
+
allowPrivateNetwork: true,
|
|
725
|
+
lookupFn: createLookupFn([{ address: "93.184.216.34", family: 4 }]),
|
|
726
|
+
}),
|
|
727
|
+
).rejects.toThrow(
|
|
728
|
+
"Matrix homeserver must use https:// unless it targets a private or loopback host",
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("accepts internal http hostnames when the private-network opt-in is explicit", async () => {
|
|
733
|
+
await expect(
|
|
734
|
+
resolveValidatedMatrixHomeserverUrl("http://localhost.localdomain:8008", {
|
|
735
|
+
dangerouslyAllowPrivateNetwork: true,
|
|
736
|
+
lookupFn: createLookupFn([{ address: "127.0.0.1", family: 4 }]),
|
|
737
|
+
}),
|
|
738
|
+
).resolves.toBe("http://localhost.localdomain:8008");
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
describe("resolveMatrixAuth", () => {
|
|
743
|
+
beforeAll(async () => {
|
|
744
|
+
credentialsReadModule = await import("./credentials-read.js");
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
beforeEach(() => {
|
|
748
|
+
const readModule = requireCredentialsReadModule();
|
|
749
|
+
vi.mocked(readModule.loadMatrixCredentials).mockReset();
|
|
750
|
+
vi.mocked(readModule.loadMatrixCredentials).mockReturnValue(null);
|
|
751
|
+
vi.mocked(readModule.credentialsMatchConfig).mockReset();
|
|
752
|
+
vi.mocked(readModule.credentialsMatchConfig).mockReturnValue(false);
|
|
753
|
+
saveMatrixCredentialsMock.mockReset();
|
|
754
|
+
saveBackfilledMatrixDeviceIdMock.mockReset().mockResolvedValue("saved");
|
|
755
|
+
touchMatrixCredentialsMock.mockReset();
|
|
756
|
+
repairCurrentTokenStorageMetaDeviceIdMock.mockReset().mockReturnValue(true);
|
|
757
|
+
ensureMatrixSdkLoggingConfiguredMock.mockReset();
|
|
758
|
+
matrixDoRequestMock.mockReset();
|
|
759
|
+
setMatrixAuthClientDepsForTest({
|
|
760
|
+
MatrixClient: MockMatrixClient as unknown as typeof import("./sdk.js").MatrixClient,
|
|
761
|
+
ensureMatrixSdkLoggingConfigured: ensureMatrixSdkLoggingConfiguredMock,
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
afterEach(() => {
|
|
766
|
+
vi.restoreAllMocks();
|
|
767
|
+
vi.unstubAllGlobals();
|
|
768
|
+
setMatrixAuthClientDepsForTest(undefined);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("uses the hardened client request path for password login and persists deviceId", async () => {
|
|
772
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
773
|
+
access_token: "tok-123",
|
|
774
|
+
user_id: "@bot:example.org",
|
|
775
|
+
device_id: "DEVICE123",
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
const cfg = {
|
|
779
|
+
channels: {
|
|
780
|
+
matrix: {
|
|
781
|
+
homeserver: "https://matrix.example.org",
|
|
782
|
+
userId: "@bot:example.org",
|
|
783
|
+
password: "secret", // pragma: allowlist secret
|
|
784
|
+
encryption: true,
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
} as CoreConfig;
|
|
788
|
+
|
|
789
|
+
const auth = await resolveMatrixAuth({
|
|
790
|
+
cfg,
|
|
791
|
+
env: {} as NodeJS.ProcessEnv,
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith(
|
|
795
|
+
"POST",
|
|
796
|
+
"/_lobi/client/v3/login",
|
|
797
|
+
undefined,
|
|
798
|
+
expect.objectContaining({
|
|
799
|
+
type: "m.login.password",
|
|
800
|
+
}),
|
|
801
|
+
);
|
|
802
|
+
expect(auth).toMatchObject({
|
|
803
|
+
accountId: "default",
|
|
804
|
+
homeserver: "https://matrix.example.org",
|
|
805
|
+
userId: "@bot:example.org",
|
|
806
|
+
accessToken: "tok-123",
|
|
807
|
+
deviceId: "DEVICE123",
|
|
808
|
+
encryption: true,
|
|
809
|
+
});
|
|
810
|
+
expect(saveMatrixCredentialsMock).toHaveBeenCalledWith(
|
|
811
|
+
expect.objectContaining({
|
|
812
|
+
homeserver: "https://matrix.example.org",
|
|
813
|
+
userId: "@bot:example.org",
|
|
814
|
+
accessToken: "tok-123",
|
|
815
|
+
deviceId: "DEVICE123",
|
|
816
|
+
}),
|
|
817
|
+
expect.any(Object),
|
|
818
|
+
"default",
|
|
819
|
+
);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it("surfaces password login errors when account credentials are invalid", async () => {
|
|
823
|
+
matrixDoRequestMock.mockRejectedValueOnce(new Error("Invalid username or password"));
|
|
824
|
+
|
|
825
|
+
const cfg = {
|
|
826
|
+
channels: {
|
|
827
|
+
matrix: {
|
|
828
|
+
homeserver: "https://matrix.example.org",
|
|
829
|
+
userId: "@bot:example.org",
|
|
830
|
+
password: "secret", // pragma: allowlist secret
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
} as CoreConfig;
|
|
834
|
+
|
|
835
|
+
await expect(
|
|
836
|
+
resolveMatrixAuth({
|
|
837
|
+
cfg,
|
|
838
|
+
env: {} as NodeJS.ProcessEnv,
|
|
839
|
+
}),
|
|
840
|
+
).rejects.toThrow("Invalid username or password");
|
|
841
|
+
|
|
842
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith(
|
|
843
|
+
"POST",
|
|
844
|
+
"/_lobi/client/v3/login",
|
|
845
|
+
undefined,
|
|
846
|
+
expect.objectContaining({
|
|
847
|
+
type: "m.login.password",
|
|
848
|
+
}),
|
|
849
|
+
);
|
|
850
|
+
expect(saveMatrixCredentialsMock).not.toHaveBeenCalled();
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it("uses cached matching credentials when access token is not configured", async () => {
|
|
854
|
+
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
|
|
855
|
+
homeserver: "https://matrix.example.org",
|
|
856
|
+
userId: "@bot:example.org",
|
|
857
|
+
accessToken: "cached-token",
|
|
858
|
+
deviceId: "CACHEDDEVICE",
|
|
859
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
860
|
+
});
|
|
861
|
+
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
|
|
862
|
+
|
|
863
|
+
const cfg = {
|
|
864
|
+
channels: {
|
|
865
|
+
matrix: {
|
|
866
|
+
homeserver: "https://matrix.example.org",
|
|
867
|
+
userId: "@bot:example.org",
|
|
868
|
+
password: "secret", // pragma: allowlist secret
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
} as CoreConfig;
|
|
872
|
+
|
|
873
|
+
const auth = await resolveMatrixAuth({
|
|
874
|
+
cfg,
|
|
875
|
+
env: {} as NodeJS.ProcessEnv,
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
expect(auth).toMatchObject({
|
|
879
|
+
accountId: "default",
|
|
880
|
+
homeserver: "https://matrix.example.org",
|
|
881
|
+
userId: "@bot:example.org",
|
|
882
|
+
accessToken: "cached-token",
|
|
883
|
+
deviceId: "CACHEDDEVICE",
|
|
884
|
+
});
|
|
885
|
+
expect(saveMatrixCredentialsMock).not.toHaveBeenCalled();
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it("uses cached matching credentials for env-backed named accounts without fresh auth", async () => {
|
|
889
|
+
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
|
|
890
|
+
homeserver: "https://matrix.example.org",
|
|
891
|
+
userId: "@ops:example.org",
|
|
892
|
+
accessToken: "cached-token",
|
|
893
|
+
deviceId: "CACHEDDEVICE",
|
|
894
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
895
|
+
});
|
|
896
|
+
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
|
|
897
|
+
|
|
898
|
+
const cfg = {
|
|
899
|
+
channels: {
|
|
900
|
+
matrix: {
|
|
901
|
+
homeserver: "https://matrix.example.org",
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
} as CoreConfig;
|
|
905
|
+
const env = {
|
|
906
|
+
MATRIX_OPS_USER_ID: "@ops:example.org",
|
|
907
|
+
} as NodeJS.ProcessEnv;
|
|
908
|
+
|
|
909
|
+
const auth = await resolveMatrixAuth({
|
|
910
|
+
cfg,
|
|
911
|
+
env,
|
|
912
|
+
accountId: "ops",
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
expect(auth).toMatchObject({
|
|
916
|
+
accountId: "ops",
|
|
917
|
+
homeserver: "https://matrix.example.org",
|
|
918
|
+
userId: "@ops:example.org",
|
|
919
|
+
accessToken: "cached-token",
|
|
920
|
+
deviceId: "CACHEDDEVICE",
|
|
921
|
+
});
|
|
922
|
+
expect(saveMatrixCredentialsMock).not.toHaveBeenCalled();
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it("rejects embedded credentials in Matrix homeserver URLs", async () => {
|
|
926
|
+
const cfg = {
|
|
927
|
+
channels: {
|
|
928
|
+
matrix: {
|
|
929
|
+
homeserver: "https://user:pass@matrix.example.org",
|
|
930
|
+
accessToken: "tok-123",
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
} as CoreConfig;
|
|
934
|
+
|
|
935
|
+
await expect(resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv })).rejects.toThrow(
|
|
936
|
+
"Matrix homeserver URL must not include embedded credentials",
|
|
937
|
+
);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it("falls back to config deviceId when cached credentials are missing it", async () => {
|
|
941
|
+
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
|
|
942
|
+
homeserver: "https://matrix.example.org",
|
|
943
|
+
userId: "@bot:example.org",
|
|
944
|
+
accessToken: "tok-123",
|
|
945
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
946
|
+
});
|
|
947
|
+
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
|
|
948
|
+
|
|
949
|
+
const cfg = {
|
|
950
|
+
channels: {
|
|
951
|
+
matrix: {
|
|
952
|
+
homeserver: "https://matrix.example.org",
|
|
953
|
+
userId: "@bot:example.org",
|
|
954
|
+
accessToken: "tok-123",
|
|
955
|
+
deviceId: "DEVICE123",
|
|
956
|
+
encryption: true,
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
} as CoreConfig;
|
|
960
|
+
|
|
961
|
+
const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv });
|
|
962
|
+
|
|
963
|
+
expect(auth.deviceId).toBe("DEVICE123");
|
|
964
|
+
expect(auth.accountId).toBe("default");
|
|
965
|
+
expect(saveMatrixCredentialsMock).toHaveBeenCalledWith(
|
|
966
|
+
expect.objectContaining({
|
|
967
|
+
homeserver: "https://matrix.example.org",
|
|
968
|
+
userId: "@bot:example.org",
|
|
969
|
+
accessToken: "tok-123",
|
|
970
|
+
deviceId: "DEVICE123",
|
|
971
|
+
}),
|
|
972
|
+
expect.any(Object),
|
|
973
|
+
"default",
|
|
974
|
+
);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
it("carries the private-network opt-in through Matrix auth resolution", async () => {
|
|
978
|
+
const cfg = {
|
|
979
|
+
channels: {
|
|
980
|
+
matrix: {
|
|
981
|
+
homeserver: "http://127.0.0.1:8008",
|
|
982
|
+
allowPrivateNetwork: true,
|
|
983
|
+
userId: "@bot:example.org",
|
|
984
|
+
accessToken: "tok-123",
|
|
985
|
+
deviceId: "DEVICE123",
|
|
986
|
+
},
|
|
987
|
+
},
|
|
988
|
+
} as CoreConfig;
|
|
989
|
+
|
|
990
|
+
const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv });
|
|
991
|
+
|
|
992
|
+
expect(auth).toMatchObject({
|
|
993
|
+
homeserver: "http://127.0.0.1:8008",
|
|
994
|
+
allowPrivateNetwork: true,
|
|
995
|
+
ssrfPolicy: { allowPrivateNetwork: true },
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
it("resolves token-only non-default account userId from whoami instead of inheriting the base user", async () => {
|
|
1000
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1001
|
+
user_id: "@ops:example.org",
|
|
1002
|
+
device_id: "OPSDEVICE",
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const cfg = {
|
|
1006
|
+
channels: {
|
|
1007
|
+
matrix: {
|
|
1008
|
+
userId: "@base:example.org",
|
|
1009
|
+
homeserver: "https://matrix.example.org",
|
|
1010
|
+
accounts: {
|
|
1011
|
+
ops: {
|
|
1012
|
+
homeserver: "https://matrix.example.org",
|
|
1013
|
+
accessToken: "ops-token",
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
} as CoreConfig;
|
|
1019
|
+
|
|
1020
|
+
const auth = await resolveMatrixAuth({
|
|
1021
|
+
cfg,
|
|
1022
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1023
|
+
accountId: "ops",
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith("GET", "/_lobi/client/v3/account/whoami");
|
|
1027
|
+
expect(auth.userId).toBe("@ops:example.org");
|
|
1028
|
+
expect(auth.deviceId).toBe("OPSDEVICE");
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
it("uses named-account password auth instead of inheriting the base access token", async () => {
|
|
1032
|
+
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue(null);
|
|
1033
|
+
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(false);
|
|
1034
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1035
|
+
access_token: "ops-token",
|
|
1036
|
+
user_id: "@ops:example.org",
|
|
1037
|
+
device_id: "OPSDEVICE",
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
const cfg = {
|
|
1041
|
+
channels: {
|
|
1042
|
+
matrix: {
|
|
1043
|
+
homeserver: "https://matrix.example.org",
|
|
1044
|
+
accessToken: "legacy-token",
|
|
1045
|
+
accounts: {
|
|
1046
|
+
ops: {
|
|
1047
|
+
homeserver: "https://matrix.example.org",
|
|
1048
|
+
userId: "@ops:example.org",
|
|
1049
|
+
password: "ops-pass", // pragma: allowlist secret
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
} as CoreConfig;
|
|
1055
|
+
|
|
1056
|
+
const auth = await resolveMatrixAuth({
|
|
1057
|
+
cfg,
|
|
1058
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1059
|
+
accountId: "ops",
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith(
|
|
1063
|
+
"POST",
|
|
1064
|
+
"/_lobi/client/v3/login",
|
|
1065
|
+
undefined,
|
|
1066
|
+
expect.objectContaining({
|
|
1067
|
+
type: "m.login.password",
|
|
1068
|
+
identifier: { type: "m.id.user", user: "@ops:example.org" },
|
|
1069
|
+
password: "ops-pass",
|
|
1070
|
+
}),
|
|
1071
|
+
);
|
|
1072
|
+
expect(auth).toMatchObject({
|
|
1073
|
+
accountId: "ops",
|
|
1074
|
+
homeserver: "https://matrix.example.org",
|
|
1075
|
+
userId: "@ops:example.org",
|
|
1076
|
+
accessToken: "ops-token",
|
|
1077
|
+
deviceId: "OPSDEVICE",
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it("resolves missing whoami identity fields for token auth", async () => {
|
|
1082
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1083
|
+
user_id: "@bot:example.org",
|
|
1084
|
+
device_id: "DEVICE123",
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
const cfg = {
|
|
1088
|
+
channels: {
|
|
1089
|
+
matrix: {
|
|
1090
|
+
homeserver: "https://matrix.example.org",
|
|
1091
|
+
accessToken: "tok-123",
|
|
1092
|
+
encryption: true,
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
} as CoreConfig;
|
|
1096
|
+
|
|
1097
|
+
const auth = await resolveMatrixAuth({
|
|
1098
|
+
cfg,
|
|
1099
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith("GET", "/_lobi/client/v3/account/whoami");
|
|
1103
|
+
expect(auth).toMatchObject({
|
|
1104
|
+
accountId: "default",
|
|
1105
|
+
homeserver: "https://matrix.example.org",
|
|
1106
|
+
userId: "@bot:example.org",
|
|
1107
|
+
accessToken: "tok-123",
|
|
1108
|
+
deviceId: "DEVICE123",
|
|
1109
|
+
encryption: true,
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
it("retries token whoami when startup auth hits a transient network error", async () => {
|
|
1114
|
+
matrixDoRequestMock
|
|
1115
|
+
.mockRejectedValueOnce(
|
|
1116
|
+
Object.assign(new TypeError("fetch failed"), {
|
|
1117
|
+
cause: Object.assign(new Error("read ECONNRESET"), {
|
|
1118
|
+
code: "ECONNRESET",
|
|
1119
|
+
}),
|
|
1120
|
+
}),
|
|
1121
|
+
)
|
|
1122
|
+
.mockResolvedValue({
|
|
1123
|
+
user_id: "@bot:example.org",
|
|
1124
|
+
device_id: "DEVICE123",
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
const cfg = {
|
|
1128
|
+
channels: {
|
|
1129
|
+
matrix: {
|
|
1130
|
+
homeserver: "https://matrix.example.org",
|
|
1131
|
+
accessToken: "tok-123",
|
|
1132
|
+
},
|
|
1133
|
+
},
|
|
1134
|
+
} as CoreConfig;
|
|
1135
|
+
|
|
1136
|
+
const auth = await resolveMatrixAuth({
|
|
1137
|
+
cfg,
|
|
1138
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
expect(matrixDoRequestMock).toHaveBeenCalledTimes(2);
|
|
1142
|
+
expect(auth).toMatchObject({
|
|
1143
|
+
userId: "@bot:example.org",
|
|
1144
|
+
deviceId: "DEVICE123",
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
it("does not call whoami when token auth already has a userId and only deviceId is missing", async () => {
|
|
1149
|
+
matrixDoRequestMock.mockRejectedValue(new Error("whoami should not be called"));
|
|
1150
|
+
|
|
1151
|
+
const cfg = {
|
|
1152
|
+
channels: {
|
|
1153
|
+
matrix: {
|
|
1154
|
+
homeserver: "https://matrix.example.org",
|
|
1155
|
+
userId: "@bot:example.org",
|
|
1156
|
+
accessToken: "tok-123",
|
|
1157
|
+
encryption: true,
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
} as CoreConfig;
|
|
1161
|
+
|
|
1162
|
+
const auth = await resolveMatrixAuth({
|
|
1163
|
+
cfg,
|
|
1164
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
expect(matrixDoRequestMock).not.toHaveBeenCalled();
|
|
1168
|
+
expect(auth).toMatchObject({
|
|
1169
|
+
accountId: "default",
|
|
1170
|
+
homeserver: "https://matrix.example.org",
|
|
1171
|
+
userId: "@bot:example.org",
|
|
1172
|
+
accessToken: "tok-123",
|
|
1173
|
+
deviceId: undefined,
|
|
1174
|
+
encryption: true,
|
|
1175
|
+
});
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
it("retries password login when startup auth hits a transient network error", async () => {
|
|
1179
|
+
matrixDoRequestMock
|
|
1180
|
+
.mockRejectedValueOnce(
|
|
1181
|
+
Object.assign(new TypeError("fetch failed"), {
|
|
1182
|
+
cause: Object.assign(new Error("socket hang up"), {
|
|
1183
|
+
code: "ECONNRESET",
|
|
1184
|
+
}),
|
|
1185
|
+
}),
|
|
1186
|
+
)
|
|
1187
|
+
.mockResolvedValue({
|
|
1188
|
+
access_token: "tok-123",
|
|
1189
|
+
user_id: "@bot:example.org",
|
|
1190
|
+
device_id: "DEVICE123",
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
const cfg = {
|
|
1194
|
+
channels: {
|
|
1195
|
+
matrix: {
|
|
1196
|
+
homeserver: "https://matrix.example.org",
|
|
1197
|
+
userId: "@bot:example.org",
|
|
1198
|
+
password: "secret", // pragma: allowlist secret
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
} as CoreConfig;
|
|
1202
|
+
|
|
1203
|
+
const auth = await resolveMatrixAuth({
|
|
1204
|
+
cfg,
|
|
1205
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
expect(matrixDoRequestMock).toHaveBeenCalledTimes(2);
|
|
1209
|
+
expect(auth).toMatchObject({
|
|
1210
|
+
accessToken: "tok-123",
|
|
1211
|
+
deviceId: "DEVICE123",
|
|
1212
|
+
});
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
it("best-effort backfills a missing deviceId after startup", async () => {
|
|
1216
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1217
|
+
user_id: "@bot:example.org",
|
|
1218
|
+
device_id: "DEVICE123",
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
const deviceId = await backfillMatrixAuthDeviceIdAfterStartup({
|
|
1222
|
+
auth: {
|
|
1223
|
+
accountId: "default",
|
|
1224
|
+
homeserver: "https://matrix.example.org",
|
|
1225
|
+
userId: "@bot:example.org",
|
|
1226
|
+
accessToken: "tok-123",
|
|
1227
|
+
},
|
|
1228
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith("GET", "/_lobi/client/v3/account/whoami");
|
|
1232
|
+
expect(saveBackfilledMatrixDeviceIdMock).toHaveBeenCalledWith(
|
|
1233
|
+
{
|
|
1234
|
+
homeserver: "https://matrix.example.org",
|
|
1235
|
+
userId: "@bot:example.org",
|
|
1236
|
+
accessToken: "tok-123",
|
|
1237
|
+
deviceId: "DEVICE123",
|
|
1238
|
+
},
|
|
1239
|
+
expect.any(Object),
|
|
1240
|
+
"default",
|
|
1241
|
+
);
|
|
1242
|
+
expect(repairCurrentTokenStorageMetaDeviceIdMock).toHaveBeenCalledWith({
|
|
1243
|
+
homeserver: "https://matrix.example.org",
|
|
1244
|
+
userId: "@bot:example.org",
|
|
1245
|
+
accessToken: "tok-123",
|
|
1246
|
+
accountId: "default",
|
|
1247
|
+
deviceId: "DEVICE123",
|
|
1248
|
+
env: expect.any(Object),
|
|
1249
|
+
});
|
|
1250
|
+
expect(repairCurrentTokenStorageMetaDeviceIdMock.mock.invocationCallOrder[0]).toBeLessThan(
|
|
1251
|
+
saveBackfilledMatrixDeviceIdMock.mock.invocationCallOrder[0],
|
|
1252
|
+
);
|
|
1253
|
+
expect(deviceId).toBe("DEVICE123");
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
it("skips deviceId backfill when auth already includes it", async () => {
|
|
1257
|
+
const deviceId = await backfillMatrixAuthDeviceIdAfterStartup({
|
|
1258
|
+
auth: {
|
|
1259
|
+
accountId: "default",
|
|
1260
|
+
homeserver: "https://matrix.example.org",
|
|
1261
|
+
userId: "@bot:example.org",
|
|
1262
|
+
accessToken: "tok-123",
|
|
1263
|
+
deviceId: "DEVICE123",
|
|
1264
|
+
},
|
|
1265
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
expect(matrixDoRequestMock).not.toHaveBeenCalled();
|
|
1269
|
+
expect(saveMatrixCredentialsMock).not.toHaveBeenCalled();
|
|
1270
|
+
expect(saveBackfilledMatrixDeviceIdMock).not.toHaveBeenCalled();
|
|
1271
|
+
expect(repairCurrentTokenStorageMetaDeviceIdMock).not.toHaveBeenCalled();
|
|
1272
|
+
expect(deviceId).toBe("DEVICE123");
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
it("fails before saving repaired credentials when storage metadata repair fails", async () => {
|
|
1276
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1277
|
+
user_id: "@bot:example.org",
|
|
1278
|
+
device_id: "DEVICE123",
|
|
1279
|
+
});
|
|
1280
|
+
repairCurrentTokenStorageMetaDeviceIdMock.mockReturnValue(false);
|
|
1281
|
+
|
|
1282
|
+
await expect(
|
|
1283
|
+
backfillMatrixAuthDeviceIdAfterStartup({
|
|
1284
|
+
auth: {
|
|
1285
|
+
accountId: "default",
|
|
1286
|
+
homeserver: "https://matrix.example.org",
|
|
1287
|
+
userId: "@bot:example.org",
|
|
1288
|
+
accessToken: "tok-123",
|
|
1289
|
+
},
|
|
1290
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1291
|
+
}),
|
|
1292
|
+
).rejects.toThrow("Matrix deviceId backfill failed to repair current-token storage metadata");
|
|
1293
|
+
expect(saveBackfilledMatrixDeviceIdMock).not.toHaveBeenCalled();
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
it("skips stale deviceId backfill writes after newer credentials take over", async () => {
|
|
1297
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1298
|
+
user_id: "@bot:example.org",
|
|
1299
|
+
device_id: "DEVICE123",
|
|
1300
|
+
});
|
|
1301
|
+
vi.mocked(requireCredentialsReadModule().loadMatrixCredentials).mockReturnValue({
|
|
1302
|
+
homeserver: "https://matrix.example.org",
|
|
1303
|
+
userId: "@bot:example.org",
|
|
1304
|
+
accessToken: "tok-new",
|
|
1305
|
+
deviceId: "DEVICE999",
|
|
1306
|
+
createdAt: "2026-03-01T00:00:00.000Z",
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
const deviceId = await backfillMatrixAuthDeviceIdAfterStartup({
|
|
1310
|
+
auth: {
|
|
1311
|
+
accountId: "default",
|
|
1312
|
+
homeserver: "https://matrix.example.org",
|
|
1313
|
+
userId: "@bot:example.org",
|
|
1314
|
+
accessToken: "tok-old",
|
|
1315
|
+
},
|
|
1316
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
expect(deviceId).toBeUndefined();
|
|
1320
|
+
expect(repairCurrentTokenStorageMetaDeviceIdMock).not.toHaveBeenCalled();
|
|
1321
|
+
expect(saveBackfilledMatrixDeviceIdMock).not.toHaveBeenCalled();
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
it("skips persistence when startup backfill is aborted before whoami resolves", async () => {
|
|
1325
|
+
let resolveWhoami: ((value: { user_id: string; device_id: string }) => void) | undefined;
|
|
1326
|
+
matrixDoRequestMock.mockImplementation(
|
|
1327
|
+
() =>
|
|
1328
|
+
new Promise((resolve) => {
|
|
1329
|
+
resolveWhoami = resolve;
|
|
1330
|
+
}),
|
|
1331
|
+
);
|
|
1332
|
+
const abortController = new AbortController();
|
|
1333
|
+
const backfillPromise = backfillMatrixAuthDeviceIdAfterStartup({
|
|
1334
|
+
auth: {
|
|
1335
|
+
accountId: "default",
|
|
1336
|
+
homeserver: "https://matrix.example.org",
|
|
1337
|
+
userId: "@bot:example.org",
|
|
1338
|
+
accessToken: "tok-123",
|
|
1339
|
+
},
|
|
1340
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1341
|
+
abortSignal: abortController.signal,
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
await vi.waitFor(() => {
|
|
1345
|
+
expect(resolveWhoami).toBeTypeOf("function");
|
|
1346
|
+
});
|
|
1347
|
+
abortController.abort();
|
|
1348
|
+
resolveWhoami?.({
|
|
1349
|
+
user_id: "@bot:example.org",
|
|
1350
|
+
device_id: "DEVICE123",
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
await expect(backfillPromise).resolves.toBeUndefined();
|
|
1354
|
+
expect(repairCurrentTokenStorageMetaDeviceIdMock).not.toHaveBeenCalled();
|
|
1355
|
+
expect(saveBackfilledMatrixDeviceIdMock).not.toHaveBeenCalled();
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
it("resolves file-backed accessToken SecretRefs during Matrix auth", async () => {
|
|
1359
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-secret-ref-"));
|
|
1360
|
+
const secretPath = path.join(tempDir, "token.txt");
|
|
1361
|
+
await fs.writeFile(secretPath, "file-token\n", "utf8");
|
|
1362
|
+
await fs.chmod(secretPath, 0o600);
|
|
1363
|
+
|
|
1364
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1365
|
+
user_id: "@bot:example.org",
|
|
1366
|
+
device_id: "DEVICE123",
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
try {
|
|
1370
|
+
const cfg = {
|
|
1371
|
+
channels: {
|
|
1372
|
+
matrix: {
|
|
1373
|
+
homeserver: "https://matrix.example.org",
|
|
1374
|
+
accessToken: { source: "file", provider: "matrix-file", id: "value" },
|
|
1375
|
+
},
|
|
1376
|
+
},
|
|
1377
|
+
secrets: {
|
|
1378
|
+
providers: {
|
|
1379
|
+
"matrix-file": {
|
|
1380
|
+
source: "file",
|
|
1381
|
+
path: secretPath,
|
|
1382
|
+
mode: "singleValue",
|
|
1383
|
+
},
|
|
1384
|
+
},
|
|
1385
|
+
},
|
|
1386
|
+
} as CoreConfig;
|
|
1387
|
+
|
|
1388
|
+
const auth = await resolveMatrixAuth({
|
|
1389
|
+
cfg,
|
|
1390
|
+
env: {} as NodeJS.ProcessEnv,
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith("GET", "/_lobi/client/v3/account/whoami");
|
|
1394
|
+
expect(auth).toMatchObject({
|
|
1395
|
+
accountId: "default",
|
|
1396
|
+
homeserver: "https://matrix.example.org",
|
|
1397
|
+
userId: "@bot:example.org",
|
|
1398
|
+
accessToken: "file-token",
|
|
1399
|
+
deviceId: "DEVICE123",
|
|
1400
|
+
});
|
|
1401
|
+
} finally {
|
|
1402
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
it("does not resolve inactive password SecretRefs when scoped token auth wins", async () => {
|
|
1407
|
+
matrixDoRequestMock.mockResolvedValue({
|
|
1408
|
+
user_id: "@ops:example.org",
|
|
1409
|
+
device_id: "OPSDEVICE",
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
const cfg = {
|
|
1413
|
+
channels: {
|
|
1414
|
+
matrix: {
|
|
1415
|
+
accounts: {
|
|
1416
|
+
ops: {
|
|
1417
|
+
homeserver: "https://matrix.example.org",
|
|
1418
|
+
password: { source: "env", provider: "default", id: "MATRIX_OPS_PASSWORD" },
|
|
1419
|
+
},
|
|
1420
|
+
},
|
|
1421
|
+
},
|
|
1422
|
+
},
|
|
1423
|
+
secrets: {
|
|
1424
|
+
defaults: {
|
|
1425
|
+
env: "default",
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
} as CoreConfig;
|
|
1429
|
+
|
|
1430
|
+
installMatrixTestRuntime({ cfg });
|
|
1431
|
+
|
|
1432
|
+
const auth = await resolveMatrixAuth({
|
|
1433
|
+
cfg,
|
|
1434
|
+
env: {
|
|
1435
|
+
LOBI_OPS_ACCESS_TOKEN: "ops-token",
|
|
1436
|
+
} as NodeJS.ProcessEnv,
|
|
1437
|
+
accountId: "ops",
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
expect(matrixDoRequestMock).toHaveBeenCalledWith("GET", "/_lobi/client/v3/account/whoami");
|
|
1441
|
+
expect(auth).toMatchObject({
|
|
1442
|
+
accountId: "ops",
|
|
1443
|
+
homeserver: "https://matrix.example.org",
|
|
1444
|
+
userId: "@ops:example.org",
|
|
1445
|
+
accessToken: "ops-token",
|
|
1446
|
+
deviceId: "OPSDEVICE",
|
|
1447
|
+
password: undefined,
|
|
1448
|
+
});
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
it("uses config deviceId with cached credentials when token is loaded from cache", async () => {
|
|
1452
|
+
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
|
|
1453
|
+
homeserver: "https://matrix.example.org",
|
|
1454
|
+
userId: "@bot:example.org",
|
|
1455
|
+
accessToken: "tok-123",
|
|
1456
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
1457
|
+
});
|
|
1458
|
+
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
|
|
1459
|
+
|
|
1460
|
+
const cfg = {
|
|
1461
|
+
channels: {
|
|
1462
|
+
matrix: {
|
|
1463
|
+
homeserver: "https://matrix.example.org",
|
|
1464
|
+
userId: "@bot:example.org",
|
|
1465
|
+
deviceId: "DEVICE123",
|
|
1466
|
+
encryption: true,
|
|
1467
|
+
},
|
|
1468
|
+
},
|
|
1469
|
+
} as CoreConfig;
|
|
1470
|
+
|
|
1471
|
+
const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv });
|
|
1472
|
+
|
|
1473
|
+
expect(auth).toMatchObject({
|
|
1474
|
+
accountId: "default",
|
|
1475
|
+
homeserver: "https://matrix.example.org",
|
|
1476
|
+
userId: "@bot:example.org",
|
|
1477
|
+
accessToken: "tok-123",
|
|
1478
|
+
deviceId: "DEVICE123",
|
|
1479
|
+
encryption: true,
|
|
1480
|
+
});
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
it("falls back to the sole configured account when no global homeserver is set", async () => {
|
|
1484
|
+
const cfg = {
|
|
1485
|
+
channels: {
|
|
1486
|
+
matrix: {
|
|
1487
|
+
accounts: {
|
|
1488
|
+
ops: {
|
|
1489
|
+
homeserver: "https://ops.example.org",
|
|
1490
|
+
userId: "@ops:example.org",
|
|
1491
|
+
accessToken: "ops-token",
|
|
1492
|
+
deviceId: "OPSDEVICE",
|
|
1493
|
+
encryption: true,
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
},
|
|
1497
|
+
},
|
|
1498
|
+
} as CoreConfig;
|
|
1499
|
+
|
|
1500
|
+
const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv });
|
|
1501
|
+
|
|
1502
|
+
expect(auth).toMatchObject({
|
|
1503
|
+
accountId: "ops",
|
|
1504
|
+
homeserver: "https://ops.example.org",
|
|
1505
|
+
userId: "@ops:example.org",
|
|
1506
|
+
accessToken: "ops-token",
|
|
1507
|
+
deviceId: "OPSDEVICE",
|
|
1508
|
+
encryption: true,
|
|
1509
|
+
});
|
|
1510
|
+
expect(saveMatrixCredentialsMock).toHaveBeenCalledWith(
|
|
1511
|
+
expect.objectContaining({
|
|
1512
|
+
homeserver: "https://ops.example.org",
|
|
1513
|
+
userId: "@ops:example.org",
|
|
1514
|
+
accessToken: "ops-token",
|
|
1515
|
+
deviceId: "OPSDEVICE",
|
|
1516
|
+
}),
|
|
1517
|
+
expect.any(Object),
|
|
1518
|
+
"ops",
|
|
1519
|
+
);
|
|
1520
|
+
});
|
|
1521
|
+
});
|