mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2026-05-23 15:43:33 -06:00
Upgrade to matrix-js-sdk 41.5.0; add device verification
Upgrades matrix-js-sdk from 34.13.0 to 41.5.0. This crosses the v37 removal of the legacy libolm crypto stack, so E2EE is migrated to the Rust crypto implementation. Also adds device verification, cross-signing setup, and authenticated media support. Dependencies - Bump matrix-js-sdk ^34.13.0 -> ^41.5.0; require Node.js >= 22. - Drop the `olm` dependency (legacy crypto only); add `fake-indexeddb`. Rust crypto - Replace initCrypto() with initRustCrypto(); the legacy crypto stack was removed upstream in v37. - Add src/matrix-crypto-store.js: the Rust crypto store requires IndexedDB, absent in Node.js, so it is backed by fake-indexeddb and snapshotted to disk (rust-crypto-store.v8) to survive restarts. - Migrate existing libolm crypto state into the Rust store on first run, and discard the stored crypto state when the device ID changes. Homeserver discovery - Resolve the homeserver via .well-known, so a delegating domain (e.g. example.org) works as the configured server URL. Cross-signing & secure backup - Add a secured /matrix-chat/secure-backup admin endpoint and a modal dialog on the server config node: check status, unlock an existing secure backup with its recovery key, or reset and create a new one. Device verification (new nodes) - matrix-verification: event source emitting verification requests and phase changes, with on-node filters (phase, initiated by, type, self-verification, user allowlist, room). - matrix-verification-action: request, accept, start SAS, confirm, mismatch, or cancel an in-flight verification. Authenticated media - matrix-receive and matrix-crypt-file use the authenticated media endpoints, send a bearer token via msg.headers, and fall back between the v3 and v1 media endpoints on a 404. Fixes - Surface connection/auth errors in the log; node.error() calls were passed an empty msg object, which routed the error and suppressed console logging. - matrix-get-user: await getProfileInfo()/getPresence(). - matrix-invite-room: pass the reason as the third invite() argument (the removed callback parameter was shifting it out). - Guard the verification handlers so a throwing SDK getter cannot crash Node-RED. Docs - Add the device-verification example flow; update the READMEs and node help, correcting stale claims that device verification, secure backup, and encrypted file uploads were unsupported.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixVerificationAction(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.mode = n.mode || "accept";
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
if (!node.server) {
|
||||
node.error("No configuration node");
|
||||
return;
|
||||
}
|
||||
node.server.register(node);
|
||||
|
||||
node.server.on("disconnected", function() {
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
});
|
||||
node.server.on("connected", function() {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
});
|
||||
|
||||
node.on("input", async function(msg) {
|
||||
if (!node.server || !node.server.matrixClient) {
|
||||
msg.error = "No matrix server selected";
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.server.isConnected()) {
|
||||
msg.error = "Matrix server connection is currently closed";
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
const crypto = node.server.matrixClient.getCrypto();
|
||||
if (!crypto) {
|
||||
msg.error = "End-to-end encryption is not enabled on the Matrix server config";
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
// msg.mode overrides the node's configured mode if provided
|
||||
const mode = msg.mode || node.mode;
|
||||
|
||||
try {
|
||||
if (mode === "request") {
|
||||
// Start a new verification request.
|
||||
// - msg.userId + msg.deviceId : verify a specific device (to-device)
|
||||
// - msg.userId + msg.topic : verify a user in a DM room
|
||||
// - otherwise : verify our own other devices
|
||||
let request;
|
||||
if (msg.userId && msg.deviceId) {
|
||||
request = await crypto.requestDeviceVerification(msg.userId, msg.deviceId);
|
||||
} else if (msg.userId && msg.topic) {
|
||||
request = await crypto.requestVerificationDM(msg.userId, msg.topic);
|
||||
} else {
|
||||
request = await crypto.requestOwnUserVerification();
|
||||
}
|
||||
|
||||
if (typeof node.server.trackVerificationRequest === "function") {
|
||||
node.server.trackVerificationRequest(request);
|
||||
}
|
||||
msg.verificationId = request.transactionId;
|
||||
node.send([msg, null]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Every other mode acts on an existing tracked request.
|
||||
const request = node.server.verificationRequests.get(msg.verificationId);
|
||||
if (!request) {
|
||||
throw new Error(`No active verification found for msg.verificationId '${msg.verificationId}'`);
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case "accept":
|
||||
await request.accept();
|
||||
break;
|
||||
|
||||
case "start": {
|
||||
// Begin SAS (emoji) verification. The SAS emoji is delivered
|
||||
// through the matrix-verification node when it becomes ready.
|
||||
let verifier = request.verifier;
|
||||
if (!verifier) {
|
||||
verifier = await request.startVerification("m.sas.v1");
|
||||
}
|
||||
verifier.verify().catch(function(e) {
|
||||
node.warn("Verification ended: " + e);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "confirm": {
|
||||
const sas = node.server.verificationSas.get(msg.verificationId);
|
||||
if (!sas) {
|
||||
throw new Error("This verification has no SAS awaiting confirmation");
|
||||
}
|
||||
await sas.confirm();
|
||||
break;
|
||||
}
|
||||
|
||||
case "mismatch": {
|
||||
const sas = node.server.verificationSas.get(msg.verificationId);
|
||||
if (!sas) {
|
||||
throw new Error("This verification has no SAS awaiting confirmation");
|
||||
}
|
||||
sas.mismatch();
|
||||
break;
|
||||
}
|
||||
|
||||
case "cancel":
|
||||
await request.cancel();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Unknown verification action mode: " + mode);
|
||||
}
|
||||
|
||||
msg.verificationId = request.transactionId;
|
||||
node.send([msg, null]);
|
||||
} catch (e) {
|
||||
msg.error = String(e && e.message || e);
|
||||
node.error("Verification action failed: " + msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-verification-action", MatrixVerificationAction);
|
||||
}
|
||||
Reference in New Issue
Block a user