21 Commits

Author SHA1 Message Date
Skylar Sadlier
ce8be4a30f Update readme 2022-12-06 22:10:26 -07:00
Skylar Sadlier
3e808cabec - Set version to 0.7.0 2022-12-06 21:53:30 -07:00
Skylar Sadlier
2fdc7482ce - Add back in persistent storage
- Receive node now accepts video files
2022-12-06 21:49:05 -07:00
Skylar Sadlier
c7f9d56df2 - Updated code for new authedRequest argument format
- matrix-js-sdk updated to 22.0.0
- fs-extra updated to 11.1.0
- minimum version set to 14.14
2022-12-06 18:47:41 -07:00
Skylar Sadlier
3c042ae47d - rename "Invite Room" node to "Invite to Room" to distinguish from new node
- new node "Invite Room" for receiving room invites
- new node "Leave Room" for leaving a room
- updated examples with new nodes
2022-12-05 23:40:05 -07:00
Skylar Sadlier
0a34870fa3 Merge pull request #71 from jacotec/master
Pass invites to the receive object
2022-12-05 21:20:11 -07:00
Skylar Sadlier
768a1c8ce0 Merge pull request #73 from WesleyE/master
Verify login on the relative matrix-chat/login URI
2022-12-05 21:19:03 -07:00
Skylar Sadlier
22dd9b4ca3 - matrix-js-sdk updated to v21.2.0
- added node-fetch polyfill for older nodejs versions
- added abort-controller polyfill for older nodejs versions
2022-12-05 21:18:16 -07:00
Skylar Sadlier
462f9670c2 - matrix-js-sdk updated to v20.0.0
- olm updated to v3.2.13 https://gitlab.matrix.org/matrix-org/olm/-/packages/136
- console messages now only appear if Node-RED's settings.js `logging.console.level` is info, debug, or trace
2022-12-05 19:49:55 -07:00
Wesley Elfring
e4b01c40c2 Verify login on the relative matrix-chat/login URI 2022-11-30 13:11:33 +01:00
Marco Jakobs
908d60835d Pass invites to the receive object
This commit passes a room invite to the receive node, so you can react to it.

Sample invitation:

HDsvcHGiTYFxxxxxxx:mydomain.de : msg : Object
object
type: "r.invite"
payload: "Invitation"
userId: "@relay:mydomain.de"
topic: "!HDsvcHGiTYFxxxxxxx:mydomain.de"
_msgid: "ef1fd7f3c8ae82e6"
2022-11-25 20:56:06 +01:00
Skylar Sadlier
4c17a21008 Merge pull request #63 from Skylar-Tech/dev
Version 0.6.1
2022-04-19 13:03:47 -06:00
Skylar Sadlier
bd4f6ea486 Update version to 0.6.1
- Fix: Error thrown in folder migration if new and old path are same
2022-04-19 13:01:00 -06:00
Skylar Sadlier
5ef0b6a11f Fix: Error thrown in folder migration if new and old path are same 2022-04-19 12:58:52 -06:00
Skylar Sadlier
97f27e61c6 Merge pull request #62 from Skylar-Tech/61-audio-messages
61 audio messages
2022-03-28 00:45:22 -06:00
Skylar Sadlier
7bdadc0fe9 Closes #209
- Can now receive audio files
- Can now receive locations
- Readme updated
2022-03-28 00:42:52 -06:00
Skylar Sadlier
5f129560aa Couple of little changes:
- Fix matrix-receive message types displaying in uppercase when it should be lowercase
- Readme updated with some extra things that are now possible since last release
- Link to matrix.org project in our README.md
2022-03-28 00:42:19 -06:00
Skylar Sadlier
00bc14e1c7 Merge pull request #59 from Skylar-Tech/dev
Version 0.5.8
2022-03-18 13:50:28 -06:00
Skylar Sadlier
9f41b67174 Version 0.5.8 2022-03-18 13:49:38 -06:00
Skylar Sadlier
4e93b7253e Revert examples 2022-03-18 13:37:24 -06:00
Skylar Sadlier
ecb4427217 - Revert device verification/e2ee stuff. It's not ready. 2022-03-18 13:34:46 -06:00
28 changed files with 9735 additions and 5487 deletions

View File

