diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index bd62706940df..613306d0b437 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -133,7 +133,6 @@ extensions/lmstudio/src/setup.test.ts extensions/lmstudio/src/setup.ts extensions/matrix/src/cli.test.ts extensions/matrix/src/matrix/actions/verification.test.ts -extensions/matrix/src/matrix/client/config.ts extensions/matrix/src/matrix/monitor/events.test.ts extensions/matrix/src/matrix/monitor/handler.test.ts extensions/matrix/src/matrix/sdk.test.ts diff --git a/extensions/matrix/src/matrix/client/config.test.ts b/extensions/matrix/src/matrix/client/config.test.ts index ce64631bd38f..3aa1e2a922fb 100644 --- a/extensions/matrix/src/matrix/client/config.test.ts +++ b/extensions/matrix/src/matrix/client/config.test.ts @@ -556,7 +556,7 @@ describe("Matrix auth/config live surfaces", () => { ).toThrow(/Matrix account id "!!!" is invalid/i); }); - it("rejects explicitly selected disabled accounts instead of borrowing another account", () => { + it("rejects explicitly selected disabled accounts before resolving their secrets", () => { const cfg = { channels: { matrix: { @@ -566,7 +566,11 @@ describe("Matrix auth/config live surfaces", () => { disabled: { enabled: false, homeserver: "https://disabled.example.org", - accessToken: "disabled-token", + accessToken: { + source: "env", + provider: "default", + id: "MATRIX_DISABLED_ACCESS_TOKEN", + }, }, }, }, diff --git a/extensions/matrix/src/matrix/client/config.ts b/extensions/matrix/src/matrix/client/config.ts index 458762fef9f1..1bb12cbd8afa 100644 --- a/extensions/matrix/src/matrix/client/config.ts +++ b/extensions/matrix/src/matrix/client/config.ts @@ -14,7 +14,10 @@ import { requiresExplicitMatrixDefaultAccount, resolveMatrixDefaultOrOnlyAccountId, } from "../../account-selection.js"; -import { resolveMatrixAccountStringValues } from "../../auth-precedence.js"; +import { + resolveMatrixAccountStringValues, + type MatrixResolvedStringField, +} from "../../auth-precedence.js"; import { getMatrixScopedEnvVarNames } from "../../env-vars.js"; import type { CoreConfig } from "../../types.js"; import { @@ -36,12 +39,7 @@ import { repairCurrentTokenStorageMetaDeviceId } from "./storage.js"; import type { MatrixAuth, MatrixResolvedConfig } from "./types.js"; import { resolveValidatedMatrixHomeserverUrl } from "./url-validation.js"; -type MatrixAuthClientDeps = { - MatrixClient: typeof import("../sdk.js").MatrixClient; - ensureMatrixSdkLoggingConfigured: typeof import("./logging.js").ensureMatrixSdkLoggingConfigured; -}; - -const loadDefaultMatrixAuthClientDeps = createLazyRuntimeModule(() => +const loadMatrixAuthClientDeps = createLazyRuntimeModule(() => Promise.all([import("../sdk.js"), import("./logging.js")]).then(([sdkModule, loggingModule]) => ({ MatrixClient: sdkModule.MatrixClient, ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured, @@ -50,25 +48,16 @@ const loadDefaultMatrixAuthClientDeps = createLazyRuntimeModule(() => const MATRIX_AUTH_REQUEST_RETRY_RE = /\b(fetch failed|econnreset|econnrefused|enotfound|etimedout|ehostunreach|enetunreach|eai_again|und_err_|socket hang up|network|headers timeout|body timeout|connect timeout)\b/i; -async function loadMatrixAuthClientDeps(): Promise { - return await loadDefaultMatrixAuthClientDeps(); -} - -const loadMatrixCredentialsReadDeps = createLazyRuntimeModule(() => - import("../credentials-read.js").then((credentialsReadModule) => ({ - loadMatrixCredentials: credentialsReadModule.loadMatrixCredentials, - credentialsMatchConfig: credentialsReadModule.credentialsMatchConfig, - })), +const loadMatrixCredentialsReadDeps = createLazyRuntimeModule( + () => import("../credentials-read.js"), ); const loadMatrixCredentialsWriteRuntime = createLazyRuntimeModule( () => import("../credentials-write.runtime.js"), ); -const loadMatrixSecretInputDeps = createLazyRuntimeModule(() => - import("./config-secret-input.runtime.js").then((runtime) => ({ - resolveConfiguredSecretInputString: runtime.resolveConfiguredSecretInputString, - })), +const loadMatrixSecretInputDeps = createLazyRuntimeModule( + () => import("./config-secret-input.runtime.js"), ); function shouldRetryMatrixAuthRequest(err: unknown): boolean { @@ -109,6 +98,9 @@ async function retryMatrixAuthRequest( }); } +type MatrixWhoamiIdentity = { user_id?: string; device_id?: string }; +type MatrixLoginResponse = { access_token?: string; user_id?: string; device_id?: string }; + async function fetchMatrixWhoamiIdentity(params: { homeserver: string; accessToken: string; @@ -116,10 +108,7 @@ async function fetchMatrixWhoamiIdentity(params: { ssrfPolicy?: MatrixResolvedConfig["ssrfPolicy"]; dispatcherPolicy?: PinnedDispatcherPolicy; signal?: AbortSignal; -}): Promise<{ - user_id?: string; - device_id?: string; -}> { +}): Promise { const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps(); ensureMatrixSdkLoggingConfigured(); const tempClient = new MatrixClient(params.homeserver, params.accessToken, { @@ -127,204 +116,101 @@ async function fetchMatrixWhoamiIdentity(params: { ssrfPolicy: params.ssrfPolicy, dispatcherPolicy: params.dispatcherPolicy, }); - return (await retryMatrixAuthRequest( + return await retryMatrixAuthRequest( "matrix auth whoami", - async () => { - return (await tempClient.doRequest("GET", "/_matrix/client/v3/account/whoami")) as { - user_id?: string; - device_id?: string; - }; - }, + async () => + (await tempClient.doRequest( + "GET", + "/_matrix/client/v3/account/whoami", + )) as MatrixWhoamiIdentity, params.signal, - )) as { - user_id?: string; - device_id?: string; - }; -} - -function readEnvSecretRefFallback(params: { - value: unknown; - env?: NodeJS.ProcessEnv; - config?: Pick; -}): string | undefined { - const ref = coerceSecretRef(params.value, params.config?.secrets?.defaults); - if (!ref || ref.source !== "env" || !params.env) { - return undefined; - } - - const providerConfig = params.config?.secrets?.providers?.[ref.provider]; - if (providerConfig) { - if (providerConfig.source !== "env") { - throw new Error( - `Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`, - ); - } - if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) { - throw new Error( - `Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`, - ); - } - } else if (ref.provider !== (params.config?.secrets?.defaults?.env?.trim() || "default")) { - throw new Error( - `Secret provider "${ref.provider}" is not configured (ref: ${ref.source}:${ref.provider}:${ref.id}).`, - ); - } - - const resolved = params.env[ref.id]; - if (typeof resolved !== "string") { - return undefined; - } - - const trimmed = resolved.trim(); - return trimmed.length > 0 ? trimmed : undefined; -} - -function clean( - value: unknown, - path: string, - opts?: { - env?: NodeJS.ProcessEnv; - config?: Pick; - allowEnvSecretRefFallback?: boolean; - suppressSecretRef?: boolean; - }, -): string { - const ref = coerceSecretRef(value, opts?.config?.secrets?.defaults); - if (opts?.suppressSecretRef && ref) { - return ""; - } - const normalizedValue = opts?.allowEnvSecretRefFallback - ? ref?.source === "env" - ? (readEnvSecretRefFallback({ - value, - env: opts.env, - config: opts.config, - }) ?? value) - : ref - ? "" - : value - : value; - return ( - normalizeResolvedSecretInputString({ - value: normalizedValue, - path, - defaults: opts?.config?.secrets?.defaults, - }) ?? "" ); } -type MatrixConfigStringField = - | "homeserver" - | "userId" - | "accessToken" - | "password" - | "deviceId" - | "deviceName"; +const MATRIX_CONFIG_STRING_FIELDS = [ + "homeserver", + "userId", + "accessToken", + "password", + "deviceId", + "deviceName", +] as const satisfies readonly MatrixResolvedStringField[]; +type MatrixConfigStringField = (typeof MATRIX_CONFIG_STRING_FIELDS)[number]; +const MATRIX_AUTH_SECRET_FIELDS = ["accessToken", "password"] as const; +type MatrixAuthSecretField = (typeof MATRIX_AUTH_SECRET_FIELDS)[number]; +type MatrixConfiguredAuthInput = { value: unknown; path: string }; +type MatrixAuthInputs = Readonly>>; + +function readMatrixEnvSecretRef(params: { + ref: NonNullable>; + cfg: Pick; + env: NodeJS.ProcessEnv; +}): string | undefined { + const provider = params.cfg.secrets?.providers?.[params.ref.provider]; + if (provider) { + if (provider.source !== "env") { + throw new Error( + `Secret provider "${params.ref.provider}" has source "${provider.source}" but ref requests "env".`, + ); + } + if (provider.allowlist && !provider.allowlist.includes(params.ref.id)) { + throw new Error( + `Environment variable "${params.ref.id}" is not allowlisted in secrets.providers.${params.ref.provider}.allowlist.`, + ); + } + } else if (params.ref.provider !== (params.cfg.secrets?.defaults?.env?.trim() || "default")) { + throw new Error( + `Secret provider "${params.ref.provider}" is not configured (ref: env:${params.ref.provider}:${params.ref.id}).`, + ); + } + return params.env[params.ref.id]?.trim() || undefined; +} + +function readMatrixConfigString(params: { + value: unknown; + path: string; + cfg: Pick; + env: NodeJS.ProcessEnv; + allowEnvSecretRef?: boolean; + suppressSecretRef?: boolean; +}): string { + const ref = coerceSecretRef(params.value, params.cfg.secrets?.defaults); + if (params.suppressSecretRef && ref) { + return ""; + } + const value = params.allowEnvSecretRef + ? ref?.source === "env" + ? (readMatrixEnvSecretRef({ ref, cfg: params.cfg, env: params.env }) ?? params.value) + : ref + ? "" + : params.value + : params.value; + return ( + normalizeResolvedSecretInputString({ + value, + path: params.path, + defaults: params.cfg.secrets?.defaults, + }) ?? "" + ); +} function resolveMatrixBaseConfigFieldPath(field: MatrixConfigStringField): string { return `channels.matrix.${field}`; } -function shouldAllowEnvSecretRefFallback(field: MatrixConfigStringField): boolean { - return field === "accessToken" || field === "password"; -} - -type MatrixAuthSecretField = "accessToken" | "password"; - -type MatrixConfiguredAuthInput = { - value: unknown; - path: string; -}; - -function hasConfiguredSecretInputValue(value: unknown, cfg: Pick): boolean { +function hasConfiguredMatrixSecret(value: unknown, cfg: Pick): boolean { return ( (typeof value === "string" && value.trim().length > 0) || Boolean(coerceSecretRef(value, cfg.secrets?.defaults)) ); } -function hasConfiguredMatrixAccessTokenSource(params: { - cfg: CoreConfig; - env: NodeJS.ProcessEnv; - accountId: string; -}): boolean { - const normalizedAccountId = normalizeAccountId(params.accountId); - const account = findMatrixAccountConfig(params.cfg, normalizedAccountId) ?? {}; - const scopedAccessTokenVar = getMatrixScopedEnvVarNames(normalizedAccountId).accessToken; - if ( - hasConfiguredSecretInputValue(account.accessToken, params.cfg) || - clean(params.env[scopedAccessTokenVar], scopedAccessTokenVar).length > 0 - ) { - return true; - } - if (normalizedAccountId !== DEFAULT_ACCOUNT_ID) { - return false; - } - const matrix = resolveMatrixBaseConfig(params.cfg); - return ( - hasConfiguredSecretInputValue(matrix.accessToken, params.cfg) || - clean(params.env.MATRIX_ACCESS_TOKEN, "MATRIX_ACCESS_TOKEN").length > 0 - ); -} - -function resolveConfiguredMatrixAuthInput(params: { - cfg: CoreConfig; - env: NodeJS.ProcessEnv; - accountId: string; - field: MatrixAuthSecretField; -}): MatrixConfiguredAuthInput | undefined { - const normalizedAccountId = normalizeAccountId(params.accountId); - const account = findMatrixAccountConfig(params.cfg, normalizedAccountId) ?? {}; - const accountValue = account[params.field]; - if (accountValue !== undefined) { - return { - value: accountValue, - path: resolveMatrixConfigFieldPath(params.cfg, normalizedAccountId, params.field), - }; - } - - const scopedKeys = getMatrixScopedEnvVarNames(normalizedAccountId); - const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, params.env); - const scopedValue = scopedEnv[params.field]; - if (scopedValue !== undefined) { - return { - value: scopedValue, - path: params.field === "accessToken" ? scopedKeys.accessToken : scopedKeys.password, - }; - } - - if (normalizedAccountId !== DEFAULT_ACCOUNT_ID) { - return undefined; - } - - const matrix = resolveMatrixBaseConfig(params.cfg); - const baseValue = matrix[params.field]; - if (baseValue !== undefined) { - return { - value: baseValue, - path: resolveMatrixBaseConfigFieldPath(params.field), - }; - } - - const globalValue = - params.field === "accessToken" ? params.env.MATRIX_ACCESS_TOKEN : params.env.MATRIX_PASSWORD; - if (globalValue !== undefined) { - return { - value: globalValue, - path: params.field === "accessToken" ? "MATRIX_ACCESS_TOKEN" : "MATRIX_PASSWORD", - }; - } - - return undefined; -} - async function resolveConfiguredMatrixAuthSecretInput(params: { cfg: CoreConfig; env: NodeJS.ProcessEnv; - accountId: string; - field: MatrixAuthSecretField; + configured?: MatrixConfiguredAuthInput; }): Promise { - const configured = resolveConfiguredMatrixAuthInput(params); + const configured = params.configured; if (!configured) { return undefined; } @@ -355,42 +241,6 @@ async function resolveConfiguredMatrixAuthSecretInput(params: { ); } -function readMatrixBaseConfigField( - matrix: ReturnType, - field: MatrixConfigStringField, - opts?: { - env?: NodeJS.ProcessEnv; - config?: Pick; - suppressSecretRef?: boolean; - }, -): string { - return clean(matrix[field], resolveMatrixBaseConfigFieldPath(field), { - env: opts?.env, - config: opts?.config, - allowEnvSecretRefFallback: shouldAllowEnvSecretRefFallback(field), - suppressSecretRef: opts?.suppressSecretRef, - }); -} - -function readMatrixAccountConfigField( - cfg: CoreConfig, - accountId: string, - account: Partial>, - field: MatrixConfigStringField, - opts?: { - env?: NodeJS.ProcessEnv; - config?: Pick; - suppressSecretRef?: boolean; - }, -): string { - return clean(account[field], resolveMatrixConfigFieldPath(cfg, accountId, field), { - env: opts?.env, - config: opts?.config, - allowEnvSecretRefFallback: shouldAllowEnvSecretRefFallback(field), - suppressSecretRef: opts?.suppressSecretRef, - }); -} - function clampMatrixInitialSyncLimit(value: unknown): number | undefined { return resolveOptionalIntegerOption(value, { min: 0 }); } @@ -417,6 +267,25 @@ function buildMatrixNetworkFields(params: { }; } +function buildResolvedMatrixAuth( + resolved: MatrixResolvedConfig, + auth: Pick< + MatrixAuth, + "accountId" | "homeserver" | "userId" | "accessToken" | "password" | "deviceId" + >, +): MatrixAuth { + return { + ...auth, + deviceName: resolved.deviceName, + initialSyncLimit: resolved.initialSyncLimit, + encryption: resolved.encryption, + ...buildMatrixNetworkFields({ + allowPrivateNetwork: resolved.allowPrivateNetwork, + dispatcherPolicy: resolved.dispatcherPolicy, + }), + }; +} + export { hasReadyMatrixEnvAuth, resolveMatrixEnvAuthReadiness, @@ -439,79 +308,111 @@ function hasScopedMatrixEnvConfig(accountId: string, env: NodeJS.ProcessEnv): bo ); } +function readMatrixConfigStrings(params: { + cfg: CoreConfig; + env: NodeJS.ProcessEnv; + values: Partial>; + path: (field: MatrixConfigStringField) => string; + suppressPasswordSecretRef: boolean; +}): Record { + return Object.fromEntries( + MATRIX_CONFIG_STRING_FIELDS.map((field) => [ + field, + readMatrixConfigString({ + value: params.values[field], + path: params.path(field), + cfg: params.cfg, + env: params.env, + allowEnvSecretRef: MATRIX_AUTH_SECRET_FIELDS.includes(field as MatrixAuthSecretField), + suppressSecretRef: field === "password" && params.suppressPasswordSecretRef, + }), + ]), + ) as Record; +} + +function resolveMatrixAccountConfigSnapshot( + cfg: CoreConfig, + accountId: string, + env: NodeJS.ProcessEnv, +) { + const normalizedAccountId = normalizeAccountId(accountId); + const matrix = resolveMatrixBaseConfig(cfg); + const account = findMatrixAccountConfig(cfg, normalizedAccountId) ?? {}; + const scopedKeys = getMatrixScopedEnvVarNames(normalizedAccountId); + const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, env); + const globalEnv = resolveGlobalMatrixEnvConfig(env); + const authCandidates = (field: MatrixAuthSecretField): MatrixConfiguredAuthInput[] => [ + { value: account[field], path: resolveMatrixConfigFieldPath(cfg, accountId, field) }, + { value: scopedEnv[field], path: scopedKeys[field] }, + ...(normalizedAccountId === DEFAULT_ACCOUNT_ID + ? [ + { value: matrix[field], path: resolveMatrixBaseConfigFieldPath(field) }, + { + value: globalEnv[field], + path: field === "accessToken" ? "MATRIX_ACCESS_TOKEN" : "MATRIX_PASSWORD", + }, + ] + : []), + ]; + const accessTokenCandidates = authCandidates("accessToken"); + const passwordCandidates = authCandidates("password"); + const suppressPasswordSecretRef = accessTokenCandidates.some((source) => + hasConfiguredMatrixSecret(source.value, cfg), + ); + const resolvedStrings = resolveMatrixAccountStringValues({ + accountId: normalizedAccountId, + account: readMatrixConfigStrings({ + cfg, + env, + values: account, + path: (field) => resolveMatrixConfigFieldPath(cfg, normalizedAccountId, field), + suppressPasswordSecretRef, + }), + scopedEnv, + channel: readMatrixConfigStrings({ + cfg, + env, + values: matrix, + path: resolveMatrixBaseConfigFieldPath, + suppressPasswordSecretRef, + }), + globalEnv, + }); + const accountInitialSyncLimit = clampMatrixInitialSyncLimit(account.initialSyncLimit); + const allowPrivateNetwork = + isPrivateNetworkOptInEnabled(account) || isPrivateNetworkOptInEnabled(matrix) + ? true + : undefined; + return { + resolved: { + homeserver: resolvedStrings.homeserver, + userId: resolvedStrings.userId, + accessToken: resolvedStrings.accessToken || undefined, + password: resolvedStrings.password || undefined, + deviceId: resolvedStrings.deviceId || undefined, + deviceName: resolvedStrings.deviceName || undefined, + initialSyncLimit: + accountInitialSyncLimit ?? clampMatrixInitialSyncLimit(matrix.initialSyncLimit), + encryption: + typeof account.encryption === "boolean" ? account.encryption : (matrix.encryption ?? false), + ...buildMatrixNetworkFields({ + allowPrivateNetwork, + proxy: account.proxy ?? matrix.proxy, + }), + }, + authInputs: { + accessToken: accessTokenCandidates.find((source) => source.value !== undefined), + password: passwordCandidates.find((source) => source.value !== undefined), + } satisfies MatrixAuthInputs, + }; +} + export function resolveMatrixConfigForAccount( cfg: CoreConfig, accountId: string, env: NodeJS.ProcessEnv = process.env, ): MatrixResolvedConfig { - const matrix = resolveMatrixBaseConfig(cfg); - const account = findMatrixAccountConfig(cfg, accountId) ?? {}; - const normalizedAccountId = normalizeAccountId(accountId); - const suppressInactivePasswordSecretRef = hasConfiguredMatrixAccessTokenSource({ - cfg, - env, - accountId: normalizedAccountId, - }); - const fieldReadOptions = { - env, - config: cfg, - }; - const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, env); - const globalEnv = resolveGlobalMatrixEnvConfig(env); - const accountField = (field: MatrixConfigStringField) => - readMatrixAccountConfigField(cfg, normalizedAccountId, account, field, { - ...fieldReadOptions, - suppressSecretRef: field === "password" ? suppressInactivePasswordSecretRef : undefined, - }); - const resolvedStrings = resolveMatrixAccountStringValues({ - accountId: normalizedAccountId, - account: { - homeserver: accountField("homeserver"), - userId: accountField("userId"), - accessToken: accountField("accessToken"), - password: accountField("password"), - deviceId: accountField("deviceId"), - deviceName: accountField("deviceName"), - }, - scopedEnv, - channel: { - homeserver: readMatrixBaseConfigField(matrix, "homeserver", fieldReadOptions), - userId: readMatrixBaseConfigField(matrix, "userId", fieldReadOptions), - accessToken: readMatrixBaseConfigField(matrix, "accessToken", fieldReadOptions), - password: readMatrixBaseConfigField(matrix, "password", { - ...fieldReadOptions, - suppressSecretRef: suppressInactivePasswordSecretRef, - }), - deviceId: readMatrixBaseConfigField(matrix, "deviceId", fieldReadOptions), - deviceName: readMatrixBaseConfigField(matrix, "deviceName", fieldReadOptions), - }, - globalEnv, - }); - - const accountInitialSyncLimit = clampMatrixInitialSyncLimit(account.initialSyncLimit); - const initialSyncLimit = - accountInitialSyncLimit ?? clampMatrixInitialSyncLimit(matrix.initialSyncLimit); - const encryption = - typeof account.encryption === "boolean" ? account.encryption : (matrix.encryption ?? false); - const allowPrivateNetwork = - isPrivateNetworkOptInEnabled(account) || isPrivateNetworkOptInEnabled(matrix) - ? true - : undefined; - - return { - homeserver: resolvedStrings.homeserver, - userId: resolvedStrings.userId, - accessToken: resolvedStrings.accessToken || undefined, - password: resolvedStrings.password || undefined, - deviceId: resolvedStrings.deviceId || undefined, - deviceName: resolvedStrings.deviceName || undefined, - initialSyncLimit, - encryption, - ...buildMatrixNetworkFields({ - allowPrivateNetwork, - proxy: account.proxy ?? matrix.proxy, - }), - }; + return resolveMatrixAccountConfigSnapshot(cfg, accountId, env).resolved; } function resolveImplicitMatrixAccountId( @@ -524,16 +425,18 @@ function resolveImplicitMatrixAccountId( return normalizeAccountId(resolveMatrixDefaultOrOnlyAccountId(cfg, env)); } -export function resolveMatrixAuthContext(params: { - cfg: CoreConfig; - env?: NodeJS.ProcessEnv; - accountId?: string | null; -}): { +type MatrixAuthContext = { cfg: CoreConfig; env: NodeJS.ProcessEnv; accountId: string; resolved: MatrixResolvedConfig; -} { +}; + +function resolveMatrixAuthState(params: { + cfg: CoreConfig; + env?: NodeJS.ProcessEnv; + accountId?: string | null; +}): { context: MatrixAuthContext; authInputs: MatrixAuthInputs } { const cfg = requireRuntimeConfig(params.cfg, "Matrix auth context") as CoreConfig; const env = params?.env ?? process.env; const requestedAccountId = params?.accountId?.trim(); @@ -562,16 +465,26 @@ export function resolveMatrixAuthContext(params: { if (matrix.enabled === false || account?.enabled === false) { throw new Error(`Matrix account "${effectiveAccountId}" is disabled.`); } - const resolved = resolveMatrixConfigForAccount(cfg, effectiveAccountId, env); - + const snapshot = resolveMatrixAccountConfigSnapshot(cfg, effectiveAccountId, env); return { - cfg, - env, - accountId: effectiveAccountId, - resolved, + context: { + cfg, + env, + accountId: effectiveAccountId, + resolved: snapshot.resolved, + }, + authInputs: snapshot.authInputs, }; } +export function resolveMatrixAuthContext(params: { + cfg: CoreConfig; + env?: NodeJS.ProcessEnv; + accountId?: string | null; +}): MatrixAuthContext { + return resolveMatrixAuthState(params).context; +} + export async function resolveMatrixAuth(params?: { cfg?: CoreConfig; env?: NodeJS.ProcessEnv; @@ -582,17 +495,17 @@ export async function resolveMatrixAuth(params?: { "Matrix auth requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.", ); } - const { cfg, env, accountId, resolved } = resolveMatrixAuthContext({ + const { context, authInputs } = resolveMatrixAuthState({ cfg: params.cfg, env: params.env, accountId: params.accountId, }); + const { cfg, env, accountId, resolved } = context; const accessToken = (await resolveConfiguredMatrixAuthSecretInput({ cfg, env, - accountId, - field: "accessToken", + configured: authInputs.accessToken, })) ?? resolved.accessToken; const tokenAuthPassword = resolved.password; const homeserver = await resolveValidatedMatrixHomeserverUrl(resolved.homeserver, { @@ -657,41 +570,27 @@ export async function resolveMatrixAuth(params?: { const { touchMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await touchMatrixCredentials(env, accountId); } - return { + return buildResolvedMatrixAuth(resolved, { accountId, homeserver, userId, accessToken, password: tokenAuthPassword, deviceId: knownDeviceId, - deviceName: resolved.deviceName, - initialSyncLimit: resolved.initialSyncLimit, - encryption: resolved.encryption, - ...buildMatrixNetworkFields({ - allowPrivateNetwork: resolved.allowPrivateNetwork, - dispatcherPolicy: resolved.dispatcherPolicy, - }), - }; + }); } if (cachedCredentials) { const { touchMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await touchMatrixCredentials(env, accountId); - return { + return buildResolvedMatrixAuth(resolved, { accountId, homeserver: cachedCredentials.homeserver, userId: cachedCredentials.userId, accessToken: cachedCredentials.accessToken, password: tokenAuthPassword, deviceId: cachedCredentials.deviceId || resolved.deviceId, - deviceName: resolved.deviceName, - initialSyncLimit: resolved.initialSyncLimit, - encryption: resolved.encryption, - ...buildMatrixNetworkFields({ - allowPrivateNetwork: resolved.allowPrivateNetwork, - dispatcherPolicy: resolved.dispatcherPolicy, - }), - }; + }); } if (!resolved.userId) { @@ -702,8 +601,7 @@ export async function resolveMatrixAuth(params?: { (await resolveConfiguredMatrixAuthSecretInput({ cfg, env, - accountId, - field: "password", + configured: authInputs.password, })) ?? resolved.password; if (!password) { throw new Error( @@ -718,44 +616,31 @@ export async function resolveMatrixAuth(params?: { ssrfPolicy: resolved.ssrfPolicy, dispatcherPolicy: resolved.dispatcherPolicy, }); - const login = (await retryMatrixAuthRequest("matrix auth login", async () => { - return (await loginClient.doRequest("POST", "/_matrix/client/v3/login", undefined, { - type: "m.login.password", - identifier: { type: "m.id.user", user: resolved.userId }, - password, - device_id: resolved.deviceId, - initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway", - })) as { - access_token?: string; - user_id?: string; - device_id?: string; - }; - })) as { - access_token?: string; - user_id?: string; - device_id?: string; - }; + const login = await retryMatrixAuthRequest( + "matrix auth login", + async () => + (await loginClient.doRequest("POST", "/_matrix/client/v3/login", undefined, { + type: "m.login.password", + identifier: { type: "m.id.user", user: resolved.userId }, + password, + device_id: resolved.deviceId, + initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway", + })) as MatrixLoginResponse, + ); const loginAccessToken = login.access_token?.trim(); if (!loginAccessToken) { throw new Error("Matrix login did not return an access token"); } - const auth: MatrixAuth = { + const auth = buildResolvedMatrixAuth(resolved, { accountId, homeserver, userId: login.user_id ?? resolved.userId, accessToken: loginAccessToken, password, deviceId: login.device_id ?? resolved.deviceId, - deviceName: resolved.deviceName, - initialSyncLimit: resolved.initialSyncLimit, - encryption: resolved.encryption, - ...buildMatrixNetworkFields({ - allowPrivateNetwork: resolved.allowPrivateNetwork, - dispatcherPolicy: resolved.dispatcherPolicy, - }), - }; + }); const { saveMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await saveMatrixCredentials( @@ -850,4 +735,3 @@ export async function backfillMatrixAuthDeviceIdAfterStartup(params: { ); return saved === "saved" ? deviceId : undefined; } -/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */