Compare commits

..

14 Commits

Author SHA1 Message Date
skylord123 54a9972bbc Merge pull request #121 from Skylar-Tech/dev
Release v0.9.0
2025-02-07 20:56:40 -07:00
skylord123 ad34f018ab Merge pull request #127 from koosc/allow-unknown
Add option for allowing unknown devices in rooms
2025-02-07 20:52:10 -07:00
skylord123 20345787d2 README.md change 2025-02-07 20:50:25 -07:00
skylord123 99c19923c6 Release v0.9.0 2025-02-07 20:27:36 -07:00
skylord123 093d59893e Fix roomId and eventId inputs not saving field type correctly for get-event node 2025-02-07 20:27:19 -07:00
skylord123 913f5dfcb9 - Upgrade to matrix-js-sdk 34.11.1 to fix CVE-2024-50336
- Remove request package (no longer needed)
2025-02-05 11:59:39 -07:00
skylord123 e0947dd3bc Merge pull request #128 from wuast94/master
Add m.notice to the receive node
2025-02-03 20:54:40 -07:00
skylord123 8287f3c08a Merge pull request #130 from LokiMidgard/patch-1
Support default plaintext in msg.format
2025-02-03 20:51:43 -07:00
Patrick Kranz 2a78524a90 use hasOwn instead of keys 2025-01-09 15:28:28 +01:00
Patrick Kranz d01838ac84 Fix error 2025-01-09 15:14:50 +01:00
Patrick Kranz 2059f8455d Update matrix-send-message.js 2025-01-09 15:12:09 +01:00
skylord123 0cb8ecf8aa Updated README with a link to a guide that explains how to register users via web browser 2025-01-04 12:58:08 -07:00
Marc 77f2c4be46 Add m.notice to the receive node 2025-01-01 05:17:12 +00:00
Chris Koos cf82daf5da Add option for allowing unknown devices
Allows workaround for sending messages until verification is implemented
2024-11-10 10:57:43 -08:00
11 changed files with 16331 additions and 6066 deletions
+6 -2
View File
@@ -5,6 +5,8 @@
Join our public Matrix room for help: [#node-red-contrib-matrix-chat:skylar.tech](https://app.element.io/#/room/#node-red-contrib-matrix-chat:skylar.tech)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/B0B51BM7C)
### Features
Supported functionality in this package includes:
@@ -59,7 +61,9 @@ Interested in helping? Contributions to finalize E2EE support are welcome!
This module includes a node to register users using the Synapse secret registration endpoint. It returns both an `access_token` and a `device_id`, perfect for setting up the bot.
[See how to register a user here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme).
[Guide on registering a user via the web browser](https://skylar.tech/matrix-chat-bot-module-for-node-red/)
[Guide on registering using shared secret registration](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) (for server owners)
### Other Packages
@@ -69,4 +73,4 @@ This module includes a node to register users using the Synapse secret registrat
We welcome all contributions! Please submit a pull request if you add a feature so the whole community can benefit.
**Sharing is caring!**
**Sharing is caring!**
+16271 -5413
View File
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.8.0",
"version": "0.9.0",
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"abort-controller": "^3.0.0",
@@ -9,12 +9,11 @@
"got": "^12.0.2",
"image-size": "^1.0.2",
"isomorphic-webcrypto": "^2.3.8",
"matrix-js-sdk": "^28.0.0",
"matrix-js-sdk": "34.11.1",
"mime": "^3.0.0",
"node-fetch": "^3.3.0",
"node-localstorage": "^2.2.1",
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
"request": "^2.88.2",
"sharp": "^0.33.4",
"tmp": "^0.2.1",
"utf8": "^3.0.0"
@@ -52,8 +51,7 @@
"matrix-whois-user": "src/matrix-whois-user.js",
"matrix-paginate-room": "src/matrix-paginate-room.js",
"matrix-get-event": "src/matrix-get-event.js",
"matrix-event-relations": "src/matrix-event-relations.js",
"matrix-device-verification": "src/matrix-device-verification.js"
"matrix-event-relations": "src/matrix-event-relations.js"
}
},
"engines": {
-240
View File
@@ -1,240 +0,0 @@
<script type="text/javascript">
let computeInputAndOutputCounts = function(node){
switch($("#node-input-mode").val()) {
default:
node.outputs = node.inputs = 0;
break;
case 'receive':
node.outputs = 1;
node.inputs = 0;
break;
case 'request':
case 'start':
case 'accept':
case 'cancel':
node.outputs = 2;
node.inputs = 1;
break;
}
};
RED.nodes.registerType('matrix-device-verification', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
inputs: 0,
outputs: 0,
outputLabels: ["success", "error"],
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
mode: { value: null, type: "text", required: true },
inputs: { value: 0 },
outputs: { value: 0 }
},
oneditprepare: function () {
computeInputAndOutputCounts(this);
},
oneditsave: function () {
computeInputAndOutputCounts(this);
},
label: function() {
if(this.name) {
return this.name;
}
switch(this.mode) {
default:
return 'Device Verification';
case 'receive':
return 'Receive Device Verification';
case 'request':
return 'Request Device Verification';
case 'start':
return 'Start Device Verification';
case 'accept':
return 'Accept Device Verification';
case 'cancel':
return 'Cancel Device Verification';
}
return this.name || "Device Verify Request";
},
paletteLabel: function(){
return "Device Verification";
}
});
</script>
<script type="text/html" data-template-name="matrix-device-verification">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-user"></i> Mode</label>
<select id="node-input-mode" style="width:70%;">
<option value="">Unconfigured</option>
<option value="receive">Receive Verification Request</option>
<option value="request">Request Verification</option>
<option value="start">Verification Start</option>
<option value="accept">Verification Accept</option>
<option value="cancel">Verification Cancel</option>
</select>
</div>
</script>
<script type="text/html" data-help-name="matrix-device-verification">
<h3>Details</h3>
<p>
Handle device verification. Check out the <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme" target="_blank">examples</a> page for a good understanding of how this works.
<br />
General flow:
<ol>
<li>Request/Receive device verification</li>
<li>Start Verification</li>
<li>Compare Emojis</li>
<li>Accept/Cancel Verification</li>
</ol>
<br />
THIS NODE IS IN BETA. There is a good chance that we will change how this node works later down the road. Make sure to read the release notes before upgrading.
</p>
<a href="https://matrix-org.github.io/synapse/develop/admin_api/room_membership.html#edit-room-membership-api" target="_blank">Synapse API Endpoint Information</a>
<h3>Inputs</h3>
<ul class="node-inputs">
<li><code>mode</code> set to '<strong>Receive Verification Request</strong>'
<div class="form-tips" style="margin-bottom: 12px;">
Doesn't take an input
</div>
</li>
<li><code>mode</code> set to '<strong>Request Verification</strong>'
<dl class="message-properties">
<dt>msg.userId <span class="property-type">string</span></dt>
<dd>
ID of the user to request device verification from
</dd>
</dl>
<dl class="message-properties">
<dt>msg.devices <span class="property-type">array[string]|null</span></dt>
<dd> list of <code>msg.userId</code>'s devices IDs to request verification from. If empty it will request from all known devices.</dd>
</dl>
</li>
<li><code>mode</code> set to '<strong>Verification Start</strong>'
<dl class="message-properties">
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
<dd>
Internal ID to reference the verification request throughout the flows
</dd>
</dl>
<dl class="message-properties">
<dt>msg.cancel <span class="property-type">bool</span></dt>
<dd>
If set and is true the verification request will be cancelled
</dd>
</dl>
</li>
<li><code>mode</code> set to '<strong>Verification Accept</strong>'
<dl class="message-properties">
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
<dd>
Internal ID to reference the verification request throughout the flows
</dd>
</dl>
</li>
<li><code>mode</code> set to '<strong>Verification Cancel</strong>'
<dl class="message-properties">
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
<dd>
Internal ID to reference the verification request throughout the flows
</dd>
</dl>
</li>
</ul>
<h3>Outputs</h3>
<ul class="node-outputs">
<li><code>mode</code> set to '<strong>Receive Verification Request</strong>' or '<strong>Request Verification</strong>'
<dl class="message-properties">
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
<dd>
Internal ID to reference the verification request throughout the flows
</dd>
</dl>
<dl class="message-properties">
<dt>msg.verifyMethods <span class="property-type">string</span></dt>
<dd>
Common verification methods supported by both sides
</dd>
</dl>
<dl class="message-properties">
<dt>msg.userId <span class="property-type">string</span></dt>
<dd>
ID of the user to request device verification from
</dd>
</dl>
<dl class="message-properties">
<dt>msg.deviceIds <span class="property-type">array[string]</span></dt>
<dd>
List of devices we are verifying
</dd>
</dl>
<dl class="message-properties">
<dt>msg.selfVerification <span class="property-type">bool</span></dt>
<dd>
true if we are verifying one of our own devices
</dd>
</dl>
<dl class="message-properties">
<dt>msg.phase <span class="property-type">string</span></dt>
<dd>
what phase of verification we are in
</dd>
</dl>
</li>
<li><code>mode</code> set to '<strong>Verification Start</strong>'
<dl class="message-properties">
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>
sas verification payload
</dd>
</dl>
<dl class="message-properties">
<dt>msg.emojis <span class="property-type">array[string]</span></dt>
<dd>
array of emojis for verification request
</dd>
</dl>
<dl class="message-properties">
<dt>msg.emojis_text <span class="property-type">array[string]</span></dt>
<dd>
array of emojis in text form for verification request
</dd>
</dl>
</li>
<li><code>mode</code> set to '<strong>Verification Accept</strong>' or '<strong>Verification Cancel</strong>'
<div class="form-tips" style="margin-bottom: 12px;">
Passes input straight to output on success. If an error occurs it goes to the second output.
</div>
</li>
</ul>
</script>
-267
View File
@@ -1,267 +0,0 @@
const {Phase} = require("matrix-js-sdk/lib/crypto/verification/request/VerificationRequest");
const {CryptoEvent} = require("matrix-js-sdk/lib/crypto");
module.exports = function(RED) {
const verificationRequests = new Map();
function MatrixDeviceVerification(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.mode = n.mode;
if (!node.server) {
node.warn("No configuration node");
return;
}
if(!node.server.e2ee) {
node.error("End-to-end encryption needs to be enabled to use this.");
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
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" });
});
function getKeyByValue(object, value) {
return Object.keys(object).find(key => object[key] === value);
}
switch(node.mode) {
default:
node.error("Node not configured with a mode");
break;
case 'request':
node.on('input', async function(msg){
if(!msg.userId) {
node.error("msg.userId is required for start verification mode");
}
node.server.matrixClient.requestDeviceVerification(msg.userId, msg.devices || undefined)
.then(function(e) {
node.log("Successfully requested verification", e);
let verifyRequestId = msg.userId + ':' + e.channel.deviceId;
verificationRequests.set(verifyRequestId, e);
node.send({
verifyRequestId: verifyRequestId, // internally used to reference between nodes
verifyMethods: e.methods,
userId: msg.userId,
deviceIds: e.channel.devices,
selfVerification: e.isSelfVerification,
phase: getKeyByValue(Phase, e.phase)
});
})
.catch(function(e){
node.warn("Error requesting device verification: " + e);
msg.error = e;
node.send([null, msg]);
});
});
break;
case 'receive':
/**
* Fires when a key verification is requested.
* @event module:client~MatrixClient#"crypto.verification.request"
* @param {object} data
* @param {MatrixEvent} data.event the original verification request message
* @param {Array} data.methods the verification methods that can be used
* @param {Number} data.timeout the amount of milliseconds that should be waited
* before cancelling the request automatically.
* @param {Function} data.beginKeyVerification a function to call if a key
* verification should be performed. The function takes one argument: the
* name of the key verification method (taken from data.methods) to use.
* @param {Function} data.cancel a function to call if the key verification is
* rejected.
*/
node.server.matrixClient.on(CryptoEvent.VerificationRequestReceived, async function(data){
if(data.phase === Phase.Cancelled || data.phase === Phase.Done) {
return;
}
if(data.requested || true) {
let verifyRequestId = data.targetDevice.userId + ':' + data.targetDevice.deviceId;
verificationRequests.set(verifyRequestId, data);
node.send({
verifyRequestId: verifyRequestId, // internally used to reference between nodes
verifyMethods: data.methods,
userId: data.targetDevice.userId,
deviceId: data.targetDevice.deviceId,
selfVerification: data.isSelfVerification,
phase: getKeyByValue(Phase, data.phase)
});
}
});
node.on('close', function(done) {
// clear verification requests
verificationRequests.clear();
done();
});
break;
case 'start':
node.on('input', async function(msg){
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
node.error("invalid verification request (invalid msg.verifyRequestId): " + (msg.verifyRequestId || null));
}
var data = verificationRequests.get(msg.verifyRequestId);
if(msg.cancel) {
await data.verifier.cancel();
verificationRequests.delete(msg.verifyRequestId);
} else {
try {
data.on('change', async function() {
// VerificationPhase {
// /** Initial state: no event yet exchanged */
// Unsent = 1,
//
// /** An `m.key.verification.request` event has been sent or received */
// Requested = 2,
//
// /** An `m.key.verification.ready` event has been sent or received, indicating the verification request is accepted. */
// Ready = 3,
//
// /** An `m.key.verification.start` event has been sent or received, choosing a verification method */
// Started = 4,
//
// /** An `m.key.verification.cancel` event has been sent or received at any time before the `done` event, cancelling the verification request */
// Cancelled = 5,
//
// /** An `m.key.verification.done` event has been **sent**, completing the verification request. */
// Done = 6,
// }
console.log("[Verification Start] VERIFIER EVENT CHANGE", this.phase);
var that = this;
if(this.phase === Phase.Started) {
console.log("[Verification Start] VERIFIER EVENT PHASE STARTED");
let verifierCancel = function(){
let verifyRequestId = that.targetDevice.userId + ':' + that.targetDevice.deviceId;
if(verificationRequests.has(verifyRequestId)) {
verificationRequests.delete(verifyRequestId);
}
};
data.verifier.on('cancel', function(e){
node.warn("Device verification cancelled " + e);
console.log(JSON.stringify(e.value));
verifierCancel();
});
const sasEventPromise = new Promise(resolve =>
data.verifier.once("show_sas", resolve)
);
console.log("[Verification Start] Starting verification");
data.verifier.verify()
.then(function() {
console.log("[Verification Start] verify() success");
}).catch(function(e) {
console.log("[Verification Start] verify() error", e);
msg.error = e;
node.send([null, msg]);
});
console.log("[Verification Start] WAITING FOR SHOW SAS EVENT");
const sasEvent = await sasEventPromise;
console.log("SHOW SAS", sasEvent);
// e = {
// sas: {
// decimal: [ 8641, 3153, 2357 ],
// emoji: [
// [Array], [Array],
// [Array], [Array],
// [Array], [Array],
// [Array]
// ]
// },
// confirm: [AsyncFunction: confirm],
// cancel: [Function: cancel],
// mismatch: [Function: mismatch]
// }
msg.payload = sasEvent.sas;
msg.emojis = sasEvent.sas.emoji.map(function(emoji, i) {
return emoji[0];
});
msg.emojis_text = sasEvent.sas.emoji.map(function(emoji, i) {
return emoji[1];
});
node.send(msg);
// sasEvent.mismatch();
}
});
console.log("[Verification Start] Starting verification");
try {
console.log("[Verification Start] Accepting..");
await data.accept();
console.log(`[Verification] beginKeyVerification (methods=${data.methods[0]}, targetDevice=${data.targetDevice})`);
await data.beginKeyVerification(
data.methods[0],
data.targetDevice
);
} catch(e) {
console.log("[Verification Start] VERIFICATION ERROR", e);
}
} catch(e) {
console.log("ERROR", e);
}
}
});
break;
case 'cancel':
node.on('input', async function(msg){
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
node.error("Invalid verification request: " + (msg.verifyRequestId || null));
}
var data = verificationRequests.get(msg.verifyRequestId);
if(data) {
data.cancel()
.then(function(e){
node.send([msg, null]);
})
.catch(function(e) {
msg.error = e;
node.send([null, msg]);
});
}
});
break;
case 'accept':
node.on('input', async function(msg){
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
node.error("Invalid verification request: " + (msg.verifyRequestId || null));
}
var data = verificationRequests.get(msg.verifyRequestId);
if(data.verifier && data.verifier.sasEvent) {
try {
await data.verifier.sasEvent.confirm();
node.send([msg, null]);
} catch(e) {
msg.error = e;
node.send([null, msg]);
}
} else {
node.error("Verification must be started");
}
});
break;
}
}
RED.nodes.registerType("matrix-device-verification", MatrixDeviceVerification);
}
+10 -18
View File
@@ -19,14 +19,18 @@
},
oneditprepare: function() {
$("#node-input-roomId").typedInput({
type: this.roomIdType,
types:['msg','flow','global','str'],
}).typedInput('value', this.roomIdValue);
types: ['msg','flow','global','str'],
typeField: "#node-input-roomId"
});
$("#node-input-roomId").typedInput("type", this.roomIdType || "msg");
$("#node-input-roomId").typedInput("value", this.roomIdValue || "topic");
$("#node-input-eventId").typedInput({
type: this.eventIdType,
types:['msg','flow','global','str'],
}).typedInput('value', this.eventIdValue);
types: ['msg','flow','global','str'],
typeField: "#node-input-eventId"
});
$("#node-input-eventId").typedInput("type", this.eventIdType || "msg");
$("#node-input-eventId").typedInput("value", this.eventIdValue || "eventId");
},
oneditsave: function() {
this.roomIdType = $("#node-input-roomId").typedInput('type');
@@ -58,18 +62,6 @@
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
<input type="text" id="node-input-eventId">
</div>
<script type="text/javascript">
$(function(){
$("#node-input-roomId").on("keyup", function() {
if($(this).val() && !$(this).val().startsWith("!")) {
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
} else {
$("#node-input-roomId-error").hide();
}
}).trigger('keyup');
});
</script>
</script>
<script type="text/html" data-help-name="matrix-get-event">
+11
View File
@@ -13,6 +13,7 @@
acceptOwnEvents: {"value": false},
acceptText: {"value": true},
acceptEmotes: {"value": true},
acceptNotices: {"value": true},
acceptStickers: {"value": true},
acceptReactions: {"value": true},
acceptFiles: {"value": true},
@@ -66,6 +67,16 @@
Accept text <code style="text-transform: none;">m.text</code>
</label>
</div>
<div class="form-row" style="margin-bottom:0;">
<input
type="checkbox"
id="node-input-acceptNotices"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptNotices" style="width: auto">
Accept notices <code style="text-transform: none;">m.notice</code>
</label>
</div>
<div class="form-row" style="margin-bottom:0;">
<input
type="checkbox"
+5
View File
@@ -10,6 +10,7 @@ module.exports = function(RED) {
this.acceptOwnEvents = n.acceptOwnEvents;
this.acceptText = n.acceptText;
this.acceptEmotes = n.acceptEmotes;
this.acceptNotices = n.acceptNotices;
this.acceptStickers = n.acceptStickers;
this.acceptReactions = n.acceptReactions;
this.acceptFiles = n.acceptFiles;
@@ -69,6 +70,10 @@ module.exports = function(RED) {
if (!node.acceptEmotes) return;
break;
case 'm.notice':
if (!node.acceptNotices) return;
break;
case 'm.text':
if (!node.acceptText) return;
break;
+2 -2
View File
@@ -127,7 +127,7 @@ module.exports = function(RED) {
}
if(msgFormat === 'msg.format') {
if(!msg.format) {
if(!Object.hasOwn(msg, 'format')) {
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
return;
}
@@ -202,4 +202,4 @@ module.exports = function(RED) {
});
}
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
}
}
+16 -1
View File
@@ -36,7 +36,8 @@
name: { value: null },
autoAcceptRoomInvites: { value: true },
enableE2ee: { type: "checkbox", value: true },
global: { type: "checkbox", value: true }
global: { type: "checkbox", value: true },
allowUnknownDevices: { type: "checkbox", value: false }
},
icon: "matrix.png",
label: function() {
@@ -130,6 +131,20 @@
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
</div>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-allowUnknownDevices"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-allowUnknownDevices" style="width: auto">
Allow unverified devices in rooms
</label>
<div class="form-tips" style="margin-bottom: 12px;">
Allow sending messages to a room with unknown devices which have not been verified.
</div>
</div>
<script type="text/javascript">
$("#matrix-login-btn").on("click", function() {
function prettyPrintJson(json) {
+7 -118
View File
@@ -7,7 +7,6 @@ const { resolve } = require('path');
const { LocalStorage } = require('node-localstorage');
const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
const {RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore} = require("matrix-js-sdk");
const request = require("request");
require("abort-controller/polyfill"); // polyfill abort-controller if we don't have it
if (!globalThis.fetch) {
// polyfill fetch if we don't have it
@@ -52,11 +51,11 @@ module.exports = function(RED) {
this.userId = this.credentials.userId;
this.deviceLabel = this.credentials.deviceLabel || null;
this.deviceId = this.credentials.deviceId || null;
this.secretStoragePassphrase = null;
this.url = this.credentials.url;
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
this.e2ee = n.enableE2ee || false;
this.globalAccess = n.global;
this.allowUnknownDevices = n.allowUnknownDevices || false;
this.initializedAt = new Date();
node.initialSyncLimit = 25;
@@ -67,39 +66,12 @@ module.exports = function(RED) {
node.deregister = function(consumerNode) {
delete node.users[consumerNode.id];
};
if(!this.userId) {
node.log("Matrix connection failed: missing user ID in configuration.");
return;
}
let cryptoCallbacks = undefined;
if(node.e2ee) {
cryptoCallbacks = {
getSecretStorageKey: async ({ keys }) => {
return null; // we don't do secret storage right now
const backupPassphrase = node.secretStoragePassphrase;
if (!backupPassphrase) {
node.WARN("Missing secret storage key");
return null;
}
let keyId = await node.matrixClient.getDefaultSecretStorageKeyId();
if (keyId && !keys[keyId]) {
keyId = undefined;
}
if (!keyId) {
keyId = keys[0][0];
}
const backupInfo = await node.matrixClient.getKeyBackupVersion();
const key = await node.matrixClient.keyBackupKeyFromPassword(
backupPassphrase,
backupInfo
);
return [keyId, key];
},
}
}
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId),
localStorage = new LocalStorage(localStorageDir),
initialSetup = false;
@@ -111,61 +83,6 @@ module.exports = function(RED) {
} else if(!this.url) {
node.error("Matrix connection failed: missing server URL in configuration.", {});
} else {
/**
* Ensures secret storage and cross signing are ready for use. Does not
* support initial setup of secret storage. If the backup passphrase is not
* set, this is a no-op, else it is cleared once the operation is complete.
*
* @returns {Promise<void>}
*/
async function bootstrapSSSS() {
if (!node.matrixClient) {
// client startup will do bootstrapping
return;
}
const password = "testphrase";
if (!password) {
// We do not support setting up secret storage, so we need a passphrase
// to bootstrap.
return;
}
const backupInfo = await node.matrixClient.getKeyBackupVersion();
await node.matrixClient.getCrypto().bootstrapSecretStorage({
setupNewKeyBackup: false,
async getKeyBackupPassphrase() {
const key = await node.matrixClient.keyBackupKeyFromPassword(
password,
backupInfo
);
return key;
},
});
await node.matrixClient.getCrypto().bootstrapCrossSigning({
authUploadDeviceSigningKeys(makeRequest) {
console.log("authUploadDeviceSigningKeys");
makeRequest({
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": node.matrixClient.getUserId()
},
"password": "examplepass",
"session": node.matrixClient.getSessionId()
});
return Promise.resolve();
},
});
await node.matrixClient.checkOwnCrossSigningTrust();
if (backupInfo) {
await node.matrixClient.restoreKeyBackupWithSecretStorage(backupInfo);
}
// Clear passphrase once bootstrap was successful
// this.imAccount.setString("backupPassphrase", "");
// this.imAccount.save();
// this._encryptionError = "";
// await this.updateEncryptionStatus();
}
node.setConnected = async function(connected, cb) {
if (node.connected !== connected) {
node.connected = connected;
@@ -177,19 +94,11 @@ module.exports = function(RED) {
node.log("Matrix server connection ready.");
node.emit("connected");
if(!initialSetup) {
console.log("INITIAL SETUP", await node.matrixClient.getCrypto().getCrossSigningStatus());
if(node.e2ee && !await node.matrixClient.getCrypto().isCrossSigningReady()) {
// bootstrap cross-signing
await bootstrapSSSS();
let crossSigningStatus = node.matrixClient.getCrypto().getCrossSigningStatus();
console.log("crossSigningStatus", crossSigningStatus);
}
// store Device ID internally
let stored_device_id = getStoredDeviceId(localStorage),
device_id = this.matrixClient.getDeviceId();
if(!device_id && node.e2ee) {
if(!device_id && node.enableE2ee) {
node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.", {})
} else {
if(!stored_device_id || stored_device_id !== device_id) {
@@ -236,17 +145,6 @@ module.exports = function(RED) {
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
upgradeDirectoryIfNecessary(node, storageDir);
// taken from https://github.com/matrix-org/matrix-react-sdk/blob/d9d0ab3d98dea8f260bd7037482c3c8cf288ae82/cypress/support/bot.ts
// these next lines are to fix "Device verification cancelled Error: No getCrossSigningKey callback supplied" error
const privateKeys = {};
const getCrossSigningKey = (type) => {
return privateKeys[type];
};
const saveCrossSigningKeys = (k) => {
Object.assign(privateKeys, k);
};
node.matrixClient = sdk.createClient({
baseUrl: this.url,
accessToken: this.credentials.accessToken,
@@ -255,15 +153,8 @@ module.exports = function(RED) {
localStorage: localStorage,
}),
userId: this.userId,
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
request,
verificationMethods: ["m.sas.v1"],
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
});
node.matrixClient.on("crypto.keyBackupStatus", function() {
console.log("crypto.keyBackupStatus");
bootstrapSSSS();
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined
// verificationMethods: ["m.sas.v1"]
});
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
@@ -508,9 +399,8 @@ module.exports = function(RED) {
if(node.e2ee){
node.log("Initializing crypto...");
await node.matrixClient.initCrypto();
node.log("Bootstrapping SSSS...");
await bootstrapSSSS();
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
node.matrixClient.getCrypto().globalErrorOnUnknownDevices = !node.allowUnknownDevices;
}
node.log("Connecting to Matrix server...");
await node.matrixClient.startClient({
@@ -589,8 +479,7 @@ module.exports = function(RED) {
baseUrl: baseUrl,
deviceId: deviceId,
timelineSupport: true,
localTimeoutMs: '30000',
request
localTimeoutMs: '30000'
});
new TimelineWindow()