mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
Matrix-js: add E2EE verification runtime and CLI
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import type { GatewayRequestHandlerOptions, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { matrixPlugin } from "./src/channel.js";
|
||||
import { registerMatrixJsCli } from "./src/cli.js";
|
||||
import {
|
||||
bootstrapMatrixVerification,
|
||||
getMatrixVerificationStatus,
|
||||
verifyMatrixRecoveryKey,
|
||||
} from "./src/matrix/actions/verification.js";
|
||||
import { setMatrixRuntime } from "./src/runtime.js";
|
||||
|
||||
const plugin = {
|
||||
@@ -11,6 +17,78 @@ const plugin = {
|
||||
register(api: OpenClawPluginApi) {
|
||||
setMatrixRuntime(api.runtime);
|
||||
api.registerChannel({ plugin: matrixPlugin });
|
||||
|
||||
const sendError = (respond: (ok: boolean, payload?: unknown) => void, err: unknown) => {
|
||||
respond(false, { error: err instanceof Error ? err.message : String(err) });
|
||||
};
|
||||
|
||||
api.registerGatewayMethod(
|
||||
"matrix-js.verify.recoveryKey",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const key = typeof params?.key === "string" ? params.key : "";
|
||||
if (!key.trim()) {
|
||||
respond(false, { error: "key required" });
|
||||
return;
|
||||
}
|
||||
const accountId =
|
||||
typeof params?.accountId === "string"
|
||||
? params.accountId.trim() || undefined
|
||||
: undefined;
|
||||
const result = await verifyMatrixRecoveryKey(key, { accountId });
|
||||
respond(result.success, result);
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod(
|
||||
"matrix-js.verify.bootstrap",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const accountId =
|
||||
typeof params?.accountId === "string"
|
||||
? params.accountId.trim() || undefined
|
||||
: undefined;
|
||||
const recoveryKey =
|
||||
typeof params?.recoveryKey === "string" ? params.recoveryKey : undefined;
|
||||
const forceResetCrossSigning = params?.forceResetCrossSigning === true;
|
||||
const result = await bootstrapMatrixVerification({
|
||||
accountId,
|
||||
recoveryKey,
|
||||
forceResetCrossSigning,
|
||||
});
|
||||
respond(result.success, result);
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod(
|
||||
"matrix-js.verify.status",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const accountId =
|
||||
typeof params?.accountId === "string"
|
||||
? params.accountId.trim() || undefined
|
||||
: undefined;
|
||||
const includeRecoveryKey = params?.includeRecoveryKey === true;
|
||||
const status = await getMatrixVerificationStatus({ accountId, includeRecoveryKey });
|
||||
respond(true, status);
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.registerCli(
|
||||
({ program }) => {
|
||||
registerMatrixJsCli({ program });
|
||||
},
|
||||
{ commands: ["matrix-js"] },
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
154
extensions/matrix-js/src/cli.ts
Normal file
154
extensions/matrix-js/src/cli.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { Command } from "commander";
|
||||
import {
|
||||
bootstrapMatrixVerification,
|
||||
getMatrixVerificationStatus,
|
||||
verifyMatrixRecoveryKey,
|
||||
} from "./matrix/actions/verification.js";
|
||||
|
||||
function printVerificationStatus(status: {
|
||||
verified: boolean;
|
||||
userId: string | null;
|
||||
deviceId: string | null;
|
||||
backupVersion: string | null;
|
||||
recoveryKeyStored: boolean;
|
||||
recoveryKeyCreatedAt: string | null;
|
||||
pendingVerifications: number;
|
||||
}): void {
|
||||
if (status.verified) {
|
||||
console.log("Verified: yes");
|
||||
console.log(`User: ${status.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${status.deviceId ?? "unknown"}`);
|
||||
if (status.backupVersion) {
|
||||
console.log(`Backup version: ${status.backupVersion}`);
|
||||
}
|
||||
} else {
|
||||
console.log("Verified: no");
|
||||
console.log(`User: ${status.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${status.deviceId ?? "unknown"}`);
|
||||
console.log("Run 'openclaw matrix-js verify recovery-key <key>' to verify this device.");
|
||||
}
|
||||
console.log(`Recovery key stored: ${status.recoveryKeyStored ? "yes" : "no"}`);
|
||||
if (status.recoveryKeyCreatedAt) {
|
||||
console.log(`Recovery key created at: ${status.recoveryKeyCreatedAt}`);
|
||||
}
|
||||
console.log(`Pending verifications: ${status.pendingVerifications}`);
|
||||
}
|
||||
|
||||
export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
const root = params.program
|
||||
.command("matrix-js")
|
||||
.description("Matrix-js channel utilities")
|
||||
.addHelpText("after", () => "\nDocs: https://docs.openclaw.ai/channels/matrix\n");
|
||||
|
||||
const verify = root.command("verify").description("Device verification for Matrix E2EE");
|
||||
|
||||
verify
|
||||
.command("status")
|
||||
.description("Check Matrix-js device verification status")
|
||||
.option("--account <id>", "Account ID (for multi-account setups)")
|
||||
.option("--include-recovery-key", "Include stored recovery key in output")
|
||||
.option("--json", "Output as JSON")
|
||||
.action(async (options: { account?: string; includeRecoveryKey?: boolean; json?: boolean }) => {
|
||||
try {
|
||||
const status = await getMatrixVerificationStatus({
|
||||
accountId: options.account,
|
||||
includeRecoveryKey: options.includeRecoveryKey === true,
|
||||
});
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(status, null, 2));
|
||||
return;
|
||||
}
|
||||
printVerificationStatus(status);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify({ error: message }, null, 2));
|
||||
} else {
|
||||
console.error(`Error: ${message}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
}
|
||||
});
|
||||
|
||||
verify
|
||||
.command("bootstrap")
|
||||
.description("Bootstrap Matrix-js cross-signing and device verification state")
|
||||
.option("--account <id>", "Account ID (for multi-account setups)")
|
||||
.option("--recovery-key <key>", "Recovery key to apply before bootstrap")
|
||||
.option("--force-reset-cross-signing", "Force reset cross-signing identity before bootstrap")
|
||||
.option("--json", "Output as JSON")
|
||||
.action(
|
||||
async (options: {
|
||||
account?: string;
|
||||
recoveryKey?: string;
|
||||
forceResetCrossSigning?: boolean;
|
||||
json?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const result = await bootstrapMatrixVerification({
|
||||
accountId: options.account,
|
||||
recoveryKey: options.recoveryKey,
|
||||
forceResetCrossSigning: options.forceResetCrossSigning === true,
|
||||
});
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
console.log(`Bootstrap success: ${result.success ? "yes" : "no"}`);
|
||||
if (result.error) {
|
||||
console.log(`Error: ${result.error}`);
|
||||
}
|
||||
console.log(`Verified: ${result.verification.verified ? "yes" : "no"}`);
|
||||
console.log(`User: ${result.verification.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${result.verification.deviceId ?? "unknown"}`);
|
||||
console.log(
|
||||
`Cross-signing published: ${result.crossSigning.published ? "yes" : "no"} (master=${result.crossSigning.masterKeyPublished ? "yes" : "no"}, self=${result.crossSigning.selfSigningKeyPublished ? "yes" : "no"}, user=${result.crossSigning.userSigningKeyPublished ? "yes" : "no"})`,
|
||||
);
|
||||
console.log(`Pending verifications: ${result.pendingVerifications}`);
|
||||
if (!result.success) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
||||
} else {
|
||||
console.error(`Verification bootstrap failed: ${message}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
verify
|
||||
.command("recovery-key <key>")
|
||||
.description("Verify device using a Matrix recovery key")
|
||||
.option("--account <id>", "Account ID (for multi-account setups)")
|
||||
.option("--json", "Output as JSON")
|
||||
.action(async (key: string, options: { account?: string; json?: boolean }) => {
|
||||
try {
|
||||
const result = await verifyMatrixRecoveryKey(key, { accountId: options.account });
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else if (result.success) {
|
||||
console.log("Device verification completed successfully.");
|
||||
console.log(`User: ${result.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${result.deviceId ?? "unknown"}`);
|
||||
if (result.backupVersion) {
|
||||
console.log(`Backup version: ${result.backupVersion}`);
|
||||
}
|
||||
} else {
|
||||
console.error(`Verification failed: ${result.error ?? "unknown error"}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
||||
} else {
|
||||
console.error(`Verification failed: ${message}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -13,17 +13,20 @@ export { listMatrixReactions, removeMatrixReactions } from "./actions/reactions.
|
||||
export { pinMatrixMessage, unpinMatrixMessage, listMatrixPins } from "./actions/pins.js";
|
||||
export { getMatrixMemberInfo, getMatrixRoomInfo } from "./actions/room.js";
|
||||
export {
|
||||
bootstrapMatrixVerification,
|
||||
acceptMatrixVerification,
|
||||
cancelMatrixVerification,
|
||||
confirmMatrixVerificationReciprocateQr,
|
||||
confirmMatrixVerificationSas,
|
||||
generateMatrixVerificationQr,
|
||||
getMatrixEncryptionStatus,
|
||||
getMatrixVerificationStatus,
|
||||
getMatrixVerificationSas,
|
||||
listMatrixVerifications,
|
||||
mismatchMatrixVerificationSas,
|
||||
requestMatrixVerification,
|
||||
scanMatrixVerificationQr,
|
||||
startMatrixVerification,
|
||||
verifyMatrixRecoveryKey,
|
||||
} from "./actions/verification.js";
|
||||
export { reactMatrixMessage } from "./send.js";
|
||||
|
||||
@@ -5,7 +5,9 @@ function requireCrypto(
|
||||
client: import("../sdk.js").MatrixClient,
|
||||
): NonNullable<import("../sdk.js").MatrixClient["crypto"]> {
|
||||
if (!client.crypto) {
|
||||
throw new Error("Matrix encryption is not available (enable channels.matrix.encryption=true)");
|
||||
throw new Error(
|
||||
"Matrix encryption is not available (enable channels.matrix-js.encryption=true)",
|
||||
);
|
||||
}
|
||||
return client.crypto;
|
||||
}
|
||||
@@ -218,3 +220,61 @@ export async function getMatrixEncryptionStatus(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMatrixVerificationStatus(
|
||||
opts: MatrixActionClientOpts & { includeRecoveryKey?: boolean } = {},
|
||||
) {
|
||||
const { client, stopOnDone } = await resolveActionClient(opts);
|
||||
try {
|
||||
const status = await client.getOwnDeviceVerificationStatus();
|
||||
const payload = {
|
||||
...status,
|
||||
pendingVerifications: client.crypto ? (await client.crypto.listVerifications()).length : 0,
|
||||
};
|
||||
if (!opts.includeRecoveryKey) {
|
||||
return payload;
|
||||
}
|
||||
const recoveryKey = client.crypto ? await client.crypto.getRecoveryKey() : null;
|
||||
return {
|
||||
...payload,
|
||||
recoveryKey: recoveryKey?.encodedPrivateKey ?? null,
|
||||
};
|
||||
} finally {
|
||||
if (stopOnDone) {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyMatrixRecoveryKey(
|
||||
recoveryKey: string,
|
||||
opts: MatrixActionClientOpts = {},
|
||||
) {
|
||||
const { client, stopOnDone } = await resolveActionClient(opts);
|
||||
try {
|
||||
return await client.verifyWithRecoveryKey(recoveryKey);
|
||||
} finally {
|
||||
if (stopOnDone) {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function bootstrapMatrixVerification(
|
||||
opts: MatrixActionClientOpts & {
|
||||
recoveryKey?: string;
|
||||
forceResetCrossSigning?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
const { client, stopOnDone } = await resolveActionClient(opts);
|
||||
try {
|
||||
return await client.bootstrapOwnDeviceVerification({
|
||||
recoveryKey: opts.recoveryKey?.trim() || undefined,
|
||||
forceResetCrossSigning: opts.forceResetCrossSigning === true,
|
||||
});
|
||||
} finally {
|
||||
if (stopOnDone) {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("registerMatrixAutoJoin", () => {
|
||||
const { client, getInviteHandler, joinRoom } = createClientStub();
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
matrix: {
|
||||
"matrix-js": {
|
||||
autoJoin: "always",
|
||||
},
|
||||
},
|
||||
@@ -71,7 +71,7 @@ describe("registerMatrixAutoJoin", () => {
|
||||
});
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
matrix: {
|
||||
"matrix-js": {
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["#allowed:example.org"],
|
||||
},
|
||||
@@ -102,7 +102,7 @@ describe("registerMatrixAutoJoin", () => {
|
||||
});
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
matrix: {
|
||||
"matrix-js": {
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: [" #allowed:example.org "],
|
||||
},
|
||||
|
||||
@@ -16,9 +16,9 @@ export function registerMatrixAutoJoin(params: {
|
||||
}
|
||||
runtime.log?.(message);
|
||||
};
|
||||
const autoJoin = cfg.channels?.matrix?.autoJoin ?? "always";
|
||||
const autoJoin = cfg.channels?.["matrix-js"]?.autoJoin ?? "always";
|
||||
const autoJoinAllowlist = new Set(
|
||||
(cfg.channels?.matrix?.autoJoinAllowlist ?? [])
|
||||
(cfg.channels?.["matrix-js"]?.autoJoinAllowlist ?? [])
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
@@ -75,7 +75,7 @@ export function registerMatrixMonitorEvents(params: {
|
||||
if (auth.encryption !== true && !warnedEncryptedRooms.has(roomId)) {
|
||||
warnedEncryptedRooms.add(roomId);
|
||||
const warning =
|
||||
"matrix: encrypted event received without encryption enabled; set channels.matrix.encryption=true and verify the device to decrypt";
|
||||
"matrix: encrypted event received without encryption enabled; set channels.matrix-js.encryption=true and verify the device to decrypt";
|
||||
logger.warn({ roomId }, warning);
|
||||
}
|
||||
if (auth.encryption === true && !client.crypto && !warnedCryptoMissingRooms.has(roomId)) {
|
||||
|
||||
@@ -234,10 +234,10 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
|
||||
const senderName = await getMemberDisplayName(roomId, senderId);
|
||||
const storeAllowFrom = await core.channel.pairing
|
||||
.readAllowFromStore("matrix")
|
||||
.readAllowFromStore("matrix-js")
|
||||
.catch(() => []);
|
||||
const effectiveAllowFrom = normalizeMatrixAllowList([...allowFrom, ...storeAllowFrom]);
|
||||
const groupAllowFrom = cfg.channels?.matrix?.groupAllowFrom ?? [];
|
||||
const groupAllowFrom = cfg.channels?.["matrix-js"]?.groupAllowFrom ?? [];
|
||||
const effectiveGroupAllowFrom = normalizeMatrixAllowList(groupAllowFrom);
|
||||
const groupAllowConfigured = effectiveGroupAllowFrom.length > 0;
|
||||
|
||||
@@ -254,7 +254,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
if (!allowMatch.allowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
id: senderId,
|
||||
meta: { name: senderName },
|
||||
});
|
||||
@@ -271,7 +271,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
"openclaw pairing approve matrix <code>",
|
||||
"openclaw pairing approve matrix-js <code>",
|
||||
].join("\n"),
|
||||
{ client },
|
||||
);
|
||||
@@ -375,7 +375,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
});
|
||||
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
||||
cfg,
|
||||
surface: "matrix",
|
||||
surface: "matrix-js",
|
||||
});
|
||||
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||
const senderAllowedForCommands = resolveMatrixAllowListMatches({
|
||||
@@ -410,7 +410,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
if (isRoom && commandGate.shouldBlock) {
|
||||
logInboundDrop({
|
||||
log: logVerboseMessage,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
reason: "control command (unauthorized)",
|
||||
target: senderId,
|
||||
});
|
||||
@@ -451,7 +451,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
|
||||
const route = core.channel.routing.resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
peer: {
|
||||
kind: isDirectMessage ? "dm" : "channel",
|
||||
id: isDirectMessage ? senderId : roomId,
|
||||
@@ -493,8 +493,8 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
GroupSubject: isRoom ? (roomName ?? roomId) : undefined,
|
||||
GroupChannel: isRoom ? (roomInfo.canonicalAlias ?? roomId) : undefined,
|
||||
GroupSystemPrompt: isRoom ? groupSystemPrompt : undefined,
|
||||
Provider: "matrix" as const,
|
||||
Surface: "matrix" as const,
|
||||
Provider: "matrix-js" as const,
|
||||
Surface: "matrix-js" as const,
|
||||
WasMentioned: isRoom ? wasMentioned : undefined,
|
||||
MessageSid: messageId,
|
||||
ReplyToId: threadTarget ? undefined : (replyToEventId ?? undefined),
|
||||
@@ -506,7 +506,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
...locationPayload?.context,
|
||||
CommandAuthorized: commandAuthorized,
|
||||
CommandSource: "text" as const,
|
||||
OriginatingChannel: "matrix" as const,
|
||||
OriginatingChannel: "matrix-js" as const,
|
||||
OriginatingTo: `room:${roomId}`,
|
||||
});
|
||||
|
||||
@@ -517,7 +517,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
updateLastRoute: isDirectMessage
|
||||
? {
|
||||
sessionKey: route.mainSessionKey,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
to: `room:${roomId}`,
|
||||
accountId: route.accountId,
|
||||
}
|
||||
@@ -576,13 +576,13 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
let didSendReply = false;
|
||||
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg,
|
||||
agentId: route.agentId,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const typingCallbacks = createTypingCallbacks({
|
||||
@@ -591,7 +591,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
onStartError: (err) => {
|
||||
logTypingFailure({
|
||||
log: logVerboseMessage,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
action: "start",
|
||||
target: roomId,
|
||||
error: err,
|
||||
@@ -600,7 +600,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
onStopError: (err) => {
|
||||
logTypingFailure({
|
||||
log: logVerboseMessage,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
action: "stop",
|
||||
target: roomId,
|
||||
error: err,
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
}
|
||||
const core = getMatrixRuntime();
|
||||
let cfg = core.config.loadConfig() as CoreConfig;
|
||||
if (cfg.channels?.matrix?.enabled === false) {
|
||||
if (cfg.channels?.["matrix-js"]?.enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,10 +220,10 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
matrix: {
|
||||
...cfg.channels?.matrix,
|
||||
"matrix-js": {
|
||||
...cfg.channels?.["matrix-js"],
|
||||
dm: {
|
||||
...cfg.channels?.matrix?.dm,
|
||||
...cfg.channels?.["matrix-js"]?.dm,
|
||||
allowFrom,
|
||||
},
|
||||
groupAllowFrom,
|
||||
@@ -253,13 +253,13 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
|
||||
const { groupPolicy: groupPolicyRaw, providerMissingFallbackApplied } =
|
||||
resolveAllowlistProviderRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.matrix !== undefined,
|
||||
providerConfigPresent: cfg.channels?.["matrix-js"] !== undefined,
|
||||
groupPolicy: accountConfig.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
});
|
||||
warnMissingProviderGroupPolicyFallbackOnce({
|
||||
providerMissingFallbackApplied,
|
||||
providerKey: "matrix",
|
||||
providerKey: "matrix-js",
|
||||
accountId: account.accountId,
|
||||
blockedLabel: GROUP_POLICY_BLOCKED_LABEL.room,
|
||||
log: (message) => logVerboseMessage(message),
|
||||
@@ -271,7 +271,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
const dmEnabled = dmConfig?.enabled ?? true;
|
||||
const dmPolicyRaw = dmConfig?.policy ?? "pairing";
|
||||
const dmPolicy = allowlistOnly && dmPolicyRaw !== "disabled" ? "allowlist" : dmPolicyRaw;
|
||||
const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "matrix");
|
||||
const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "matrix-js");
|
||||
const mediaMaxMb = opts.mediaMaxMb ?? accountConfig.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;
|
||||
const mediaMaxBytes = Math.max(1, mediaMaxMb) * 1024 * 1024;
|
||||
const startupMs = Date.now();
|
||||
@@ -329,18 +329,19 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
// Shared client is already started via resolveSharedMatrixClient.
|
||||
logger.info(`matrix: logged in as ${auth.userId}`);
|
||||
|
||||
// If E2EE is enabled, trigger device verification
|
||||
// If E2EE is enabled, report device verification status and guidance.
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
// Request verification from other sessions
|
||||
const verificationRequest = await (
|
||||
client.crypto as { requestOwnUserVerification?: () => Promise<unknown> }
|
||||
).requestOwnUserVerification?.();
|
||||
if (verificationRequest) {
|
||||
logger.info("matrix: device verification requested - please verify in another client");
|
||||
const status = await client.getOwnDeviceVerificationStatus();
|
||||
if (status.verified) {
|
||||
logger.info("matrix: device is verified and ready for encrypted rooms");
|
||||
} else {
|
||||
logger.info(
|
||||
"matrix: device not verified — run 'openclaw matrix-js verify recovery-key <key>' to enable E2EE",
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug?.("Device verification request failed (may already be verified)", {
|
||||
logger.debug?.("Failed to resolve matrix-js verification status (non-fatal)", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function deliverMatrixReplies(params: {
|
||||
params.tableMode ??
|
||||
core.channel.text.resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "matrix",
|
||||
channel: "matrix-js",
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const logVerbose = (message: string) => {
|
||||
@@ -29,7 +29,7 @@ export async function deliverMatrixReplies(params: {
|
||||
}
|
||||
};
|
||||
const chunkLimit = Math.min(params.textLimit, 4000);
|
||||
const chunkMode = core.channel.text.resolveChunkMode(cfg, "matrix", params.accountId);
|
||||
const chunkMode = core.channel.text.resolveChunkMode(cfg, "matrix-js", params.accountId);
|
||||
let hasReplied = false;
|
||||
for (const reply of params.replies) {
|
||||
const hasMedia = Boolean(reply?.mediaUrl) || (reply?.mediaUrls?.length ?? 0) > 0;
|
||||
|
||||
@@ -98,6 +98,25 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("fails in strict mode when cross-signing keys are still unpublished", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const crypto = createCryptoApi({
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
isCrossSigningReady: vi.fn(async () => false),
|
||||
userHasCrossSigningKeys: vi.fn(async () => false),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
})),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
);
|
||||
|
||||
await expect(bootstrapper.bootstrap(crypto, { strict: true })).rejects.toThrow(
|
||||
"Cross-signing bootstrap finished but server keys are still not published",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses password UIA fallback when null and dummy auth fail", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const bootstrapCrossSigning = vi.fn(async () => {});
|
||||
|
||||
@@ -22,15 +22,38 @@ export type MatrixCryptoBootstrapperDeps<TRawEvent extends MatrixRawEvent> = {
|
||||
decryptBridge: Pick<MatrixDecryptBridge<TRawEvent>, "bindCryptoRetrySignals">;
|
||||
};
|
||||
|
||||
export type MatrixCryptoBootstrapOptions = {
|
||||
forceResetCrossSigning?: boolean;
|
||||
strict?: boolean;
|
||||
};
|
||||
|
||||
export type MatrixCryptoBootstrapResult = {
|
||||
crossSigningReady: boolean;
|
||||
crossSigningPublished: boolean;
|
||||
ownDeviceVerified: boolean | null;
|
||||
};
|
||||
|
||||
export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
constructor(private readonly deps: MatrixCryptoBootstrapperDeps<TRawEvent>) {}
|
||||
|
||||
async bootstrap(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
await this.bootstrapSecretStorage(crypto);
|
||||
await this.bootstrapCrossSigning(crypto);
|
||||
await this.bootstrapSecretStorage(crypto);
|
||||
await this.ensureOwnDeviceTrust(crypto);
|
||||
async bootstrap(
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
options: MatrixCryptoBootstrapOptions = {},
|
||||
): Promise<MatrixCryptoBootstrapResult> {
|
||||
const strict = options.strict === true;
|
||||
await this.bootstrapSecretStorage(crypto, strict);
|
||||
const crossSigning = await this.bootstrapCrossSigning(crypto, {
|
||||
forceResetCrossSigning: options.forceResetCrossSigning === true,
|
||||
strict,
|
||||
});
|
||||
await this.bootstrapSecretStorage(crypto, strict);
|
||||
const ownDeviceVerified = await this.ensureOwnDeviceTrust(crypto, strict);
|
||||
this.registerVerificationRequestHandler(crypto);
|
||||
return {
|
||||
crossSigningReady: crossSigning.ready,
|
||||
crossSigningPublished: crossSigning.published,
|
||||
ownDeviceVerified,
|
||||
};
|
||||
}
|
||||
|
||||
private createSigningKeysUiAuthCallback(params: {
|
||||
@@ -47,7 +70,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
} catch {
|
||||
if (!params.password?.trim()) {
|
||||
throw new Error(
|
||||
"Matrix cross-signing key upload requires UIA; provide matrix.password for m.login.password fallback",
|
||||
"Matrix cross-signing key upload requires UIA; provide matrix-js.password for m.login.password fallback",
|
||||
);
|
||||
}
|
||||
return await makeRequest({
|
||||
@@ -60,7 +83,10 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
};
|
||||
}
|
||||
|
||||
private async bootstrapCrossSigning(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
private async bootstrapCrossSigning(
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
options: { forceResetCrossSigning: boolean; strict: boolean },
|
||||
): Promise<{ ready: boolean; published: boolean }> {
|
||||
const userId = await this.deps.getUserId();
|
||||
const authUploadDeviceSigningKeys = this.createSigningKeysUiAuthCallback({
|
||||
userId,
|
||||
@@ -87,6 +113,37 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
}
|
||||
};
|
||||
|
||||
const finalize = async (): Promise<{ ready: boolean; published: boolean }> => {
|
||||
const ready = await isCrossSigningReady();
|
||||
const published = await hasPublishedCrossSigningKeys();
|
||||
if (ready && published) {
|
||||
LogService.info("MatrixClientLite", "Cross-signing bootstrap complete");
|
||||
return { ready, published };
|
||||
}
|
||||
const message = "Cross-signing bootstrap finished but server keys are still not published";
|
||||
LogService.warn("MatrixClientLite", message);
|
||||
if (options.strict) {
|
||||
throw new Error(message);
|
||||
}
|
||||
return { ready, published };
|
||||
};
|
||||
|
||||
if (options.forceResetCrossSigning) {
|
||||
try {
|
||||
await crypto.bootstrapCrossSigning({
|
||||
setupNewCrossSigning: true,
|
||||
authUploadDeviceSigningKeys,
|
||||
});
|
||||
} catch (err) {
|
||||
LogService.warn("MatrixClientLite", "Forced cross-signing reset failed:", err);
|
||||
if (options.strict) {
|
||||
throw err instanceof Error ? err : new Error(String(err));
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
return await finalize();
|
||||
}
|
||||
|
||||
// First pass: preserve existing cross-signing identity and ensure public keys are uploaded.
|
||||
try {
|
||||
await crypto.bootstrapCrossSigning({
|
||||
@@ -105,7 +162,10 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
});
|
||||
} catch (resetErr) {
|
||||
LogService.warn("MatrixClientLite", "Failed to bootstrap cross-signing:", resetErr);
|
||||
return;
|
||||
if (options.strict) {
|
||||
throw resetErr instanceof Error ? resetErr : new Error(String(resetErr));
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +173,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
const firstPassPublished = await hasPublishedCrossSigningKeys();
|
||||
if (firstPassReady && firstPassPublished) {
|
||||
LogService.info("MatrixClientLite", "Cross-signing bootstrap complete");
|
||||
return;
|
||||
return { ready: true, published: true };
|
||||
}
|
||||
|
||||
// Fallback: recover from broken local/server state by creating a fresh identity.
|
||||
@@ -124,27 +184,27 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
});
|
||||
} catch (err) {
|
||||
LogService.warn("MatrixClientLite", "Fallback cross-signing bootstrap failed:", err);
|
||||
return;
|
||||
if (options.strict) {
|
||||
throw err instanceof Error ? err : new Error(String(err));
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
|
||||
const finalReady = await isCrossSigningReady();
|
||||
const finalPublished = await hasPublishedCrossSigningKeys();
|
||||
if (finalReady && finalPublished) {
|
||||
LogService.info("MatrixClientLite", "Cross-signing bootstrap complete");
|
||||
return;
|
||||
}
|
||||
LogService.warn(
|
||||
"MatrixClientLite",
|
||||
"Cross-signing bootstrap finished but server keys are still not published",
|
||||
);
|
||||
return await finalize();
|
||||
}
|
||||
|
||||
private async bootstrapSecretStorage(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
private async bootstrapSecretStorage(
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
strict = false,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto);
|
||||
LogService.info("MatrixClientLite", "Secret storage bootstrap complete");
|
||||
} catch (err) {
|
||||
LogService.warn("MatrixClientLite", "Failed to bootstrap secret storage:", err);
|
||||
if (strict) {
|
||||
throw err instanceof Error ? err : new Error(String(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +248,13 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
LogService.info("MatrixClientLite", "Verification request handler registered");
|
||||
}
|
||||
|
||||
private async ensureOwnDeviceTrust(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
private async ensureOwnDeviceTrust(
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
strict = false,
|
||||
): Promise<boolean | null> {
|
||||
const deviceId = this.deps.getDeviceId()?.trim();
|
||||
if (!deviceId) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const userId = await this.deps.getUserId();
|
||||
|
||||
@@ -206,7 +269,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
deviceStatus?.signedByOwner === true;
|
||||
|
||||
if (alreadyVerified) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof crypto.setDeviceVerified === "function") {
|
||||
@@ -222,5 +285,19 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
await crypto.crossSignDevice(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
const refreshedStatus =
|
||||
typeof crypto.getDeviceVerificationStatus === "function"
|
||||
? await crypto.getDeviceVerificationStatus(userId, deviceId).catch(() => null)
|
||||
: null;
|
||||
const verified =
|
||||
refreshedStatus?.isVerified?.() === true ||
|
||||
refreshedStatus?.localVerified === true ||
|
||||
refreshedStatus?.crossSigningVerified === true ||
|
||||
refreshedStatus?.signedByOwner === true;
|
||||
if (!verified && strict) {
|
||||
throw new Error(`Matrix own device ${deviceId} is not verified after bootstrap`);
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ function resolveDefaultIdbSnapshotPath(): string {
|
||||
process.env.OPENCLAW_STATE_DIR ||
|
||||
process.env.MOLTBOT_STATE_DIR ||
|
||||
path.join(process.env.HOME || "/tmp", ".openclaw");
|
||||
return path.join(stateDir, "credentials", "matrix", "crypto-idb-snapshot.json");
|
||||
return path.join(stateDir, "credentials", "matrix-js", "crypto-idb-snapshot.json");
|
||||
}
|
||||
|
||||
export async function restoreIdbFromDisk(snapshotPath?: string): Promise<boolean> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { encodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key.js";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { MatrixRecoveryKeyStore } from "./recovery-key-store.js";
|
||||
import type { MatrixCryptoBootstrapApi } from "./types.js";
|
||||
@@ -173,4 +174,29 @@ describe("MatrixRecoveryKeyStore", () => {
|
||||
encodedPrivateKey: "encoded-recovered-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("stores an encoded recovery key and decodes its private key material", () => {
|
||||
const recoveryKeyPath = createTempRecoveryKeyPath();
|
||||
const store = new MatrixRecoveryKeyStore(recoveryKeyPath);
|
||||
const encoded = encodeRecoveryKey(new Uint8Array(Array.from({ length: 32 }, (_, i) => i + 1)));
|
||||
expect(encoded).toBeTypeOf("string");
|
||||
|
||||
const summary = store.storeEncodedRecoveryKey({
|
||||
encodedPrivateKey: encoded as string,
|
||||
keyId: "SSSSKEY",
|
||||
});
|
||||
|
||||
expect(summary.keyId).toBe("SSSSKEY");
|
||||
expect(summary.encodedPrivateKey).toBe(encoded);
|
||||
const persisted = JSON.parse(fs.readFileSync(recoveryKeyPath, "utf8")) as {
|
||||
privateKeyBase64?: string;
|
||||
keyId?: string;
|
||||
};
|
||||
expect(persisted.keyId).toBe("SSSSKEY");
|
||||
expect(
|
||||
Buffer.from(persisted.privateKeyBase64 ?? "", "base64").equals(
|
||||
Buffer.from(Array.from({ length: 32 }, (_, i) => i + 1)),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key.js";
|
||||
import { LogService } from "./logger.js";
|
||||
import type {
|
||||
MatrixCryptoBootstrapApi,
|
||||
@@ -88,6 +89,43 @@ export class MatrixRecoveryKeyStore {
|
||||
};
|
||||
}
|
||||
|
||||
storeEncodedRecoveryKey(params: {
|
||||
encodedPrivateKey: string;
|
||||
keyId?: string | null;
|
||||
keyInfo?: MatrixStoredRecoveryKey["keyInfo"];
|
||||
}): {
|
||||
encodedPrivateKey?: string;
|
||||
keyId?: string | null;
|
||||
createdAt?: string;
|
||||
} {
|
||||
const encodedPrivateKey = params.encodedPrivateKey.trim();
|
||||
if (!encodedPrivateKey) {
|
||||
throw new Error("Matrix recovery key is required");
|
||||
}
|
||||
let privateKey: Uint8Array;
|
||||
try {
|
||||
privateKey = decodeRecoveryKey(encodedPrivateKey);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Invalid Matrix recovery key: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedKeyId =
|
||||
typeof params.keyId === "string" && params.keyId.trim() ? params.keyId.trim() : null;
|
||||
const keyInfo = params.keyInfo ?? this.loadStoredRecoveryKey()?.keyInfo;
|
||||
this.saveRecoveryKeyToDisk({
|
||||
keyId: normalizedKeyId,
|
||||
keyInfo,
|
||||
privateKey,
|
||||
encodedPrivateKey,
|
||||
});
|
||||
if (normalizedKeyId) {
|
||||
this.rememberSecretStorageKey(normalizedKeyId, privateKey, keyInfo);
|
||||
}
|
||||
return this.getRecoveryKeySummary() ?? {};
|
||||
}
|
||||
|
||||
async bootstrapSecretStorageWithRecoveryKey(crypto: MatrixCryptoBootstrapApi): Promise<void> {
|
||||
let status: MatrixSecretStorageStatus | null = null;
|
||||
if (typeof crypto.getSecretStorageStatus === "function") {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
readStringParam,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import {
|
||||
bootstrapMatrixVerification,
|
||||
acceptMatrixVerification,
|
||||
cancelMatrixVerification,
|
||||
confirmMatrixVerificationReciprocateQr,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
editMatrixMessage,
|
||||
generateMatrixVerificationQr,
|
||||
getMatrixEncryptionStatus,
|
||||
getMatrixVerificationStatus,
|
||||
getMatrixMemberInfo,
|
||||
getMatrixRoomInfo,
|
||||
getMatrixVerificationSas,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
sendMatrixMessage,
|
||||
startMatrixVerification,
|
||||
unpinMatrixMessage,
|
||||
verifyMatrixRecoveryKey,
|
||||
} from "./matrix/actions.js";
|
||||
import { reactMatrixMessage } from "./matrix/send.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
@@ -50,6 +53,9 @@ const verificationActions = new Set([
|
||||
"verificationConfirm",
|
||||
"verificationMismatch",
|
||||
"verificationConfirmQr",
|
||||
"verificationStatus",
|
||||
"verificationBootstrap",
|
||||
"verificationRecoveryKey",
|
||||
]);
|
||||
|
||||
function readRoomId(params: Record<string, unknown>, required = true): string {
|
||||
@@ -68,7 +74,8 @@ export async function handleMatrixAction(
|
||||
cfg: CoreConfig,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const isActionEnabled = createActionGate(cfg.channels?.matrix?.actions);
|
||||
const accountId = readStringParam(params, "accountId") ?? undefined;
|
||||
const isActionEnabled = createActionGate(cfg.channels?.["matrix-js"]?.actions);
|
||||
|
||||
if (reactionActions.has(action)) {
|
||||
if (!isActionEnabled("reactions")) {
|
||||
@@ -198,11 +205,37 @@ export async function handleMatrixAction(
|
||||
|
||||
if (action === "encryptionStatus") {
|
||||
const includeRecoveryKey = params.includeRecoveryKey === true;
|
||||
const status = await getMatrixEncryptionStatus({ includeRecoveryKey });
|
||||
const status = await getMatrixEncryptionStatus({ includeRecoveryKey, accountId });
|
||||
return jsonResult({ ok: true, status });
|
||||
}
|
||||
if (action === "verificationStatus") {
|
||||
const includeRecoveryKey = params.includeRecoveryKey === true;
|
||||
const status = await getMatrixVerificationStatus({ includeRecoveryKey, accountId });
|
||||
return jsonResult({ ok: true, status });
|
||||
}
|
||||
if (action === "verificationBootstrap") {
|
||||
const recoveryKey =
|
||||
readStringParam(params, "recoveryKey", { trim: false }) ??
|
||||
readStringParam(params, "key", { trim: false });
|
||||
const result = await bootstrapMatrixVerification({
|
||||
recoveryKey: recoveryKey ?? undefined,
|
||||
forceResetCrossSigning: params.forceResetCrossSigning === true,
|
||||
accountId,
|
||||
});
|
||||
return jsonResult({ ok: result.success, result });
|
||||
}
|
||||
if (action === "verificationRecoveryKey") {
|
||||
const recoveryKey =
|
||||
readStringParam(params, "recoveryKey", { trim: false }) ??
|
||||
readStringParam(params, "key", { trim: false });
|
||||
const result = await verifyMatrixRecoveryKey(
|
||||
readStringParam({ recoveryKey }, "recoveryKey", { required: true, trim: false }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: result.success, result });
|
||||
}
|
||||
if (action === "verificationList") {
|
||||
const verifications = await listMatrixVerifications();
|
||||
const verifications = await listMatrixVerifications({ accountId });
|
||||
return jsonResult({ ok: true, verifications });
|
||||
}
|
||||
if (action === "verificationRequest") {
|
||||
@@ -215,12 +248,14 @@ export async function handleMatrixAction(
|
||||
userId: userId ?? undefined,
|
||||
deviceId: deviceId ?? undefined,
|
||||
roomId: roomId ?? undefined,
|
||||
accountId,
|
||||
});
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
if (action === "verificationAccept") {
|
||||
const verification = await acceptMatrixVerification(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
@@ -229,7 +264,7 @@ export async function handleMatrixAction(
|
||||
const code = readStringParam(params, "code");
|
||||
const verification = await cancelMatrixVerification(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ reason: reason ?? undefined, code: code ?? undefined },
|
||||
{ reason: reason ?? undefined, code: code ?? undefined, accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
@@ -243,13 +278,14 @@ export async function handleMatrixAction(
|
||||
}
|
||||
const verification = await startMatrixVerification(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ method: "sas" },
|
||||
{ method: "sas", accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
if (action === "verificationGenerateQr") {
|
||||
const qr = await generateMatrixVerificationQr(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, ...qr });
|
||||
}
|
||||
@@ -261,30 +297,35 @@ export async function handleMatrixAction(
|
||||
const verification = await scanMatrixVerificationQr(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
readStringParam({ qrDataBase64 }, "qrDataBase64", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
if (action === "verificationSas") {
|
||||
const sas = await getMatrixVerificationSas(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, sas });
|
||||
}
|
||||
if (action === "verificationConfirm") {
|
||||
const verification = await confirmMatrixVerificationSas(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
if (action === "verificationMismatch") {
|
||||
const verification = await mismatchMatrixVerificationSas(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
if (action === "verificationConfirmQr") {
|
||||
const verification = await confirmMatrixVerificationReciprocateQr(
|
||||
readStringParam({ requestId }, "requestId", { required: true }),
|
||||
{ accountId },
|
||||
);
|
||||
return jsonResult({ ok: true, verification });
|
||||
}
|
||||
|
||||
990
pnpm-lock.yaml
generated
990
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user