mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2026-05-18 05:03:37 -06:00
Compare commits
6 Commits
45ff930518
...
e2ee-wip
| Author | SHA1 | Date | |
|---|---|---|---|
| 474ba6ea59 | |||
| b44142c0db | |||
| 702a980c6f | |||
| 543a1e658d | |||
| 487241e097 | |||
| 3b161f1ad9 |
+205
-210
@@ -22,59 +22,47 @@ To try out any of the examples:
|
||||
|
||||
## Index
|
||||
|
||||
**Click the ▶ example to ▼ expand**
|
||||
|
||||
### User Management
|
||||
|
||||
- [Get or set current user display name](#get-or-set-current-user-display-name)
|
||||
- [Set user avatar using URL](#set-user-avatar-using-url)
|
||||
- [Fetch user info by userId](#fetch-user-info-by-userid)
|
||||
- [Create User with Shared Secret Registration](#create-user-with-shared-secret-registration)
|
||||
- [Create/Edit Synapse User](#createedit-synapse-user)
|
||||
- [Deactivate User](#deactivate-user)
|
||||
- [Force User to Join Room](#force-user-to-join-room)
|
||||
<details>
|
||||
<summary>Get or set current user display name</summary>
|
||||
|
||||
### Message Handling
|
||||
[View JSON](get-set-displayname.json)
|
||||
|
||||
- [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)
|
||||
- [Remove Messages Containing "delete"](#remove-messages-containing-delete)
|
||||
This flow lets you get or set the displayname for the current user.
|
||||
|
||||
### Event Handling
|
||||

|
||||
|
||||
- [Sending Typing Events to a Room](#sending-typing-events-to-a-room)
|
||||
- [Mark all received events as read](#mark-all-received-events-as-read)
|
||||
- [Fetch event by eventId and roomId](#fetch-event-by-eventid-and-roomid)
|
||||
- [Paginate the entire history of a given room](#paginate-the-entire-history-of-a-given-room)
|
||||
- [Paginate related events to a given eventId](#paginate-related-events-to-a-given-eventid)
|
||||
</details>
|
||||
|
||||
### Room Management
|
||||
<details>
|
||||
<summary>Set user avatar using URL</summary>
|
||||
|
||||
- [Set room name and topic](#set-room-name-and-topic)
|
||||
- [Accept Room Invites from Specific User](#accept-room-invites-from-specific-user)
|
||||
- [Leave Room When Someone Says "bye"](#leave-room-when-someone-says-bye)
|
||||
- [Respond to "newroom" by Creating a New Room and Inviting User](#respond-to-newroom-by-creating-a-new-room-and-inviting-user)
|
||||
- [Respond to "joinroom \<room_id_or_alias\>" by Joining Mentioned Room](#respond-to-joinroom-room_id_or_alias-by-joining-mentioned-room)
|
||||
- [Kick/Ban User from Room](#kickban-user-from-room)
|
||||
[View JSON](set-avatar-from-url.json)
|
||||
|
||||
### User Information
|
||||
Inject a URL to an image and Node-RED will fetch the contents, upload to matrix, then set the user avatar to the new mxc_url.
|
||||
|
||||
- [Respond to "users" with Full List of Server Users](#respond-to-users-with-full-list-of-server-users)
|
||||
- [Respond to "whois \<user_id\>" with Information about the User's Session](#respond-to-whois-user_id-with-information-about-the-users-session)
|
||||
- [Respond to "rooms \<user_id\>" with User's Rooms](#respond-to-rooms-user_id-with-users-rooms)
|
||||
- [Respond to "room_users" with Current Room's Users](#respond-to-room_users-with-current-rooms-users)
|
||||
This is a good example of how to use the upload file node.
|
||||
|
||||
### Advanced Features
|
||||

|
||||
|
||||
- [Use Function Node to Run Any Command](#use-function-node-to-run-any-command)
|
||||
- [Download & Store All Received Files/Images](#download--store-all-received-filesimages)
|
||||
</details>
|
||||
|
||||
---
|
||||
<details>
|
||||
<summary>Fetch user info by userId</summary>
|
||||
|
||||
## User Management
|
||||
[View JSON](get-user.json)
|
||||
|
||||
### Create User with Shared Secret Registration
|
||||
Note this only works for users that the bot shares a room with. It will attempt to fetch the user from local storage first and if not found will query the server for the data.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Create User with Shared Secret Registration</summary>
|
||||
|
||||
[View JSON](shared-secret-registration.json)
|
||||
|
||||
@@ -89,9 +77,10 @@ Use this flow to create users on servers with closed registration. You can also
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Create/Edit Synapse User
|
||||
<details>
|
||||
<summary>Create/Edit Synapse User</summary>
|
||||
|
||||
[View JSON](add-user-with-admin-user.json)
|
||||
|
||||
@@ -99,9 +88,10 @@ Allows an administrator to create or modify a user account with a specified `msg
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Deactivate User
|
||||
<details>
|
||||
<summary>Deactivate User</summary>
|
||||
|
||||
[View JSON](deactivate-user.json)
|
||||
|
||||
@@ -114,9 +104,10 @@ If you send "deactivate_user @test:example.com", the bot will deactivate the `@t
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Force User to Join Room
|
||||
<details>
|
||||
<summary>Force User to Join Room</summary>
|
||||
|
||||
[View JSON](force-join-room.json)
|
||||
|
||||
@@ -129,11 +120,25 @@ If you send "force_join @test:example.com !320j90mf0394f:example.com", the bot w
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## Message Handling
|
||||
### Message Handling
|
||||
|
||||
### Respond to "ping" with "pong"
|
||||
<details>
|
||||
<summary>Upload file and send to room</summary>
|
||||
|
||||
[View JSON](send-image-to-room.json)
|
||||
|
||||
This flow will download an image from a given URL and upload it to the matrix server then send it to a room.
|
||||
|
||||
This isn't just for images and supports any sort of file format. Videos, images, and audio files will have metadata detected automatically and appended to the message (duration, dimensions, thumbnail, etc)
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Respond to "ping" with "pong"</summary>
|
||||
|
||||
[View JSON](respond-ping-pong.json)
|
||||
|
||||
@@ -141,9 +146,10 @@ Use this flow to respond to anyone who says "ping" with "pong" in the same room.
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "html" with an HTML Message
|
||||
<details>
|
||||
<summary>Respond to "html" with an HTML Message</summary>
|
||||
|
||||
[View JSON](respond-to-html-with-html.json)
|
||||
|
||||
@@ -151,39 +157,10 @@ Use this flow to respond to anyone who says "html" with an example HTML message.
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "image" with an Uploaded Image
|
||||
|
||||
[View JSON](respond-image-with-image.json)
|
||||
|
||||
You will need an image on the machine running Node-RED. In this example, `example.png` exists inside the Node-RED directory.
|
||||
|
||||
**Instructions:**
|
||||
|
||||
1. Place the image file (`example.png`) in the appropriate directory.
|
||||
2. Import the flow and deploy it.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Respond to "file" with an Uploaded File
|
||||
|
||||
[View JSON](respond-file-with-file.json)
|
||||
|
||||
You will need a file on the machine running Node-RED. In this example, `sample.pdf` exists inside the Node-RED directory.
|
||||
|
||||
**Instructions:**
|
||||
|
||||
1. Place the file (`sample.pdf`) in the appropriate directory.
|
||||
2. Import the flow and deploy it.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Respond to "react" with a Reaction
|
||||
<details>
|
||||
<summary>Respond to "react" with a Reaction</summary>
|
||||
|
||||
[View JSON](respond-react-with-reaction.json)
|
||||
|
||||
@@ -191,9 +168,10 @@ Gives a 👍 reaction when someone says "react".
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Remove Messages Containing "delete"
|
||||
<details>
|
||||
<summary>Remove Messages Containing "delete"</summary>
|
||||
|
||||
[View JSON](delete-event.json)
|
||||
|
||||
@@ -203,11 +181,99 @@ Any messages containing "delete" will be removed by the client.
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## Room Management
|
||||
### Event Handling
|
||||
|
||||
### Accept Room Invites from Specific User
|
||||
<details>
|
||||
<summary>Sending Typing Events to a Room</summary>
|
||||
|
||||
[View JSON](send-typing-events.json)
|
||||
|
||||
You can indicate to a room that the bot is typing and also cancel the typing event. This can be useful for making bots feel more interactive (e.g., show typing while requesting an API endpoint).
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Mark all received events as read</summary>
|
||||
|
||||
[View JSON](mark-all-read.json)
|
||||
|
||||
With this flow anytime an event is received by the bot it will mark it as read.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Fetch event by eventId and roomId</summary>
|
||||
|
||||
[View JSON](get-event.json)
|
||||
|
||||
Fetch an event from Matrix by eventId and roomId
|
||||
|
||||
**Instructions:**
|
||||
|
||||
- Change the inject node to contain a proper eventId and roomId (topic)
|
||||
- Inject the payload and you should see the result contain the event data
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Paginate the entire history of a given room</summary>
|
||||
|
||||
[View JSON](paginate-room-history.json)
|
||||
|
||||
This flow iterates the entire history of a room (outputting for every page we hit).
|
||||
|
||||
There is a configurable delay (currently set at 1000ms) in this flow. This is recommended, so you are not bogging down the server.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Paginate related events to a given eventId</summary>
|
||||
|
||||
[View JSON](fetch-event-relations.json)
|
||||
|
||||
Paginate through the related events to a given eventId. Related events being reactions, thread messages, message modifications, message removals, etc. This outputs once per iterated page.
|
||||
|
||||
If you would rather have it output one massive list at the end of pagination use this flow:
|
||||
[View Aggregated Flow JSON](fetch-event-relations-aggregated.json)
|
||||
|
||||
**Instructions:**
|
||||
|
||||
- Change the inject node to contain a proper eventId and roomId (topic)
|
||||
- Inject the payload and you should see the result contain a list of related events for the given eventId
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
### Room Management
|
||||
|
||||
<details>
|
||||
<summary>Set room name and topic</summary>
|
||||
|
||||
[View JSON](set-room-name-and-topic.json)
|
||||
|
||||
Changes the specified room's name and topic to the injected values.
|
||||
|
||||
There are a bunch of different settings you can change, this is just an example for these two fields to show how it's done.
|
||||
|
||||
This node can also be used to read these values.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Accept Room Invites from Specific User</summary>
|
||||
|
||||
[View JSON](accept-room-invites.json)
|
||||
|
||||
@@ -215,9 +281,10 @@ Automatically accept room invites from a specific user.
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Leave Room When Someone Says "bye"
|
||||
<details>
|
||||
<summary>Leave Room When Someone Says "bye"</summary>
|
||||
|
||||
[View JSON](leave-room-bye.json)
|
||||
|
||||
@@ -225,9 +292,10 @@ Leaves the room when someone says "bye".
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "newroom" by Creating a New Room and Inviting User
|
||||
<details>
|
||||
<summary>Respond to "newroom" by Creating a New Room and Inviting User</summary>
|
||||
|
||||
[View JSON](respond-newroom-invite.json)
|
||||
|
||||
@@ -235,19 +303,21 @@ When someone sends "newroom", a new room will be created, and the user who sent
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "joinroom \<room_id_or_alias\>" by Joining Mentioned Room
|
||||
<details>
|
||||
<summary>Respond to "joinroom <room_id_or_alias>" by Joining Mentioned Room</summary>
|
||||
|
||||
[View JSON](respond-joinroom.json)
|
||||
|
||||
When someone sends "joinroom \<room_id_or_alias\>", the bot will join the mentioned room.
|
||||
When someone sends "joinroom <room_id_or_alias>", the bot will join the mentioned room.
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Kick/Ban User from Room
|
||||
<details>
|
||||
<summary>Kick/Ban User from Room</summary>
|
||||
|
||||
[View JSON](room-kick-ban.json)
|
||||
|
||||
@@ -258,11 +328,12 @@ When someone sends "joinroom \<room_id_or_alias\>", the bot will join the mentio
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## User Information
|
||||
### User Information
|
||||
|
||||
### Respond to "users" with Full List of Server Users
|
||||
<details>
|
||||
<summary>Respond to "users" with Full List of Server Users</summary>
|
||||
|
||||
[View JSON](respond-users-list.json)
|
||||
|
||||
@@ -275,9 +346,10 @@ When someone sends the text "users", they receive an HTML message containing all
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "whois \<user_id\>" with Information about the User's Session
|
||||
<details>
|
||||
<summary>Respond to "whois <user_id>" with Information about the User's Session</summary>
|
||||
|
||||
[View JSON](respond-whois.json)
|
||||
|
||||
@@ -290,13 +362,14 @@ Lists out the user's session info, including IP address, last seen time, and use
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "rooms \<user_id\>" with User's Rooms
|
||||
<details>
|
||||
<summary>Respond to "rooms <user_id>" with User's Rooms</summary>
|
||||
|
||||
[View JSON](respond-rooms.json)
|
||||
|
||||
Responds to "rooms \<user_id\>" with that user's rooms. If the message is just "rooms", it responds with a list of all rooms the server is participating in.
|
||||
Responds to "rooms <user_id>" with that user's rooms. If the message is just "rooms", it responds with a list of all rooms the server is participating in.
|
||||
|
||||
**Notes:**
|
||||
|
||||
@@ -306,9 +379,10 @@ Responds to "rooms \<user_id\>" with that user's rooms. If the message is just "
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Respond to "room_users" with Current Room's Users
|
||||
<details>
|
||||
<summary>Respond to "room_users" with Current Room's Users</summary>
|
||||
|
||||
[View JSON](respond-room-users.json)
|
||||
|
||||
@@ -318,11 +392,12 @@ Lists the users participating in the current room.
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## Advanced Features
|
||||
### Advanced Features
|
||||
|
||||
### Use Function Node to Run Any Command
|
||||
<details>
|
||||
<summary>Use Function Node to Run Any Command</summary>
|
||||
|
||||
[View JSON](custom-redact-function-node.json)
|
||||
|
||||
@@ -337,19 +412,10 @@ To view the available functions, check out the [`client.ts` file from `matrix-js
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Sending Typing Events to a Room
|
||||
|
||||
[View JSON](send-typing-events.json)
|
||||
|
||||
You can indicate to a room that the bot is typing and also cancel the typing event. This can be useful for making bots feel more interactive (e.g., show typing while requesting an API endpoint).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Download & Store All Received Files/Images
|
||||
<details>
|
||||
<summary>Download & Store All Received Files/Images</summary>
|
||||
|
||||
[View JSON](store-received-files.json)
|
||||
|
||||
@@ -363,109 +429,38 @@ Downloads received files/images. If the file is encrypted, it will decrypt it fo
|
||||
|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Fetch event by eventId and roomId
|
||||
### Deprecated
|
||||
|
||||
[View JSON](get-event.json)
|
||||
<details>
|
||||
<summary>Respond to "image" with an uploaded image</summary>
|
||||
|
||||
Fetch an event from Matrix by eventId and roomId
|
||||
[View JSON](respond-image-with-image.json)
|
||||
|
||||
You will need an image on the machine running Node-RED. In this example, `example.png` exists inside the Node-RED directory.
|
||||
|
||||
**Instructions:**
|
||||
|
||||
- Change the inject node to contain a proper eventId and roomId (topic)
|
||||
- Inject the payload and you should see the result contain the event data
|
||||
1. Place the image file (`example.png`) in the appropriate directory.
|
||||
2. Import the flow and deploy it.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
### Paginate related events to a given eventId
|
||||
<details>
|
||||
<summary>Respond to "file" with an uploaded file</summary>
|
||||
|
||||
[View JSON](fetch-event-relations.json)
|
||||
|
||||
Paginate through the related events to a given eventId. Related events being reactions, thread messages, message modifications, message removals, etc. This outputs once per iterated page.
|
||||
|
||||
If you would rather have it output one massive list at the end of pagination use this flow:
|
||||
[View Aggregated Flow JSON](fetch-event-relations-aggregated.json)
|
||||
[View JSON](respond-file-with-file.json)
|
||||
|
||||
You will need a file on the machine running Node-RED. In this example, `sample.pdf` exists inside the Node-RED directory.
|
||||
|
||||
**Instructions:**
|
||||
|
||||
- Change the inject node to contain a proper eventId and roomId (topic)
|
||||
- Inject the payload and you should see the result contain a list of related events for the given eventId
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Mark all received events as read
|
||||
|
||||
[View JSON](mark-all-read.json)
|
||||
|
||||
With this flow anytime an event is received by the bot it will mark it as read.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Paginate the entire history of a given room
|
||||
|
||||
[View JSON](paginate-room-history.json)
|
||||
|
||||
This flow iterates the entire history of a room (outputting for every page we hit).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Fetch user info by userId
|
||||
|
||||
[View JSON](get-user.json)
|
||||
|
||||
Note this only works for users that the bot shares a room with. It will attempt to fetch the user from local storage first and if not found will query the server for the data.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Get or set current user display name
|
||||
|
||||
[View JSON](get-set-displayname.json)
|
||||
|
||||
This flow lets you get or set the displayname for the current user.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Set user avatar using URL
|
||||
|
||||
[View JSON](set-avatar-from-url.json)
|
||||
|
||||
Inject a URL to an image and Node-RED will fetch the contents, upload to matrix, then set the user avatar to the new mxc_url.
|
||||
|
||||
This is a good example of how to use the upload file node.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Set room name and topic
|
||||
|
||||
[View JSON](set-room-name-and-topic.json)
|
||||
|
||||
Changes the specified room's name and topic to the injected values.
|
||||
|
||||
There are a bunch of different settings you can change, this is just an example for these two fields to show how it's done.
|
||||
|
||||
This node can also be used to read these values.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
1. Place the file (`sample.pdf`) in the appropriate directory.
|
||||
2. Import the flow and deploy it.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"973dd418b00172c8",
|
||||
"3edbea9403d7c347"
|
||||
],
|
||||
"x": 754,
|
||||
"x": 854,
|
||||
"y": 1339,
|
||||
"w": 932,
|
||||
"w": 832,
|
||||
"h": 182
|
||||
},
|
||||
{
|
||||
@@ -113,7 +113,7 @@
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "!example:skylar.tech",
|
||||
"x": 900,
|
||||
"x": 1000,
|
||||
"y": 1480,
|
||||
"wires": [
|
||||
[
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
[
|
||||
{
|
||||
"id": "f4a0c2a9d7eed027",
|
||||
"type": "group",
|
||||
"z": "8fd89a0b44c61e76",
|
||||
"name": "Send an uploaded file to a room",
|
||||
"style": {
|
||||
"label": true
|
||||
},
|
||||
"nodes": [
|
||||
"8d475ab136d1ee7e",
|
||||
"2524f5a9a7ea2444",
|
||||
"9a149a36d6ab6470",
|
||||
"9da1ed1dc33930bb",
|
||||
"f93782c346d0e6ef"
|
||||
],
|
||||
"x": 754,
|
||||
"y": 2719,
|
||||
"w": 992,
|
||||
"h": 82
|
||||
},
|
||||
{
|
||||
"id": "8d475ab136d1ee7e",
|
||||
"type": "matrix-upload-file",
|
||||
"z": "8fd89a0b44c61e76",
|
||||
"g": "f4a0c2a9d7eed027",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"inputType": "msg",
|
||||
"inputValue": "payload",
|
||||
"fileNameType": "msg",
|
||||
"fileNameValue": "filename",
|
||||
"contentType": "",
|
||||
"generateThumbnails": true,
|
||||
"x": 1270,
|
||||
"y": 2760,
|
||||
"wires": [
|
||||
[
|
||||
"2524f5a9a7ea2444"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2524f5a9a7ea2444",
|
||||
"type": "matrix-send-message",
|
||||
"z": "8fd89a0b44c61e76",
|
||||
"g": "f4a0c2a9d7eed027",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"message": "",
|
||||
"messageType": "m.text",
|
||||
"messageFormat": "",
|
||||
"replaceMessage": false,
|
||||
"threadReplyType": "msg",
|
||||
"threadReplyValue": "isThread",
|
||||
"x": 1440,
|
||||
"y": 2760,
|
||||
"wires": [
|
||||
[
|
||||
"f93782c346d0e6ef"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9a149a36d6ab6470",
|
||||
"type": "http request",
|
||||
"z": "8fd89a0b44c61e76",
|
||||
"g": "f4a0c2a9d7eed027",
|
||||
"name": "",
|
||||
"method": "GET",
|
||||
"ret": "bin",
|
||||
"paytoqs": "ignore",
|
||||
"url": "",
|
||||
"tls": "",
|
||||
"persist": false,
|
||||
"proxy": "",
|
||||
"insecureHTTPParser": false,
|
||||
"authType": "",
|
||||
"senderr": false,
|
||||
"headers": [],
|
||||
"x": 1110,
|
||||
"y": 2760,
|
||||
"wires": [
|
||||
[
|
||||
"8d475ab136d1ee7e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9da1ed1dc33930bb",
|
||||
"type": "inject",
|
||||
"z": "8fd89a0b44c61e76",
|
||||
"g": "f4a0c2a9d7eed027",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "url",
|
||||
"v": "https://nodered.org/about/resources/media/node-red-icon.png",
|
||||
"vt": "str"
|
||||
},
|
||||
{
|
||||
"p": "filename",
|
||||
"v": "avatar.jpg",
|
||||
"vt": "str"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "!example:skylar.tech",
|
||||
"x": 900,
|
||||
"y": 2760,
|
||||
"wires": [
|
||||
[
|
||||
"9a149a36d6ab6470"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f93782c346d0e6ef",
|
||||
"type": "debug",
|
||||
"z": "8fd89a0b44c61e76",
|
||||
"g": "f4a0c2a9d7eed027",
|
||||
"name": "Debug Output",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"x": 1620,
|
||||
"y": 2760,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Generated
+5033
-16493
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -52,7 +52,8 @@
|
||||
"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-event-relations": "src/matrix-event-relations.js",
|
||||
"matrix-device-verification": "src/matrix-device-verification.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
<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>
|
||||
@@ -0,0 +1,267 @@
|
||||
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);
|
||||
}
|
||||
@@ -89,6 +89,9 @@
|
||||
<label for="node-input-threadReply"><i class="fa fa-commenting-o"></i> Thread Reply</label>
|
||||
<input type="text" id="node-input-threadReply">
|
||||
</div>
|
||||
<div class="form-row form-tips">
|
||||
If true and <code>msg.content.['m.relates_to'].event_id</code> or <code>msg.eventId</code> (parsed in that order) is provided the message will be a thread reply to the original event.
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-messageFormat">
|
||||
|
||||
+114
-4
@@ -52,6 +52,7 @@ 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;
|
||||
@@ -66,12 +67,39 @@ 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;
|
||||
@@ -83,6 +111,61 @@ 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;
|
||||
@@ -94,11 +177,19 @@ 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.enableE2ee) {
|
||||
if(!device_id && node.e2ee) {
|
||||
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) {
|
||||
@@ -145,6 +236,17 @@ 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,
|
||||
@@ -154,8 +256,14 @@ module.exports = function(RED) {
|
||||
}),
|
||||
userId: this.userId,
|
||||
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
|
||||
request
|
||||
// verificationMethods: ["m.sas.v1"]
|
||||
request,
|
||||
verificationMethods: ["m.sas.v1"],
|
||||
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
|
||||
});
|
||||
|
||||
node.matrixClient.on("crypto.keyBackupStatus", function() {
|
||||
console.log("crypto.keyBackupStatus");
|
||||
bootstrapSSSS();
|
||||
});
|
||||
|
||||
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
||||
@@ -400,6 +508,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.log("Connecting to Matrix server...");
|
||||
|
||||
Reference in New Issue
Block a user