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,113 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixVerification(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
|
||||
// Phase filter - emit only the ticked phases. Undefined (config saved
|
||||
// before these options existed) is treated as ticked, so old nodes
|
||||
// keep emitting every phase.
|
||||
this.phases = {
|
||||
requested: n.phaseRequested !== false,
|
||||
ready: n.phaseReady !== false,
|
||||
started: n.phaseStarted !== false,
|
||||
sas: n.phaseSas !== false,
|
||||
done: n.phaseDone !== false,
|
||||
cancelled: n.phaseCancelled !== false,
|
||||
};
|
||||
this.initiatedBy = n.initiatedBy || 'any'; // any | me | notme
|
||||
this.verificationType = n.verificationType || 'any'; // any | room | device
|
||||
this.selfVerification = n.selfVerification || 'any'; // any | self | others
|
||||
this.userFilter = (n.userFilter || '').split(',')
|
||||
.map(function(s){ return s.trim().toLowerCase(); })
|
||||
.filter(Boolean);
|
||||
this.roomFilter = (n.roomFilter || '').split(',')
|
||||
.map(function(s){ return s.trim(); })
|
||||
.filter(Boolean);
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
if (!node.server) {
|
||||
node.error("No configuration node");
|
||||
return;
|
||||
}
|
||||
node.server.register(node);
|
||||
|
||||
// Returns true if a verification update message passes every configured
|
||||
// filter. All filters AND-combine; each defaults to "pass everything".
|
||||
function passesFilters(m) {
|
||||
// phase
|
||||
if ((m.phase in node.phases) && !node.phases[m.phase]) {
|
||||
return false;
|
||||
}
|
||||
// initiated by
|
||||
if (node.initiatedBy === 'me' && !m.initiatedByMe) {
|
||||
return false;
|
||||
}
|
||||
if (node.initiatedBy === 'notme' && m.initiatedByMe) {
|
||||
return false;
|
||||
}
|
||||
// verification type - room verifications carry a roomId (msg.topic),
|
||||
// to-device verifications do not
|
||||
if (node.verificationType === 'room' && !m.topic) {
|
||||
return false;
|
||||
}
|
||||
if (node.verificationType === 'device' && m.topic) {
|
||||
return false;
|
||||
}
|
||||
// self-verification (the other party is one of the bot's own devices)
|
||||
if (node.selfVerification === 'self' && !m.isSelfVerification) {
|
||||
return false;
|
||||
}
|
||||
if (node.selfVerification === 'others' && m.isSelfVerification) {
|
||||
return false;
|
||||
}
|
||||
// user id allowlist
|
||||
if (node.userFilter.length &&
|
||||
(!m.userId || node.userFilter.indexOf(m.userId.toLowerCase()) === -1)) {
|
||||
return false;
|
||||
}
|
||||
// room id filter - only constrains room verifications; device
|
||||
// verifications have no room and are not affected
|
||||
if (node.roomFilter.length && m.topic &&
|
||||
node.roomFilter.indexOf(m.topic) === -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const onConnected = function() {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
};
|
||||
const onDisconnected = function() {
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
};
|
||||
const onVerificationUpdate = function(verificationMsg) {
|
||||
if (!passesFilters(verificationMsg)) {
|
||||
return;
|
||||
}
|
||||
node.status({ fill: "blue", shape: "dot", text: verificationMsg.phase });
|
||||
// clone so multiple verification nodes don't share/mutate one object
|
||||
node.send(RED.util.cloneMessage(verificationMsg));
|
||||
};
|
||||
|
||||
node.server.on("connected", onConnected);
|
||||
node.server.on("disconnected", onDisconnected);
|
||||
node.server.on("Verification.update", onVerificationUpdate);
|
||||
|
||||
if (node.server.isConnected && node.server.isConnected()) {
|
||||
onConnected();
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.removeListener("connected", onConnected);
|
||||
node.server.removeListener("disconnected", onDisconnected);
|
||||
node.server.removeListener("Verification.update", onVerificationUpdate);
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-verification", MatrixVerification);
|
||||
}
|
||||
Reference in New Issue
Block a user