@@ -1,5 +1,5 @@
# node-red-contrib-matrix-chat
Matrix chat server client for [Node-RED](https://nodered.org/)
[Matrix](https://matrix.org/) chat server client for [Node-RED](https://nodered.org/)
***Currently we are in beta. We ask that you open any issues you have on our repository to help us reach a stable well tested version. Things may change & break before our first release so check changelog before updating.***
@@ -11,8 +11,10 @@ The following is supported from this package:
- End-to-end encryption
- [Currently a WIP](#end-to-end-encryption-notes)
- Receive events from a room (messages, reactions, images, and files) whether encrypted or not
- Receive events from a room (messages, reactions, images, audio, locations, and files) whether encrypted or not
- Send Images/Files (sending files to e2ee room doesn't currently encrypt them yet)
- Edit messages
- Delete events (messages, reactions, etc)
- Decrypt files in e2ee rooms
- Send HTML/Plain Text Message/Notice
- React to messages
@@ -24,9 +26,7 @@ The following is supported from this package:
- Get a user list from a room
- Kick user from room
- Ban user from room
- Join a room
- Create a room
- Invite to a room
- Join, Create, Invite, and Leave rooms
- Synapse admin API to force add user to room (requires bot to be in same room already)
@@ -48,9 +48,7 @@ You are not limited by just the nodes we have created. If you turn on global acc
View an example [here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command)
### End-to-End Encryption Notes
It is recommended you use the bot exclusively with Node-RED after it's creation if using e2ee. Failure to do so will lead to your bot being unable to receive messages from e2ee rooms it joined from another client. Shared secret registration makes this super easy since it returns a token and device ID.
We now have a device verification node that will help in sharing keys (check the [examples](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) for more info). This node is currently in beta and is still experimental.
Currently, this module has no way of getting encryption keys from other devices on the same account. Therefore it is recommended you use the bot exclusively with Node-RED after it's creation. Failure to do so will lead to your bot being unable to receive messages from e2ee rooms it joined from another client. Shared secret registration makes this super easy since it returns a token and device ID.
This module stores a folder in your Node-RED directory called `matrix-client-storage` and is it vital that you periodically back this up if you are using e2ee. This is where the client stores all the keys necessary to decrypt messages and if lost you will lose access to e2e rooms. If you move your client to another NR install make sure to migrate this folder as well (and do not let both the old and new client run at same time).

View File

@@ -10,13 +10,13 @@ Build something cool with these nodes? Feel free to submit a pull request to sha
- [Create User with Shared Secret Registration](#create-user-with-shared-secret-registration)
- [Create/Edit Synapse User](#createedit-synapse-user)
- [Use function node to run any command](#use-function-node-to-run-any-command)
- [Start and accept device verification from specific user](#start-and-accept-device-verification-from-specific-user)
- [Request device verification & immediately accept](#request-device-verification--immediately-accept)
- [Respond to "ping" with "pong"](#respond-to-ping-with-pong)
- [Respond to "html" with an HTML message](#respond-to-html-with-an-html-message)
- [Respond to "image" with an uploaded image](#respond-to-image-with-an-uploaded-image)
- [Respond to "file" with an uploaded file](#respond-to-file-with-an-uploaded-file)
- [Respond to "react" with a reaction](#respond-to-react-with-a-reaction)
- [Accept room invites from specific user](#accept-room-invites-from-specific-user)
- [Leave room when someone says "bye"](#leave-room-when-someone-says-bye)
- [Remove messages containing "delete"](#remove-messages-containing-delete)
- [Respond to "users" with full list of server users](#respond-to-users-with-full-list-of-server-users)
- [Respond to "newroom" by creating new room and inviting user](#respond-to-newroom-by-creating-new-room-and-inviting-user)
@@ -64,24 +64,6 @@ To view what sort of functions you have access to check out the `client.ts` file
![custom-redact-function-node.png](custom-redact-function-node.png)
### Request device verification & immediately accept
[View JSON](request-device-verification.json)
Edit the inject node to match the details of a user & device you would like to request verification from.
After the end user starts verification the bot automatically accepts the result (note: you should be validating the result and not just blindly accepting them, this is just an example)
![add-user-with-admin-user.png](request-device-verification.png)
### Start and accept device verification from specific user
[View JSON](start-accept-verification-from-user.json)
Edit the switch node labeled "is from me" to match whatever user ID you would like to accept verification requests from.
After verification starts the bot automatically accepts the result (note: you should be validating the result and not just blindly accepting them, this is just an example)
![add-user-with-admin-user.png](start-accept-verification-from-user.png)
### Respond to "ping" with "pong"
@@ -133,6 +115,26 @@ Give a 👍 reaction when someone says "react"
### Accept room invites from specific user
[View JSON](accept-room-invites.json)
Accept room invites from specific user.
![accept-room-invites.png](accept-room-invites.png)
### Leave room when someone says bye
[View JSON](leave-room-bye.json)
Leave room when someone says "bye".
![leave-room-bye.png](leave-room-bye.png)
### Remove messages containing "delete"
[View JSON](delete-event.json)

View File

@@ -0,0 +1,94 @@
[
{
"id": "64f76474ff7a3727",
"type": "matrix-room-invite",
"z": "f025a8b9fbd1b054",
"name": "",
"server": "8da0ef83f77f8e24",
"roomId": null,
"x": 270,
"y": 2380,
"wires": [
[
"22f6056fa5bc5bd0"
]
]
},
{
"id": "6d6f304a0a6342b8",
"type": "matrix-join-room",
"z": "f025a8b9fbd1b054",
"name": "",
"server": "8da0ef83f77f8e24",
"x": 770,
"y": 2380,
"wires": [
[
"1409ebb4a0e65663"
],
[
"1409ebb4a0e65663"
]
]
},
{
"id": "22f6056fa5bc5bd0",
"type": "switch",
"z": "f025a8b9fbd1b054",
"name": "msg.userId == @skylord123:skylar.tech",
"property": "userId",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "@skylord123:skylar.tech",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 520,
"y": 2380,
"wires": [
[
"6d6f304a0a6342b8"
]
]
},
{
"id": "043f74e6d76b1eb0",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "Only accept room invites from specific user",
"info": "",
"x": 360,
"y": 2340,
"wires": []
},
{
"id": "1409ebb4a0e65663",
"type": "debug",
"z": "f025a8b9fbd1b054",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 910,
"y": 2380,
"wires": []
},
{
"id": "8da0ef83f77f8e24",
"type": "matrix-server-config",
"name": null,
"autoAcceptRoomInvites": false,
"enableE2ee": true,
"global": true
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,103 @@
[
{
"id": "997c354038202dba",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "Leave room when someone says \"bye\"",
"info": "",
"x": 350,
"y": 2520,
"wires": []
},
{
"id": "69c4ea189be94feb",
"type": "matrix-receive",
"z": "f025a8b9fbd1b054",
"name": "",
"server": "8da0ef83f77f8e24",
"roomId": "",
"acceptText": true,
"acceptEmotes": true,
"acceptStickers": true,
"acceptReactions": true,
"acceptFiles": true,
"acceptAudio": true,
"acceptImages": true,
"acceptLocations": true,
"x": 280,
"y": 2560,
"wires": [
[
"19e1d64b63ae8a1f"
]
]
},
{
"id": "19e1d64b63ae8a1f",
"type": "switch",
"z": "f025a8b9fbd1b054",
"name": "msg.payload == bye",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "bye",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 480,
"y": 2560,
"wires": [
[
"db0e51f8e7793f92"
]
]
},
{
"id": "db0e51f8e7793f92",
"type": "matrix-leave-room",
"z": "f025a8b9fbd1b054",
"name": "",
"server": "8da0ef83f77f8e24",
"roomId": null,
"x": 670,
"y": 2560,
"wires": [
[
"3791f551bf0e4fc4"
],
[
"3791f551bf0e4fc4"
]
]
},
{
"id": "3791f551bf0e4fc4",
"type": "debug",
"z": "f025a8b9fbd1b054",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 810,
"y": 2560,
"wires": []
},
{
"id": "8da0ef83f77f8e24",
"type": "matrix-server-config",
"name": null,
"autoAcceptRoomInvites": false,
"enableE2ee": true,
"global": true
}
]

BIN
examples/leave-room-bye.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,92 +0,0 @@
[
{
"id": "9345e8c42e327dba",
"type": "matrix-device-verification",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"mode": "request",
"inputs": 1,
"outputs": 2,
"x": 480,
"y": 1660,
"wires": [
[
"b676082d56430aec"
],
[]
]
},
{
"id": "b676082d56430aec",
"type": "matrix-device-verification",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"mode": "start",
"inputs": 1,
"outputs": 1,
"x": 740,
"y": 1660,
"wires": [
[
"23a0225f2f2615a3"
]
]
},
{
"id": "23a0225f2f2615a3",
"type": "matrix-device-verification",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"mode": "accept",
"inputs": 1,
"outputs": 1,
"x": 970,
"y": 1660,
"wires": [
[]
]
},
{
"id": "3eced60b58c999eb",
"type": "inject",
"z": "f025a8b9fbd1b054",
"name": "",
"props": [
{
"p": "userId",
"v": "@bot:example.com",
"vt": "str"
},
{
"p": "devices",
"v": "[\"ZRRJKASJDUK\"]",
"vt": "json"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 290,
"y": 1660,
"wires": [
[
"9345e8c42e327dba"
]
]
},
{
"id": "f58ceba2a8819c09",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "Request verification from a specific userId and device",
"info": "",
"x": 440,
"y": 1620,
"wires": []
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,86 +0,0 @@
[
{
"id": "5073ca88b21abfb4",
"type": "matrix-device-verification",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"mode": "receive",
"inputs": 0,
"outputs": 1,
"x": 350,
"y": 1540,
"wires": [
[
"b76c1d185c2793a0"
]
]
},
{
"id": "05947740ced04e2c",
"type": "matrix-device-verification",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"mode": "start",
"inputs": 1,
"outputs": 1,
"x": 740,
"y": 1540,
"wires": [
[
"b3158c0779b72b41"
]
]
},
{
"id": "b76c1d185c2793a0",
"type": "switch",
"z": "f025a8b9fbd1b054",
"name": "is from me",
"property": "userId",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "@skylord123:skylar.tech",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 550,
"y": 1540,
"wires": [
[
"05947740ced04e2c"
]
]
},
{
"id": "b3158c0779b72b41",
"type": "matrix-device-verification",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"mode": "accept",
"inputs": 1,
"outputs": 1,
"x": 970,
"y": 1540,
"wires": [
[]
]
},
{
"id": "eb8ba0741df1b365",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "Accept all device validation from a user",
"info": "",
"x": 390,
"y": 1500,
"wires": []
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

13782
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,17 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.5.6",
"version": "0.7.0",
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"fs-extra": "^10.0.1",
"abort-controller": "^3.0.0",
"fs-extra": "^11.1.0",
"got": "^12.0.2",
"isomorphic-webcrypto": "^2.3.8",
"matrix-js-sdk": "^16.0.0",
"matrix-js-sdk": "^22.0.0",
"node-fetch": "^3.3.0",
"node-localstorage": "^2.2.1",
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/271/download",
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/472/download",
"request": "^2.88.2",
"utf8": "^3.0.0"
},
"node-red": {
@@ -23,11 +26,12 @@
"matrix-react": "src/matrix-react.js",
"matrix-create-room": "src/matrix-create-room.js",
"matrix-invite-room": "src/matrix-invite-room.js",
"matrix-room-invite": "src/matrix-room-invite.js",
"matrix-join-room": "src/matrix-join-room.js",
"matrix-leave-room": "src/matrix-leave-room.js",
"matrix-crypt-file": "src/matrix-crypt-file.js",
"matrix-room-kick": "src/matrix-room-kick.js",
"matrix-room-ban": "src/matrix-room-ban.js",
"matrix-device-verification": "src/matrix-device-verification.js",
"matrix-synapse-users": "src/matrix-synapse-users.js",
"matrix-synapse-register": "src/matrix-synapse-register.js",
"matrix-synapse-create-edit-user": "src/matrix-synapse-create-edit-user.js",
@@ -43,9 +47,9 @@
"keywords": [
"node-red",
"matrix",
"support",
"bot",
"chat"
"chat",
"chatbot",
"federated"
],
"repository": {
"type": "git",

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>

View File

@@ -1,234 +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.requestVerification(msg.userId, msg.devices || null)
.then(function(e) {
node.log("Successfully requested verification");
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.VerificationRequest, 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)) {
// if(msg.userId && msg.deviceId) {
// node.server.beginKeyVerification("m.sas.v1", msg.userId, msg.deviceId);
// }
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() {
var that = this;
if(this.phase === 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);
verifierCancel();
});
let show_sas = function(e) {
// 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 = e.sas;
msg.emojis = e.sas.emoji.map(function(emoji, i) {
return emoji[0];
});
msg.emojis_text = e.sas.emoji.map(function(emoji, i) {
return emoji[1];
});
node.send(msg);
};
data._verifier.on('show_sas', show_sas);
data._verifier.verify()
.then(function(e){
data._verifier.off('show_sas', show_sas);
data._verifier.done();
}, function(e) {
verifierCancel();
node.warn(e);
// @todo return over second output
});
}
});
data.emit("change");
await data.accept();
} 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) {
data._verifier.sasEvent.confirm()
.then(function(e){
node.send([msg, null]);
})
.catch(function(e) {
msg.error = e;
node.send([null, msg]);
});
} else {
node.error("Verification must be started");
}
});
break;
}
}
RED.nodes.registerType("matrix-device-verification", MatrixDeviceVerification);
}

View File

@@ -12,9 +12,9 @@
roomId: { value: null },
},
label: function() {
return this.name || "Room Invite";
return this.name || "Invite to Room";
},
paletteLabel: 'Room Invite'
paletteLabel: 'Invite to Room'
});
</script>

View File

@@ -0,0 +1,63 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-leave-room', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs: 1,
outputs: 2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
return this.name || "Leave Room";
},
paletteLabel: 'Leave Room'
});
</script>
<script type="text/html" data-template-name="matrix-leave-room">
<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>
</script>
<script type="text/html" data-help-name="matrix-leave-room">
<h3>Details</h3>
<p>
This node leaves a room
</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to leave: for example, <code>!h8zld9j31:example.com</code>.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>Returns the same message that was received</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

54
src/matrix-leave-room.js Normal file
View File

@@ -0,0 +1,54 @@
module.exports = function(RED) {
function MatrixLeaveRoom(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
node.status({ fill: "red", shape: "ring", text: "disconnected" });
if (!node.server) {
node.error("No configuration node");
return;
}
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', function(msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
return;
}
if(!msg.topic) {
node.error('No room provided in msg.topic');
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
}
try {
node.log("Leaving room " + msg.topic);
node.server.matrixClient.leave(msg.topic);
node.send([msg, null]);
} catch(e) {
node.error("Failed to leave room " + msg.topic + ": " + e);
msg.payload = e;
node.send([null, msg]);
}
});
}
RED.nodes.registerType("matrix-leave-room", MatrixLeaveRoom);
}

View File

@@ -15,7 +15,10 @@
acceptStickers: {"value": true},
acceptReactions: {"value": true},
acceptFiles: {"value": true},
acceptAudio: {"value": true},
acceptImages: {"value": true},
acceptVideos: {"value": true},
acceptLocations: {"value": true},
},
label: function() {
return this.name || "Matrix Receive";
@@ -49,7 +52,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptText" style="width: auto">
Accept text <code>m.text</code>
Accept text <code style="text-transform: none;">m.text</code>
</label>
</div>
<div class="form-row">
@@ -59,7 +62,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptEmotes" style="width: auto">
Accept emotes <code>m.emote</code>
Accept emotes <code style="text-transform: none;">m.emote</code>
</label>
</div>
<div class="form-row">
@@ -69,7 +72,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptStickers" style="width: auto">
Accept stickers <code>m.sticker</code>
Accept stickers <code style="text-transform: none;">m.sticker</code>
</label>
</div>
<div class="form-row">
@@ -79,7 +82,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptReactions" style="width: auto">
Accept reactions <code>m.reaction</code>
Accept reactions <code style="text-transform: none;">m.reaction</code>
</label>
</div>
<div class="form-row">
@@ -89,7 +92,17 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptFiles" style="width: auto">
Accept files <code>m.file</code>
Accept files <code style="text-transform: none;">m.file</code>
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-acceptAudio"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptAudio" style="width: auto">
Accept files <code style="text-transform: none;">m.audio</code>
</label>
</div>
<div class="form-row">
@@ -99,7 +112,27 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptImages" style="width: auto">
Accept images <code>m.image</code>
Accept images <code style="text-transform: none;">m.image</code>
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-acceptVideos"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptVideos" style="width: auto">
Accept videos <code style="text-transform: none;">m.video</code>
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-acceptLocations"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptLocations" style="width: auto">
Accept locations <code style="text-transform: none;">m.location</code>
</label>
</div>
<script type="text/javascript">
@@ -237,6 +270,38 @@
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.audio</strong>'
<dl class="message-properties">
<dt>msg.filename <span class="property-type">string</span></dt>
<dd>the image's parsed filename</dd>
</dl>
<dl class="message-properties">
<dt>msg.mimetype <span class="property-type">string</span></dt>
<dd>audio file mimetype (ex: audio/ogg)</dd>
</dl>
<dl class="message-properties">
<dt>msg.url <span class="property-type">string</span></dt>
<dd>the file's URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.mxc_url <span class="property-type">string</span></dt>
<dd>the file's Matrix URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.duration <span class="property-type">integer</span></dt>
<dd>duration of audio file in milliseconds</dd>
</dl>
<dl class="message-properties">
<dt>msg.waveform <span class="property-type">array[int]</span></dt>
<dd>waveform of the audio clip</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.image</strong>'
<dl class="message-properties">
<dt>msg.filename <span class="property-type">string</span></dt>
@@ -263,5 +328,12 @@
<dd>the image's thumbnail Matrix URL</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.location</strong>'
<dl class="message-properties">
<dt>msg.geo_uri <span class="property-type">string</span></dt>
<dd>URI format of the geolocation</dd>
</dl>
</li>
</ul>
</script>

View File

@@ -11,7 +11,10 @@ module.exports = function(RED) {
this.acceptStickers = n.acceptStickers;
this.acceptReactions = n.acceptReactions;
this.acceptFiles = n.acceptFiles;
this.acceptAudio = n.acceptAudio;
this.acceptImages = n.acceptImages;
this.acceptVideos = n.acceptVideos;
this.acceptLocations = n.acceptLocations;
this.roomId = n.roomId;
this.roomIds = this.roomId ? this.roomId.split(',') : [];
@@ -72,6 +75,27 @@ module.exports = function(RED) {
}
break;
case 'm.audio':
if(!node.acceptAudio) return;
if(msg.encrypted) {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
msg.mxc_url = msg.content.file.url;
} else {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
}
if('org.matrix.msc1767.file' in msg.content) {
msg.filename = msg.content['org.matrix.msc1767.file'].name;
msg.mimetype = msg.content['org.matrix.msc1767.file'].mimetype;
}
if('org.matrix.msc1767.audio' in msg.content) {
msg.duration = msg.content['org.matrix.msc1767.audio'].duration;
msg.waveform = msg.content['org.matrix.msc1767.audio'].waveform;
}
break;
case 'm.image':
if(!node.acceptImages) return;
msg.filename = msg.content.filename || msg.content.body;
@@ -88,6 +112,29 @@ module.exports = function(RED) {
}
break;
case 'm.video':
if(!node.acceptVideos) return;
msg.filename = msg.content.filename || msg.content.body;
if(msg.encrypted) {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
msg.mxc_url = msg.content.file.url;
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_file.url);
msg.thumbnail_mxc_url = msg.content.info.thumbnail_file.url;
} else {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_url);
msg.thumbnail_mxc_url = msg.content.info.thumbnail_url;
}
break;
case 'm.location':
if(!node.acceptLocations) return;
msg.geo_uri = msg.content.geo_uri;
msg.payload = msg.content.body;
break;
case 'm.reaction':
if(!node.acceptReactions) return;
msg.info = msg.content["m.relates_to"].info;

View File

@@ -0,0 +1,80 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-room-invite', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs: 0,
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
return this.name || "Room Invite";
},
paletteLabel: 'Room Invite'
});
</script>
<script type="text/html" data-template-name="matrix-room-invite">
<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>
</script>
<script type="text/html" data-help-name="matrix-room-invite">
<h3>Details</h3>
<p>
This node receives room invites.
</p>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.type <span class="property-type">string</span></dt>
<dd>Always <code>m.room.member</code></dd>
</dl>
<dl class="message-properties">
<dt>msg.userId <span class="property-type">string</span></dt>
<dd>ID of the user the invite is from</dd>
</dl>
<dl class="message-properties">
<dt>msg.topic <span class="property-type">string</span></dt>
<dd>The room identifier for the invite: for example, <code>!h8zld9j31:example.com</code>.</dd>
</dl>
<dl class="message-properties">
<dt>msg.topicName <span class="property-type">string</span></dt>
<dd>The invited room name.</dd>
</dl>
<dl class="message-properties">
<dt>msg.event <span class="property-type">object</span></dt>
<dd>The event object for this invite to get extra details.</dd>
</dl>
<dl class="message-properties">
<dt>msg.eventId <span class="property-type">object</span></dt>
<dd>The ID of the event for this invite.</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

31
src/matrix-room-invite.js Normal file
View File

@@ -0,0 +1,31 @@
module.exports = function(RED) {
function MatrixRoomInvite(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
if(!this.server) {
node.error('Server must be configured on the node.');
return;
}
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" });
});
node.server.on("Room.invite", async function(msg) {
node.send(msg);
});
}
RED.nodes.registerType("matrix-room-invite", MatrixRoomInvite);
}

View File

@@ -30,7 +30,6 @@
deviceLabel: { type: "text", required: false },
accessToken: { type: "password", required: true },
deviceId: { type: "text", required: false },
secretStoragePassphrase: { type: "password", required: false },
url: { type: "text", required: true },
},
defaults: {
@@ -87,14 +86,6 @@
You can either provide/generate an access token yourself or use the login button above to do it automatically. View the <a href="javascript:$('#red-ui-tab-help-link-button').click();">node docs</a> to figure out how to generate an Access Token manually. If you generated a user with shared secret registration you will already have an access token you can place here.
</div>
<div class="form-row">
<label for="node-config-input-secretStoragePassphrase"><i class="fa fa-key"></i> Secret Storage Passphrase</label>
<input type="text" id="node-config-input-secretStoragePassphrase">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
You can either provide/generate an access token yourself or use the login button above to do it automatically. View the <a href="javascript:$('#red-ui-tab-help-link-button').click();">node docs</a> to figure out how to generate an Access Token manually. If you generated a user with shared secret registration you will already have an access token you can place here.
</div>
<div class="form-row">
<label for="node-config-input-deviceId"><i class="fa fa-desktop"></i> Device ID</label>
<input type="text" id="node-config-input-deviceId">
@@ -171,7 +162,7 @@
$("#matrix-access-token-loader").show();
$.ajax({
type: 'POST',
url: '/matrix-chat/login',
url: 'matrix-chat/login',
dataType: 'json',
data: {
'userId': userId,
@@ -223,4 +214,4 @@
<p>If this is a server you do not administrate/have access to follow these instructions:</p>
<ol><li>In a private/incognito browser window, open Element.</li><li>Log in to the account you want to get the access token for, such as the bot's account. <strong>Do not setup key storage</strong>.</li><li>Click on the bot's name in the top left corner then "Settings".</li><li>(Optional) Set your bot's display name and avatar.</li><li>Click the "Help &amp; About" tab (left side of the dialog).</li><li>Scroll to the bottom and click the <code>&lt;click to reveal&gt;</code> part of <code>Access Token: &lt;click to reveal&gt;</code>.</li><li>Copy your access token to a safe place, like the bot's configuration file.</li><li><strong>Do not log out.</strong> Instead, just close the window. If you used a private browsing session, you should be able to still use Element for your own account. Logging out deletes the access token from the server, making the bot unable to use it.</li></ol>
</div>
</script>
</script>

View File

@@ -4,11 +4,30 @@ const sdk = require("matrix-js-sdk");
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} = require("matrix-js-sdk");
const {deriveKey} = require("matrix-js-sdk/lib/crypto/key_passphrase");
const {encryptAES} = require("matrix-js-sdk/lib/crypto/aes");
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
if (!globalThis.fetch) {
import('node-fetch').then(({ default: fetch, Headers, Request, Response }) => {
Object.assign(globalThis, { fetch, Headers, Request, Response })
})
}
}
module.exports = function(RED) {
// disable logging if set to "off"
let loggingSettings = RED.settings.get('logging');
if(
typeof loggingSettings.console !== 'undefined' &&
typeof loggingSettings.console.level !== 'undefined' &&
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
) {
const { logger } = require('matrix-js-sdk/lib/logger');
logger.disableAll();
}
function MatrixFolderNameFromUserId(name) {
return name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
}
@@ -30,10 +49,9 @@ module.exports = function(RED) {
this.userId = this.credentials.userId;
this.deviceLabel = this.credentials.deviceLabel || null;
this.deviceId = this.credentials.deviceId || null;
this.secretStoragePassphrase = this.credentials.secretStoragePassphrase || null;
this.url = this.credentials.url;
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
this.e2ee = this.enableE2ee = n.enableE2ee || false;
this.e2ee = n.enableE2ee || false;
this.globalAccess = n.global;
this.initializedAt = new Date();
@@ -43,53 +61,6 @@ module.exports = function(RED) {
return;
}
let cryptoCallbacks = undefined;
if(node.enableE2ee && node.secretStoragePassphrase && false) {
// cryptoCallbacks = {
// getSecretStorageKey: async function({ keys }, name) {
// const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
// for (const [keyName, keyInfo] of Object.entries(keys)) {
// const key = await deriveKey(node.secretStoragePassphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations);
// // const key = Uint8Array.of(36, 47, 159, 193, 29, 188, 180, 86, 189, 180, 207, 101, 79, 255, 93, 159, 228, 43, 160, 158, 98, 209, 84, 196, 137, 122, 119, 118, 11, 131, 75, 87);
// const { mac } = await encryptAES(ZERO_STR, key, "", keyInfo.iv);
// if (keyInfo.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, '')) {
// return [keyName, key];
// }
// }
// return null;
// },
// async getDehydrationKey() {
// return node.secretStoragePassphrase;
// },
// async generateDehydrationKey() {
// return {key: node.secretStoragePassphrase};
// }
// };
cryptoCallbacks = {
getSecretStorageKey: async ({ keys }) => {
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;
@@ -112,16 +83,6 @@ module.exports = function(RED) {
node.log("Matrix server connection ready.");
node.emit("connected");
if(!initialSetup) {
if(node.enableE2ee && node.secretStoragePassphrase && !await node.matrixClient.isCrossSigningReady() && false) {
// bootstrap cross-signing
await node.matrixClient.bootstrapCrossSigning({
// maybe we can skip this?
authUploadDeviceSigningKeys: () => {
return true;
}
});
}
// store Device ID internally
let stored_device_id = getStoredDeviceId(localStorage),
device_id = this.matrixClient.getDeviceId();
@@ -176,12 +137,14 @@ module.exports = function(RED) {
node.matrixClient = sdk.createClient({
baseUrl: this.url,
accessToken: this.credentials.accessToken,
sessionStore: new sdk.WebStorageSessionStore(localStorage),
cryptoStore: new LocalStorageCryptoStore(localStorage),
store: new MemoryStore({
localStorage: localStorage,
}),
userId: this.userId,
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
verificationMethods: ["m.sas.v1"],
cryptoCallbacks: cryptoCallbacks
request
// verificationMethods: ["m.sas.v1"]
});
// set globally if configured to do so
@@ -291,16 +254,30 @@ module.exports = function(RED) {
// handle auto-joining rooms
node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) {
if(node.initializedAt > event.getDate()) {
return; // skip events that occurred before our client initialized
}
if (member.membership === "invite" && member.userId === node.userId) {
node.log("Got invite to join room " + member.roomId);
console.log(event);
if(node.autoAcceptRoomInvites) {
node.matrixClient.joinRoom(member.roomId).then(function() {
node.log("Automatically accepted invitation to join room " + member.roomId);
}).catch(function(e) {
node.warn("Cannot join room (could be from being kicked/banned) " + member.roomId + ": " + e);
});
} else {
node.log("Got invite to join room " + member.roomId);
}
let room = node.matrixClient.getRoom(event.getRoomId());
node.emit("Room.invite", {
type : 'm.room.member',
userId : event.getSender(),
topic : event.getRoomId(),
topicName : (room ? room.name : null) || null,
event : event,
eventId : event.getId(),
});
}
});
@@ -472,7 +449,8 @@ module.exports = function(RED) {
const matrixClient = sdk.createClient({
baseUrl: baseUrl,
deviceId: deviceId,
localTimeoutMs: '30000'
localTimeoutMs: '30000',
request
});
matrixClient.login(
@@ -503,7 +481,7 @@ module.exports = function(RED) {
let oldStorageDir = './matrix-local-storage',
oldStorageDir2 = './matrix-client-storage';
// if the old storage location exists lets move it to it's new location
// if the old storage location exists lets move it to the new location
if(fs.pathExistsSync(oldStorageDir)){
RED.nodes.eachNode(function(n){
try {
@@ -516,7 +494,7 @@ module.exports = function(RED) {
fs.copySync(oldStorageDir, dir);
}
} catch (err) {
console.error(err)
node.error(err);
}
});
@@ -525,7 +503,7 @@ module.exports = function(RED) {
fs.renameSync(oldStorageDir, oldStorageDir + "-backup");
}
if(RED.settings.userDir !== resolve('./')) {
if(RED.settings.userDir !== resolve('./') && resolve(oldStorageDir2) !== resolve(storageDir)) {
// user directory does not match running directory
// check if we stored stuff in wrong directory and move it
if(fs.pathExistsSync(oldStorageDir2)){

View File

@@ -52,7 +52,6 @@ module.exports = function(RED) {
node.server.matrixClient.http
.authedRequest(
undefined,
'PUT',
node.encodeUri(
"/_synapse/admin/v2/users/$userId",
@@ -62,13 +61,13 @@ module.exports = function(RED) {
msg.payload,
{ prefix: '' }
).then(function(e){
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error creating/editing user " + e);
msg.error = e;
node.send([null, msg]);
});
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error creating/editing user " + e);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-synapse-create-edit-user", MatrixSynapseCreateEditUser);

View File

@@ -56,7 +56,6 @@ module.exports = function(RED) {
);
node.server.matrixClient.http
.authedRequest(
undefined,
'POST',
path,
undefined,

View File

@@ -60,7 +60,6 @@ module.exports = function(RED) {
// we need the status code, so set onlydata to false for this request
node.server.matrixClient.http
.authedRequest(
undefined,
'POST',
node.encodeUri(
"/_synapse/admin/v1/join/$room_id_or_alias",

View File

@@ -48,7 +48,6 @@ module.exports = function(RED) {
node.server.matrixClient.http
.authedRequest(
undefined,
'GET',
"/_synapse/admin/v2/users",
queryParams,

View File

@@ -53,7 +53,6 @@ module.exports = function(RED) {
// we need the status code, so set onlydata to false for this request
node.server.matrixClient.http
.authedRequest(
undefined,
'GET',
node.encodeUri(
"/_matrix/client/r0/admin/whois/$userId",