Compare commits
4 Commits
e2ee-wip
...
e2ee-key-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5119bc42d4 | ||
|
|
2993dea974 | ||
|
|
678702e9c6 | ||
|
|
36c8208b01 |
Binary file not shown.
@@ -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).
|
||||
|
||||
|
||||
@@ -10,14 +10,11 @@ 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)
|
||||
- [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)
|
||||
- [Respond to "joinroom <room_id_or_alias>" by joining mentioned room](#respond-to-joinroom-room_id_or_alias-by-joining-mentioned-room)
|
||||
@@ -55,7 +52,7 @@ Allows an administrator to create or modify a user account with a specified `msg
|
||||
|
||||
[View JSON](custom-redact-function-node.json)
|
||||
|
||||
If we do not have a node for something you want to do you can do this manually with a function node. We now have a node for removing events but this is still a good example.
|
||||
If we do not have a node for something you want to do (such as redacting events/messages) you can do this manually with a function node.
|
||||
|
||||
**Note:** You should make sure to catch any errors in your function node otherwise you could cause Node-RED to crash.
|
||||
|
||||
@@ -63,26 +60,6 @@ To view what sort of functions you have access to check out the `client.ts` file
|
||||
|
||||

|
||||
|
||||
|
||||
### 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)
|
||||
|
||||

|
||||
|
||||
|
||||
### 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)
|
||||
|
||||

|
||||
|
||||
### Respond to "ping" with "pong"
|
||||
|
||||
[View JSON](respond-ping-pong.json)
|
||||
@@ -133,16 +110,6 @@ Give a 👍 reaction when someone says "react"
|
||||
|
||||
|
||||
|
||||
### Remove messages containing "delete"
|
||||
|
||||
[View JSON](delete-event.json)
|
||||
|
||||
Any messages containing "delete" will try to be removed by the client.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Respond to "users" with full list of server users
|
||||
|
||||
[View JSON](respond-users-list.json)
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "fed9197df27197a4",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 340,
|
||||
"y": 1560,
|
||||
"wires": [
|
||||
[
|
||||
"b289bb4fed9fa166"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b289bb4fed9fa166",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "cont",
|
||||
"v": "delete",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 490,
|
||||
"y": 1560,
|
||||
"wires": [
|
||||
[
|
||||
"48766b632ab2e6a1"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "48766b632ab2e6a1",
|
||||
"type": "matrix-delete-event",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"reason": "Requested deletion",
|
||||
"x": 630,
|
||||
"y": 1560,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "11f9cbbed7b95c83",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Delete messages containing \"delete\"",
|
||||
"info": "",
|
||||
"x": 480,
|
||||
"y": 1520,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -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 |
@@ -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 |
924
package-lock.json
generated
924
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.5.6",
|
||||
"version": "0.4.6",
|
||||
"description": "Matrix chat server client for Node-RED",
|
||||
"dependencies": {
|
||||
"fs-extra": "^10.0.1",
|
||||
"got": "^12.0.2",
|
||||
"fs-extra": "^10.0.0",
|
||||
"got": "^12.0.1",
|
||||
"isomorphic-webcrypto": "^2.3.8",
|
||||
"matrix-js-sdk": "^16.0.0",
|
||||
"matrix-js-sdk": "^15.5.0",
|
||||
"node-localstorage": "^2.2.1",
|
||||
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/271/download",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
"node-red": {
|
||||
@@ -17,7 +17,6 @@
|
||||
"matrix-server-config": "src/matrix-server-config.js",
|
||||
"matrix-receive": "src/matrix-receive.js",
|
||||
"matrix-send-message": "src/matrix-send-message.js",
|
||||
"matrix-delete-event": "src/matrix-delete-event.js",
|
||||
"matrix-send-file": "src/matrix-send-file.js",
|
||||
"matrix-send-image": "src/matrix-send-image.js",
|
||||
"matrix-react": "src/matrix-react.js",
|
||||
@@ -27,14 +26,15 @@
|
||||
"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",
|
||||
"matrix-synapse-deactivate-user": "src/matrix-synapse-deactivate-user.js",
|
||||
"matrix-synapse-join-room": "src/matrix-synapse-join-room.js",
|
||||
"matrix-whois-user": "src/matrix-whois-user.js",
|
||||
"matrix-room-users": "src/matrix-room-users.js"
|
||||
"matrix-room-users": "src/matrix-room-users.js",
|
||||
"matrix-device-verify": "src/matrix-device-verify.js",
|
||||
"matrix-secret-storage": "src/matrix-secret-storage.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-create-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-decrypt-file">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-decrypt-file">
|
||||
<h3>Details</h3>
|
||||
<p>Files sent in an encrypted room are themselves encrypted. Use this node to decrypt files. Note: This node will download the encrypted file so be cautious of large downloads.</p>
|
||||
<p>Files sent in an encrypted room are themselves encrypted. Use this node to encrypt/decrypt files. Note: This node will download the encrypted file so be cautious of large downloads.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-delete-event',{
|
||||
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 },
|
||||
reason: { value: "" },
|
||||
},
|
||||
label: function() {
|
||||
return this.name||"Delete Event";
|
||||
},
|
||||
paletteLabel: 'Delete Event'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-delete-event">
|
||||
<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-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reason"><i class="fa fa-sticky-note"></i> Reason</label>
|
||||
<input type="text" id="node-input-reason" placeholder="msg.reason">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-delete-event">
|
||||
<h3>Details</h3>
|
||||
<p>Delete an event in a room</p>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Room ID from where the event should be deleted from. Optional if configured on the node. If configured on the node this input will be overridden.</dd>
|
||||
<dt>msg.eventId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd>Event ID of the Event which should be deleted.</dd>
|
||||
<dt>msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd>Reason why the event is deleted. Default an empty string</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Success
|
||||
<dl class="message-properties">
|
||||
<dt>msg.eventId <span class="property-type">string</span></dt>
|
||||
<dd>the eventId from the deleted event.</dd>
|
||||
</dl>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.deleted <span class="property-type">boolean</span></dt>
|
||||
<dd>True, if the event is deleted</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 class="message-properties">
|
||||
<dt>msg.deleted <span class="property-type">boolean</span></dt>
|
||||
<dd>False, if the event is not deleted</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</li>
|
||||
</ol>
|
||||
</script>
|
||||
@@ -1,75 +0,0 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixDeleteEvent(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reason = n.reason
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration 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.on('input', function(msg) {
|
||||
|
||||
if(!msg.eventId) {
|
||||
node.error("eventId is missing");
|
||||
node.send([null, msg])
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.server || !node.server.matrixClient) {
|
||||
node.warn("No matrix server selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!node.server.isConnected()) {
|
||||
node.error("Matrix server connection is currently closed");
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.topic = node.roomId || msg.topic;
|
||||
if(!msg.topic) {
|
||||
node.warn("Room must be specified in msg.topic or in configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
msg.reason = node.reason || msg.reason;
|
||||
|
||||
if(!msg.reason) {
|
||||
msg.reason = '';
|
||||
}
|
||||
|
||||
node.server.matrixClient.redactEvent(msg.topic, msg.eventId, undefined,{
|
||||
reason: msg.reason
|
||||
})
|
||||
|
||||
.then(function(e) {
|
||||
msg.deleted = true
|
||||
node.send([msg, null]);
|
||||
})
|
||||
.catch(function(e){
|
||||
node.warn("Error deleting event " + e);
|
||||
msg.error = e;
|
||||
msg.deleted = false
|
||||
node.send([null, msg]);
|
||||
});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-delete-event",MatrixDeleteEvent);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
308
src/matrix-device-verify.html
Normal file
308
src/matrix-device-verify.html
Normal file
@@ -0,0 +1,308 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-device-verify-request', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
inputs: 0,
|
||||
outputs: 1,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Device Verify Request";
|
||||
},
|
||||
paletteLabel: 'Device Verify Request'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-device-verify-request">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-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-device-verify-request">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
|
||||
</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>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
|
||||
|
||||
<dt>msg.userId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> User's ID that will be invited to the room.</dd>
|
||||
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for the membership change.</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>Currently this endpoint returns an empty object</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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-device-verify-start', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Device Verify Start";
|
||||
},
|
||||
paletteLabel: 'Device Verify Start'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-device-verify-start">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-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-device-verify-start">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
|
||||
</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>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
|
||||
|
||||
<dt>msg.userId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> User's ID that will be invited to the room.</dd>
|
||||
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for the membership change.</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>Currently this endpoint returns an empty object</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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-device-verify-cancel', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Device Verify Cancel";
|
||||
},
|
||||
paletteLabel: 'Device Verify Cancel'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-device-verify-cancel">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-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-device-verify-cancel">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
|
||||
</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>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
|
||||
|
||||
<dt>msg.userId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> User's ID that will be invited to the room.</dd>
|
||||
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for the membership change.</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>Currently this endpoint returns an empty object</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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-device-verify-accept', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Device Verify Accept";
|
||||
},
|
||||
paletteLabel: 'Device Verify Accept'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-device-verify-accept">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-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-device-verify-accept">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
|
||||
</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>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
|
||||
|
||||
<dt>msg.userId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> User's ID that will be invited to the room.</dd>
|
||||
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for the membership change.</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>Currently this endpoint returns an empty object</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>
|
||||
271
src/matrix-device-verify.js
Normal file
271
src/matrix-device-verify.js
Normal file
@@ -0,0 +1,271 @@
|
||||
module.exports = function(RED) {
|
||||
const verificationRequests = new Map();
|
||||
|
||||
function MatrixDeviceVerifyRequest(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration 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" });
|
||||
});
|
||||
|
||||
/**
|
||||
* 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("crypto.verification.request", async function(data){
|
||||
console.log("[######### crypto.verification.request #########]", data.phase, data);
|
||||
|
||||
if(data.phase === 5 || data.phase === 6) {
|
||||
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,
|
||||
type: 'crypto.verification.request',
|
||||
selfVerification: data.isSelfVerification
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-device-verify-request", MatrixDeviceVerifyRequest);
|
||||
|
||||
|
||||
|
||||
function MatrixDeviceVerifyStart(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration 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.on('close', function(done) {
|
||||
verificationRequests.clear();
|
||||
done();
|
||||
});
|
||||
|
||||
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("Invaid verification request: " + (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;
|
||||
console.log("[##### VERIFICATION PHASE CHANGE #######]", this.phase);
|
||||
if(this.phase === 4) {
|
||||
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 verificaiton 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){
|
||||
console.log("!!!!!!!!!!! VERIFY THEN", 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-device-verify-start", MatrixDeviceVerifyStart);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function MatrixDeviceVerifyCancel(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration 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.on('close', function(done) {
|
||||
verificationRequests.clear();
|
||||
done();
|
||||
});
|
||||
|
||||
node.on('input', async function(msg){
|
||||
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
|
||||
node.error("Invaid verification request: " + (msg.verifyRequestId || null));
|
||||
}
|
||||
|
||||
var data = verificationRequests.get(msg.verifyRequestId);
|
||||
if(data) {
|
||||
data.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-device-verify-cancel", MatrixDeviceVerifyCancel);
|
||||
|
||||
|
||||
|
||||
|
||||
function MatrixDeviceVerifyAccept(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration 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.on('close', function(done) {
|
||||
verificationRequests.clear();
|
||||
done();
|
||||
});
|
||||
|
||||
node.on('input', async function(msg){
|
||||
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
|
||||
node.error("Invaid verification request: " + (msg.verifyRequestId || null));
|
||||
}
|
||||
|
||||
var data = verificationRequests.get(msg.verifyRequestId);
|
||||
if(data._verifier && data._verifier.sasEvent) {
|
||||
data._verifier.sasEvent.confirm()
|
||||
.then(function(e){
|
||||
console.log("!!!!!!!! CONFIRMED VERIFY", e);
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.log("!!!!!!!! CONFIRMED VERIFY FAILED", e);
|
||||
});
|
||||
} else {
|
||||
console.log("Verification must be started", data);
|
||||
node.error("Verification must be started");
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-device-verify-accept", MatrixDeviceVerifyAccept);
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-invite-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -30,21 +30,9 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-invite-room">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-join-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
reaction: { value: null }
|
||||
roomId: { value: null }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "React";
|
||||
@@ -21,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-react">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -29,25 +28,9 @@
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reaction"><i class="fa fa-thumbs-up"></i> Reaction</label>
|
||||
<input type="text" id="node-input-reaction" placeholder="msg.payload">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-react">
|
||||
@@ -59,7 +42,7 @@
|
||||
<dt>msg.payload
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Usually an emoji but can also be text. If configured on the node this is ignored otherwise it required. </dd>
|
||||
<dd> Usually an emoji but can also be text. </dd>
|
||||
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string | null</span>
|
||||
|
||||
@@ -7,7 +7,6 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reaction = n.reaction;
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration node");
|
||||
@@ -41,9 +40,8 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = n.reaction || msg.payload;
|
||||
if(!payload) {
|
||||
node.error('msg.payload must be defined or the reaction configured on the node.');
|
||||
if(!msg.payload) {
|
||||
node.error('msg.payload is required');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,7 +59,7 @@ module.exports = function(RED) {
|
||||
{
|
||||
"m.relates_to": {
|
||||
event_id: eventId,
|
||||
key: payload,
|
||||
key: msg.payload,
|
||||
rel_type: "m.annotation"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,8 @@
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-tips">Enter a single room, comma separated list of rooms, or leave blank to get from all</div>
|
||||
<div class="form-row" style="margin-left: 100px;margin-top:10px;font-weight:bold;">
|
||||
@@ -102,17 +101,6 @@
|
||||
Accept images <code>m.image</code>
|
||||
</label>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-receive">
|
||||
@@ -128,11 +116,6 @@
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.isDM <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message is from a direct message room.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.encrypted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message was encrypted (e2ee).</dd>
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
reason: { value: null }
|
||||
roomId: { value: null }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Ban";
|
||||
@@ -21,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-ban">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -29,25 +28,9 @@
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reason"><i class="fa fa-comment"></i> Reason</label>
|
||||
<input type="text" id="node-input-reason" placeholder="msg.topic">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-ban">
|
||||
@@ -69,7 +52,7 @@
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for banning the user. If configured on the node it will overwrite this input</dd>
|
||||
<dd> Reason for banning the user.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
|
||||
@@ -7,7 +7,6 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reason = n.reason;
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration node");
|
||||
@@ -46,7 +45,7 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.server.matrixClient.ban(msg.topic, msg.userId, n.reason || msg.reason || undefined)
|
||||
node.server.matrixClient.ban(msg.topic, msg.userId, msg.reason || undefined)
|
||||
.then(function(e) {
|
||||
node.log("Successfully banned " + msg.userId + " from " + msg.topic);
|
||||
msg.eventId = e.event_id;
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
reason: { value: null }
|
||||
roomId: { value: null }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Kick";
|
||||
@@ -21,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-kick">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -29,25 +28,9 @@
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reason"><i class="fa fa-comment"></i> Reason</label>
|
||||
<input type="text" id="node-input-reason" placeholder="msg.topic">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-kick">
|
||||
@@ -69,7 +52,7 @@
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for kicking the user. If configured on the node it will overwrite this input</dd>
|
||||
<dd> Reason for kicking the user.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
|
||||
@@ -7,7 +7,6 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reason = n.reason;
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration node");
|
||||
@@ -46,7 +45,7 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.server.matrixClient.kick(msg.topic, msg.userId, n.reason || msg.reason || undefined)
|
||||
node.server.matrixClient.kick(msg.topic, msg.userId, msg.reason || undefined)
|
||||
.then(function(e) {
|
||||
node.log("Successfully kicked " + msg.userId + " from " + msg.topic);
|
||||
msg.eventId = e.event_id;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-users">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -30,19 +30,7 @@
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-user"></i> Room Id</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-users">
|
||||
|
||||
71
src/matrix-secret-storage.html
Normal file
71
src/matrix-secret-storage.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-secret-storage', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Secret Storage";
|
||||
},
|
||||
paletteLabel: 'Secret Storage'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-secret-storage">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-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-secret-storage">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
Secure backup node. Use this to setup security key backup to the remote server. You can also use this node to import an existing secure backup.
|
||||
</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>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
|
||||
|
||||
<dt>msg.userId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> User's ID that will be invited to the room.</dd>
|
||||
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for the membership change.</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>Currently this endpoint returns an empty object</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>
|
||||
92
src/matrix-secret-storage.js
Normal file
92
src/matrix-secret-storage.js
Normal file
@@ -0,0 +1,92 @@
|
||||
module.exports = function(RED) {
|
||||
const verificationRequests = new Map();
|
||||
|
||||
function MatrixSecretStorage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration 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.on('input', async function(msg){
|
||||
try {
|
||||
msg.hasSecretStorage = await node.server.matrixClient.hasSecretStorageKey();
|
||||
} catch(e) {
|
||||
console.log("ERROR", e);
|
||||
}
|
||||
|
||||
if(msg.action) {
|
||||
if(msg.action === 'create') {
|
||||
if(msg.hasSecretStorage && !msg.forceReset) {
|
||||
node.error("Secret storage already setup. Pass msg.forceReset to bypass and regenerate.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// copying this from https://github.com/matrix-org/matrix-react-sdk/blob/e78a1adb6f1af2ea425b0bae9034fb7344a4b2e8/src/SecurityManager.ts#L294
|
||||
const recoveryKey = await node.server.matrixClient.createRecoveryKeyFromPassphrase(msg.key || undefined);
|
||||
if(msg.forceReset) {
|
||||
await node.server.matrixClient.bootstrapSecretStorage({
|
||||
createSecretStorageKey: async () => recoveryKey,
|
||||
setupNewKeyBackup: true,
|
||||
setupNewSecretStorage: true,
|
||||
});
|
||||
} else {
|
||||
// For password authentication users after 2020-09, this cross-signing
|
||||
// step will be a no-op since it is now setup during registration or login
|
||||
// when needed. We should keep this here to cover other cases such as:
|
||||
// * Users with existing sessions prior to 2020-09 changes
|
||||
// * SSO authentication users which require interactive auth to upload
|
||||
// keys (and also happen to skip all post-authentication flows at the
|
||||
// moment via token login)
|
||||
await node.server.matrixClient.bootstrapCrossSigning({
|
||||
// maybe we can skip this?
|
||||
// authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
});
|
||||
const backupInfo = await node.server.matrixClient.getKeyBackupVersion();
|
||||
await node.server.matrixClient.bootstrapSecretStorage({
|
||||
createSecretStorageKey: async () => this._recoveryKey,
|
||||
keyBackupInfo: backupInfo,
|
||||
setupNewKeyBackup: !backupInfo,
|
||||
getKeyBackupPassphrase: () => {
|
||||
// We may already have the backup key if we earlier went
|
||||
// through the restore backup path, so pass it along
|
||||
// rather than prompting again.
|
||||
if (this._backupKey) {
|
||||
return this._backupKey;
|
||||
}
|
||||
return promptForBackupPassphrase();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(msg.action === 'download') {
|
||||
if(!msg.hasSecretStorage) {
|
||||
node.error("Secret storage not setup so cannot download.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-secret-storage", MatrixSecretStorage);
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-send-file">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -29,9 +29,8 @@
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
|
||||
@@ -40,17 +39,6 @@
|
||||
<div class="form-tips">
|
||||
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a> (ex: application/pdf) or left empty
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-send-file">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-send-image">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -29,9 +29,8 @@
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
|
||||
@@ -40,17 +39,6 @@
|
||||
<div class="form-tips">
|
||||
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a> (ex: image/png) or left empty
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-send-image">
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
message: { value: null },
|
||||
messageType: { value: 'm.text' },
|
||||
messageFormat: { value: '' },
|
||||
replaceMessage : { value: false }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Send Message";
|
||||
@@ -24,7 +22,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-send-message">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -34,25 +32,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-message"><i class="fa fa-comment"></i> Message</label>
|
||||
<textarea id="node-input-message" placeholder="msg.payload" style="width: 70%;"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-replaceMessage"
|
||||
style="width: auto; margin-left: 105px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-replaceMessage" style="width: auto;max-width:50%;">
|
||||
Update existing message if <code>msg.eventId</code> is set
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@@ -79,17 +60,6 @@
|
||||
<option value="msg.format">msg.format input</option>
|
||||
</select>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-send-message">
|
||||
@@ -106,17 +76,12 @@
|
||||
<dt>msg.payload
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> the message text. If configured on the node this is ignored otherwise it required. </dd>
|
||||
|
||||
<dt>msg.replace
|
||||
<span class="property-type">bool</span>
|
||||
</dt>
|
||||
<dd> If true and <code>msg.eventId</code> is present it will update an existing message. Posts a new message if false or <code>msg.eventId</code> is missing. </dd>
|
||||
<dd> the message text. </dd>
|
||||
|
||||
<dt class="optional">msg.formatted_payload
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> the formatted HTML message (uses <code>msg.payload</code> if not defined). This only affects HTML messages.</dd>
|
||||
<dd> the formatted HTML message (uses msg.payload if not defined). This only affects HTML messages.</dd>
|
||||
|
||||
<dt class="optional">msg.type
|
||||
<span class="property-type">string | null</span>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
const {RelationType} = require("matrix-js-sdk");
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixSendImage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
@@ -11,8 +9,6 @@ module.exports = function(RED) {
|
||||
this.roomId = n.roomId;
|
||||
this.messageType = n.messageType;
|
||||
this.messageFormat = n.messageFormat;
|
||||
this.replaceMessage = n.replaceMessage;
|
||||
this.message = n.message;
|
||||
|
||||
// taken from https://github.com/matrix-org/synapse/blob/master/synapse/push/mailer.py
|
||||
this.allowedTags = [
|
||||
@@ -102,15 +98,14 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = n.message || msg.payload;
|
||||
if(!payload) {
|
||||
node.error('msg.payload must be defined or the message configured on the node.');
|
||||
if(!msg.payload) {
|
||||
node.error('msg.payload is required');
|
||||
return;
|
||||
}
|
||||
|
||||
let content = {
|
||||
msgtype: msgType,
|
||||
body: payload.toString()
|
||||
body: msg.payload.toString()
|
||||
};
|
||||
|
||||
if(msgFormat === 'html') {
|
||||
@@ -118,31 +113,12 @@ module.exports = function(RED) {
|
||||
content.formatted_body =
|
||||
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
|
||||
? msg.formatted_payload.toString()
|
||||
: payload.toString();
|
||||
}
|
||||
|
||||
if((node.replaceMessage || msg.replace) && msg.eventId) {
|
||||
content['m.new_content'] = {
|
||||
msgtype: content.msgtype,
|
||||
body: content.body
|
||||
};
|
||||
if('format' in content) {
|
||||
content['m.new_content']['format'] = content['format'];
|
||||
}
|
||||
if('formatted_body' in content) {
|
||||
content['m.new_content']['formatted_body'] = content['formatted_body'];
|
||||
}
|
||||
|
||||
content['m.relates_to'] = {
|
||||
rel_type: RelationType.Replace,
|
||||
event_id: msg.eventId
|
||||
};
|
||||
content['body'] = ' * ' + content['body'];
|
||||
: msg.payload.toString();
|
||||
}
|
||||
|
||||
node.server.matrixClient.sendMessage(msg.topic, content)
|
||||
.then(function(e) {
|
||||
node.log("Message sent: " + payload);
|
||||
node.log("Message sent: " + msg.payload);
|
||||
msg.eventId = e.event_id;
|
||||
node.send([msg, null]);
|
||||
})
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
userId: { type: "text", required: true },
|
||||
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 },
|
||||
deviceId: { type: "text", required: true },
|
||||
secureStoragePassphrase: { type: "text", required: false },
|
||||
url: { type: "text", required: true }
|
||||
},
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
@@ -87,14 +87,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">
|
||||
@@ -104,6 +96,14 @@
|
||||
WARNING: If you change this after the client has already initialized you will break encryption. Your Device ID is tied to your encryption keys.
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-secureStoragePassphrase"><i class="fa fa-key"></i> Secure Storage Passphrase</label>
|
||||
<input type="text" id="node-config-input-secureStoragePassphrase">
|
||||
</div>
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
If set secure storage will be setup with this passphrase. If secure storage already exists on the account it will attempt to decrypt it. Leave blank to skip.
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -207,6 +207,12 @@
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(function(){
|
||||
$("#node-config-input-enableE2ee").on('change', function(e){
|
||||
$("#node-config-input-secureStoragePassphrase").attr('disabled', !$(this).is(':checked'));
|
||||
}).change();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ 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");
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixFolderNameFromUserId(name) {
|
||||
@@ -30,46 +28,19 @@ 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.secureStoragePassphrase = this.credentials.secureStoragePassphrase || null;
|
||||
this.url = this.credentials.url;
|
||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||
this.e2ee = n.enableE2ee || false;
|
||||
|
||||
this.enableE2ee = n.enableE2ee || false;
|
||||
this.e2ee = (this.enableE2ee && this.deviceId);
|
||||
this.globalAccess = n.global;
|
||||
this.initializedAt = new Date();
|
||||
|
||||
|
||||
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;
|
||||
@@ -92,52 +63,42 @@ module.exports = function(RED) {
|
||||
node.log("Matrix server connection ready.");
|
||||
node.emit("connected");
|
||||
if(!initialSetup) {
|
||||
if(node.e2ee && !await node.matrixClient.isCrossSigningReady()) {
|
||||
// bootstrap cross-signing
|
||||
await node.matrixClient.bootstrapCrossSigning({
|
||||
// maybe we can skip this?
|
||||
authUploadDeviceSigningKeys: (makeRequest) => {
|
||||
makeRequest()
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
await node.matrixClient.checkOwnCrossSigningTrust();
|
||||
}
|
||||
|
||||
// store Device ID internally
|
||||
let stored_device_id = getStoredDeviceId(localStorage),
|
||||
device_id = this.matrixClient.getDeviceId();
|
||||
if(!stored_device_id || stored_device_id !== device_id) {
|
||||
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
||||
storeDeviceId(localStorage, device_id);
|
||||
}
|
||||
|
||||
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) {
|
||||
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
||||
storeDeviceId(localStorage, device_id);
|
||||
}
|
||||
|
||||
// update device label
|
||||
if(node.deviceLabel) {
|
||||
node.matrixClient
|
||||
.getDevice(device_id)
|
||||
.then(
|
||||
function(response) {
|
||||
if(response.display_name !== node.deviceLabel) {
|
||||
node.matrixClient.setDeviceDetails(device_id, {
|
||||
display_name: node.deviceLabel
|
||||
}).then(
|
||||
function(response) {},
|
||||
function(error) {
|
||||
node.error("Failed to set device label: " + error);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
node.error("Failed to fetch device: " + error);
|
||||
// update device label
|
||||
if(node.deviceLabel) {
|
||||
node.matrixClient
|
||||
.getDevice(device_id)
|
||||
.then(
|
||||
function(response) {
|
||||
if(response.display_name !== node.deviceLabel) {
|
||||
node.matrixClient.setDeviceDetails(device_id, {
|
||||
display_name: node.deviceLabel
|
||||
}).then(
|
||||
function(response) {},
|
||||
function(error) {
|
||||
node.error("Failed to set device label: " + error);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
node.error("Failed to fetch device: " + error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await accessSecretStorage(function(){});
|
||||
} catch(e) {
|
||||
node.error("secret storage bootstrap failure: " + e);
|
||||
console.log("secret storage bootstrap failure: ", e);
|
||||
}
|
||||
|
||||
initialSetup = true;
|
||||
@@ -155,6 +116,7 @@ module.exports = function(RED) {
|
||||
|
||||
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
|
||||
upgradeDirectoryIfNecessary(node, storageDir);
|
||||
|
||||
node.matrixClient = sdk.createClient({
|
||||
baseUrl: this.url,
|
||||
accessToken: this.credentials.accessToken,
|
||||
@@ -163,7 +125,61 @@ module.exports = function(RED) {
|
||||
userId: this.userId,
|
||||
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
|
||||
verificationMethods: ["m.sas.v1"],
|
||||
cryptoCallbacks: cryptoCallbacks
|
||||
// cryptoCallbacks: {
|
||||
// getSecretStorageKey: async function(
|
||||
// { keys: keyInfos },
|
||||
// ssssItemName,
|
||||
// ){
|
||||
// const cli = node.matrixClient;
|
||||
// let keyId = await cli.getDefaultSecretStorageKeyId();
|
||||
// // console.log("DEFAULT SECRET STORAGE KEY ID: " + keyId, keyInfos);
|
||||
// //
|
||||
// // let decodeBase64 = function(base64) {
|
||||
// // return Buffer.from(base64, "base64");
|
||||
// // }
|
||||
// // return await this.crypto.getSecretStorageKey(keyId);
|
||||
// let keyInfo;
|
||||
// if (keyId) {
|
||||
// // use the default SSSS key if set
|
||||
// keyInfo = keyInfos[keyId];
|
||||
// if (!keyInfo) {
|
||||
// // if the default key is not available, pretend the default key
|
||||
// // isn't set
|
||||
// keyId = undefined;
|
||||
// }
|
||||
// }
|
||||
// if (!keyId) {
|
||||
// // if no default SSSS key is set, fall back to a heuristic of using the
|
||||
// // only available key, if only one key is set
|
||||
// const keyInfoEntries = Object.entries(keyInfos);
|
||||
// if (keyInfoEntries.length > 1) {
|
||||
// throw new Error("Multiple storage key requests not implemented");
|
||||
// }
|
||||
// [keyId, keyInfo] = keyInfoEntries[0];
|
||||
// }
|
||||
//
|
||||
// // Check the in-memory cache
|
||||
// // if (isCachingAllowed() && secretStorageKeys[keyId]) {
|
||||
// // return [keyId, secretStorageKeys[keyId]];
|
||||
// // }
|
||||
//
|
||||
// // if (dehydrationCache.key) {
|
||||
// // if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
|
||||
// // cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key);
|
||||
// // return [keyId, dehydrationCache.key];
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
// // const backupInfo = await node.matrixClient.getKeyBackupVersion();
|
||||
// const backupInfo = await node.matrixClient.getAccountDataFromServer(
|
||||
// "m.secret_storage.key." + keyId
|
||||
// );
|
||||
//
|
||||
// // if(await cli.checkSecretStorageKey(key, keyInfo)) {
|
||||
// // }
|
||||
// return [keyId, await node.matrixClient.keyBackupKeyFromPassword(node.secureStoragePassphrase, backupInfo)] ;
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
// set globally if configured to do so
|
||||
@@ -191,7 +207,7 @@ module.exports = function(RED) {
|
||||
return node.connected;
|
||||
};
|
||||
|
||||
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
||||
node.matrixClient.on("Room.timeline", async function(event, room, toStartOfTimeline, removed, data) {
|
||||
if (toStartOfTimeline) {
|
||||
return; // ignore paginated results
|
||||
}
|
||||
@@ -212,32 +228,16 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDmRoom = (room) => {
|
||||
// Find out if this is a direct message room.
|
||||
let isDM = !!room.getDMInviter();
|
||||
const allMembers = room.currentState.getMembers();
|
||||
if (!isDM && allMembers.length <= 2) {
|
||||
// if not a DM, but there are 2 users only
|
||||
// double check DM (needed because getDMInviter works only if you were invited, not if you invite)
|
||||
// hence why we check for each member
|
||||
if (allMembers.some((m) => m.getDMInviter())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return allMembers.length <= 2 && isDM;
|
||||
};
|
||||
|
||||
let msg = {
|
||||
encrypted : event.isEncrypted(),
|
||||
redacted : event.isRedacted(),
|
||||
content : event.getContent(),
|
||||
type : (event.getContent()['msgtype'] || event.getType()) || null,
|
||||
payload : (event.getContent()['body'] || event.getContent()) || null,
|
||||
isDM : isDmRoom(room),
|
||||
userId : event.getSender(),
|
||||
topic : event.getRoomId(),
|
||||
eventId : event.getId(),
|
||||
event : event
|
||||
event : event,
|
||||
};
|
||||
|
||||
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body + (toStartOfTimeline ? ' [PAGINATED]' : ''));
|
||||
@@ -250,9 +250,9 @@ module.exports = function(RED) {
|
||||
*
|
||||
* @event module:client~MatrixClient#"crypto.suggestKeyRestore"
|
||||
*/
|
||||
// node.matrixClient.on("crypto.suggestKeyRestore", function(){
|
||||
//
|
||||
// });
|
||||
node.matrixClient.on("crypto.suggestKeyRestore", function(){
|
||||
|
||||
});
|
||||
|
||||
// node.matrixClient.on("RoomMember.typing", async function(event, member) {
|
||||
// let isTyping = member.typing;
|
||||
@@ -271,8 +271,7 @@ module.exports = function(RED) {
|
||||
// });
|
||||
|
||||
// handle auto-joining rooms
|
||||
|
||||
node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) {
|
||||
node.matrixClient.on("RoomMember.membership", async function(event, member) {
|
||||
if (member.membership === "invite" && member.userId === node.userId) {
|
||||
if(node.autoAcceptRoomInvites) {
|
||||
node.matrixClient.joinRoom(member.roomId).then(function() {
|
||||
@@ -286,7 +285,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
|
||||
node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) {
|
||||
node.matrixClient.on("sync", async function(state, prevState, data) {
|
||||
node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]");
|
||||
if(prevState === null && state === "PREPARED" ) {
|
||||
// Occurs when the initial sync is completed first time.
|
||||
@@ -354,8 +353,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
|
||||
node.matrixClient.on("Session.logged_out", async function(errorObj){
|
||||
// Example if user auth token incorrect:
|
||||
// {
|
||||
// errcode: 'M_UNKNOWN_TOKEN',
|
||||
@@ -371,13 +369,88 @@ module.exports = function(RED) {
|
||||
stopClient();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* This helper should be used whenever you need to access secret storage. It
|
||||
* ensures that secret storage (and also cross-signing since they each depend on
|
||||
* each other in a cycle of sorts) have been bootstrapped before running the
|
||||
* provided function.
|
||||
*
|
||||
* Bootstrapping secret storage may take one of these paths:
|
||||
* 1. Create secret storage from a passphrase and store cross-signing keys
|
||||
* in secret storage.
|
||||
* 2. Access existing secret storage by requesting passphrase and accessing
|
||||
* cross-signing keys as needed.
|
||||
* 3. All keys are loaded and there's nothing to do.
|
||||
*
|
||||
* @param {Function} [func] An operation to perform once secret storage has been
|
||||
* bootstrapped. Optional.
|
||||
* @param {boolean} [forceReset] Reset secret storage even if it's already set up
|
||||
*/
|
||||
let accessSecretStorage = async function(func = async () => { }, forceReset = false) {
|
||||
// only do this if we have a secure storage password
|
||||
if(!node.secureStoragePassphrase) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recoveryKey = await node.matrixClient.createRecoveryKeyFromPassphrase(node.secureStoragePassphrase);
|
||||
const cli = node.matrixClient;
|
||||
try {
|
||||
if (!(await cli.hasSecretStorageKey()) || forceReset) {
|
||||
// For password authentication users after 2020-09, this cross-signing
|
||||
// step will be a no-op since it is now setup during registration or login
|
||||
// when needed. We should keep this here to cover other cases such as:
|
||||
// * Users with existing sessions prior to 2020-09 changes
|
||||
// * SSO authentication users which require interactive auth to upload
|
||||
// keys (and also happen to skip all post-authentication flows at the
|
||||
// moment via token login)
|
||||
if(!await node.matrixClient.isCrossSigningReady()) {
|
||||
await node.matrixClient.bootstrapCrossSigning({
|
||||
// maybe we can skip this?
|
||||
authUploadDeviceSigningKeys: () => {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const backupInfo = await node.matrixClient.getKeyBackupVersion();
|
||||
await node.matrixClient.bootstrapSecretStorage({
|
||||
createSecretStorageKey: async () => recoveryKey,
|
||||
keyBackupInfo: backupInfo,
|
||||
setupNewKeyBackup: !backupInfo,
|
||||
getKeyBackupPassphrase: () => {
|
||||
return recoveryKey;
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await node.matrixClient.bootstrapSecretStorage({
|
||||
getKeyBackupPassphrase: async () => recoveryKey,
|
||||
});
|
||||
}
|
||||
|
||||
// `return await` needed here to ensure `finally` block runs after the
|
||||
// inner operation completes.
|
||||
return await func();
|
||||
} catch (e) {
|
||||
node.error("Secret storage init failure: " + e);
|
||||
}
|
||||
};
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
if(node.e2ee){
|
||||
if(node.e2ee && node.matrixClient.initCrypto){
|
||||
node.log("Initializing crypto...");
|
||||
await node.matrixClient.initCrypto();
|
||||
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
|
||||
try {
|
||||
await node.matrixClient.initCrypto();
|
||||
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
|
||||
node.matrixClient.setCryptoTrustCrossSignedDevices(true); // false = manually verify sessions
|
||||
// await tryToUnlockSecretStorageWithDehydrationKey(this.matrixClient);
|
||||
} catch (e) {
|
||||
node.error("Failed to initialize crypto: " + e);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
node.log("Connecting to Matrix server...");
|
||||
await node.matrixClient.startClient({
|
||||
initialSyncLimit: 8
|
||||
@@ -394,29 +467,9 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We do a /whoami request before starting for a few reasons:
|
||||
* - validate our auth token
|
||||
* - make sure auth token belongs to provided node.userId
|
||||
* - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033)
|
||||
*/
|
||||
node.matrixClient.whoami()
|
||||
node.matrixClient.getAccountDataFromServer()
|
||||
.then(
|
||||
function(data) {
|
||||
if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) {
|
||||
node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.");
|
||||
}
|
||||
if('device_id' in data && data['device_id'] && !node.deviceId) {
|
||||
// if we have no device_id configured lets use the one
|
||||
// returned by /whoami for this access_token
|
||||
node.matrixClient.deviceId = data['device_id'];
|
||||
}
|
||||
|
||||
// make sure our userId matches the access token's
|
||||
if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) {
|
||||
node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`);
|
||||
return;
|
||||
}
|
||||
function() {
|
||||
run().catch((error) => node.error(error));
|
||||
},
|
||||
function(err) {
|
||||
@@ -437,6 +490,7 @@ module.exports = function(RED) {
|
||||
userId: { type: "text", required: true },
|
||||
accessToken: { type: "text", required: true },
|
||||
deviceId: { type: "text", required: false },
|
||||
secureStoragePassphrase: { type: "text", required: false },
|
||||
url: { type: "text", required: true }
|
||||
}
|
||||
});
|
||||
@@ -524,18 +578,10 @@ module.exports = function(RED) {
|
||||
* If a device ID is stored we will use that for the client
|
||||
*/
|
||||
function getStoredDeviceId(localStorage) {
|
||||
let deviceId = localStorage.getItem('my_device_id');
|
||||
if(deviceId === "null" || !deviceId) {
|
||||
return null;
|
||||
}
|
||||
return deviceId;
|
||||
return localStorage.getItem('my_device_id');
|
||||
}
|
||||
|
||||
function storeDeviceId(localStorage, deviceId) {
|
||||
if(!deviceId) {
|
||||
return false;
|
||||
}
|
||||
localStorage.setItem('my_device_id', deviceId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-create-edit-user">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-deactivate-user">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-join-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -30,25 +30,13 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
User must be an admin to use this endpoint.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-synapse-join-room">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-register">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-users">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-whois-user">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user