Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2FA flow with start-call and send-sms nodes #14

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/2FA flow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"id":"d87bccd2.db412","type":"subflow","name":"find token","info":"","category":"","in":[{"x":60,"y":80,"wires":[{"id":"c389406d.65d7b"}]}],"out":[{"x":620,"y":40,"wires":[{"id":"1981a995.d53996","port":0}]},{"x":620,"y":120,"wires":[{"id":"1981a995.d53996","port":1}]}],"env":[],"color":"#DDAA99"},{"id":"ef751ae1.b79828","type":"mongodb3 in","z":"d87bccd2.db412","service":"_ext_","configNode":"f465d170.f70758","name":"find token","collection":"tokens","operation":"findOne","x":340,"y":80,"wires":[["1981a995.d53996"]]},{"id":"c389406d.65d7b","type":"function","z":"d87bccd2.db412","name":"get data","func":"msg.payload = {};\nmsg.payload.number = msg.req.params.number.replace(/\\+/g,'');\nmsg.payload.confirmedAt = null;\nmsg.payload.expiredAt = null;\nif (msg.req.params.token) {\n msg.payload.token = msg.req.params.token;\n}\nreturn msg;\n","outputs":1,"noerr":0,"x":200,"y":80,"wires":[["ef751ae1.b79828"]]},{"id":"1981a995.d53996","type":"switch","z":"d87bccd2.db412","name":"found?","property":"payload","propertyType":"msg","rules":[{"t":"hask","v":"number","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":480,"y":80,"wires":[[],[]],"outputLabels":["yes","no"]},{"id":"befaaff5.bfebb","type":"subflow","name":"expire tokens","info":"","category":"","in":[{"x":60,"y":80,"wires":[{"id":"af481159.ac824"}]}],"out":[{"x":520,"y":80,"wires":[{"id":"c6469928.309018","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"af481159.ac824","type":"function","z":"befaaff5.bfebb","name":"find tokens","func":"var now = new Date();\nvar tokenLifetime = msg.env.tokenLifetime;\nvar expiryDate = new Date(now - tokenLifetime * 1000);\nvar filter = { expiredAt: null, confirmedAt: null, createdAt: { $lt: expiryDate } };\nvar update = { $set: { expiredAt: now } };\nmsg.payload = [filter, update];\nreturn msg;\n","outputs":1,"noerr":0,"x":190,"y":80,"wires":[["c6469928.309018"]]},{"id":"c6469928.309018","type":"mongodb3 in","z":"befaaff5.bfebb","service":"_ext_","configNode":"f465d170.f70758","name":"expire tokens","collection":"tokens","operation":"updateMany","x":380,"y":80,"wires":[[]]},{"id":"6570c2e7.7851dc","type":"subflow","name":"auth","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"8527b231.3fad"}]}],"out":[{"x":360,"y":40,"wires":[{"id":"8527b231.3fad","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"8527b231.3fad","type":"function","z":"6570c2e7.7851dc","name":"HTTP Basic Auth","func":"var authorizationHeaderValue = msg.env.authorizationHeaderValue;\nif (msg.req.headers.authorization == authorizationHeaderValue) {\n return [msg, null];\n}\nelse {\n return [null, msg];\n}\n","outputs":2,"noerr":0,"x":190,"y":80,"wires":[[],["e529c638.327be8"]]},{"id":"e529c638.327be8","type":"http response","z":"6570c2e7.7851dc","name":"","statusCode":"401","headers":{},"x":400,"y":120,"wires":[]},{"id":"baab4476.d6ef08","type":"subflow","name":"config","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"a0134d3e.7cffd"}]}],"out":[{"x":360,"y":80,"wires":[{"id":"a0134d3e.7cffd","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"a0134d3e.7cffd","type":"function","z":"baab4476.d6ef08","name":"SET ENV VARS!","func":"// EDIT FROM HERE\n// vvvvvvvvvvvvvv\n\n\n// define environment variables\nvar fromNumber = \"TWILIO_NUMBER\";\nvar tokenLength = 6;\nvar tokenLifetime = 60; // in seconds\nvar httpBasicAuthUsername = \"admin\";\nvar httpBasicAuthPassword = \"admin\";\n\n\n// ^^^^^^^\n// TO HERE\n\n// set environment variables\nmsg.env = msg.env || {};\nmsg.env.fromNumber = fromNumber;\nmsg.env.tokenLength = tokenLength;\nmsg.env.tokenLifetime = tokenLifetime;\n\n// authorization header value\nvar authorizationHeaderValue = \"Basic \" + Buffer.from(httpBasicAuthUsername + \":\" + httpBasicAuthPassword).toString('base64');\nmsg.env.authorizationHeaderValue = authorizationHeaderValue;\n\nreturn msg;\n","outputs":1,"noerr":0,"x":200,"y":80,"wires":[[]]},{"id":"a7dc6ee3.1b8ce","type":"tab","label":"2FA","disabled":false,"info":""},{"id":"ed27613c.168c4","type":"http in","z":"a7dc6ee3.1b8ce","name":"","url":"/2fa/:number","method":"post","upload":false,"swaggerDoc":"","x":110,"y":180,"wires":[["6942db0c.af7ee4"]]},{"id":"35b540a.41261c","type":"mongodb3 in","z":"a7dc6ee3.1b8ce","service":"_ext_","configNode":"f465d170.f70758","name":"store number","collection":"tokens","operation":"insertOne","x":1050,"y":220,"wires":[["dceffda6.81c9e"]]},{"id":"7a63b83b.20c0c8","type":"http response","z":"a7dc6ee3.1b8ce","name":"","statusCode":"200","headers":{},"x":1640,"y":140,"wires":[]},{"id":"df83e441.e659b8","type":"http in","z":"a7dc6ee3.1b8ce","name":"","url":"/2fa/:number/verify/:token","method":"post","upload":false,"swaggerDoc":"","x":150,"y":480,"wires":[["c21bd8db.697568"]]},{"id":"aa8dfb9c.3fdd08","type":"mongodb3 in","z":"a7dc6ee3.1b8ce","service":"_ext_","configNode":"f465d170.f70758","name":"mark as confirmed","collection":"tokens","operation":"findOneAndUpdate","x":1090,"y":440,"wires":[["5c421358.2e124c"]]},{"id":"a64af8ad.2f6f88","type":"http response","z":"a7dc6ee3.1b8ce","name":"","statusCode":"404","headers":{},"x":1140,"y":520,"wires":[]},{"id":"d7832b93.c7fd28","type":"http response","z":"a7dc6ee3.1b8ce","name":"","statusCode":"200","headers":{},"x":1460,"y":440,"wires":[]},{"id":"6c297bf5.7d9ce4","type":"function","z":"a7dc6ee3.1b8ce","name":"","func":"var filter = msg.payload._id;\nmsg.payload = [{ _id: filter }, { $set: { confirmedAt: new Date() } }];\nreturn msg;","outputs":1,"noerr":0,"x":930,"y":440,"wires":[["aa8dfb9c.3fdd08"]]},{"id":"77e62b1e.19c904","type":"comment","z":"a7dc6ee3.1b8ce","name":"Setup instructions","info":"### Twilio configuration\n\nClick `start-call` or `send-sms` node and configure Twilio account.\n\n### 2FA configuration\n\nClick any `config` node twice and a new window should appear. Click `Edit subflow template` button in the left top corner of the window and another window should appear. You should see `SET ENV VARS!` function node. Click it twice and set environment variables inside.\n\n### MongoDB database (default)\n\nMongoDB database is provided out-of-the-box as mLab MongoDB add-on on Heroku (https://elements.heroku.com/addons/mongolab).\n\nThere is no configuration needed unless you would like to change something.\n\nYou can browse MongoDB collections from your Heroku account via browser or by connecting to the database.\n","x":110,"y":60,"wires":[]},{"id":"1fd49d4c.e30383","type":"function","z":"a7dc6ee3.1b8ce","name":"get token","func":"msg.req.params.token = msg.payload.token;\nreturn msg;","outputs":1,"noerr":0,"x":880,"y":140,"wires":[["dceffda6.81c9e"]]},{"id":"f712c3b6.219fd","type":"function","z":"a7dc6ee3.1b8ce","name":"create token","func":"var tokenLength = msg.env.tokenLength;\nvar multiplier = 10 ** tokenLength;\nvar offset = 0.1 * multiplier;\nvar range = 0.9 * multiplier;\nmsg.payload = {};\nmsg.payload.number = msg.req.params.number.replace(/\\+/g,'');\nmsg.payload.token = Math.floor(offset + Math.random() * range).toString();\nmsg.payload.createdAt = new Date();\nmsg.req.params.token = msg.payload.token;\nreturn msg;\n","outputs":1,"noerr":0,"x":890,"y":220,"wires":[["35b540a.41261c"]]},{"id":"9a9a5486.7fccb8","type":"function","z":"a7dc6ee3.1b8ce","name":"prepare response","func":"msg.payload = \"\"\nmsg.headers = {}\nreturn msg;","outputs":1,"noerr":0,"x":1450,"y":140,"wires":[["7a63b83b.20c0c8"]]},{"id":"5c421358.2e124c","type":"function","z":"a7dc6ee3.1b8ce","name":"prepare response","func":"msg.payload = \"\"\nmsg.headers = {}\nreturn msg;","outputs":1,"noerr":0,"x":1290,"y":440,"wires":[["d7832b93.c7fd28"]]},{"id":"a1736919.c81128","type":"function","z":"a7dc6ee3.1b8ce","name":"prepare response","func":"msg.payload = \"\"\nmsg.headers = {}\nreturn msg;","outputs":1,"noerr":0,"x":970,"y":520,"wires":[["a64af8ad.2f6f88"]]},{"id":"354b80d3.c6694","type":"http in","z":"a7dc6ee3.1b8ce","name":"","url":"/2fa/:number/call","method":"post","upload":false,"swaggerDoc":"","x":120,"y":260,"wires":[["6942db0c.af7ee4"]]},{"id":"dceffda6.81c9e","type":"switch","z":"a7dc6ee3.1b8ce","name":"call or sms?","property":"req.url","propertyType":"msg","rules":[{"t":"cont","v":"call","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":1230,"y":140,"wires":[["c846b949.24c028","9a9a5486.7fccb8"],["9a9a5486.7fccb8","6abafcd1.77ff94"]]},{"id":"adbb60c6.456a5","type":"say","z":"a7dc6ee3.1b8ce","name":"say token","tts":"613d6470.8a004c","output":"off","outputs":0,"x":2020,"y":40,"wires":[]},{"id":"b85c8ed9.a0cd9","type":"function","z":"a7dc6ee3.1b8ce","name":"prepare token","func":"msg.payload.token = Array.from(msg.payload.token).join(\", \");\nreturn msg;","outputs":1,"noerr":0,"x":1860,"y":40,"wires":[["adbb60c6.456a5"]]},{"id":"452ed668.2047b8","type":"say","z":"a7dc6ee3.1b8ce","name":"say token not found","tts":"d50ce25c.2be51","output":"off","outputs":0,"x":1880,"y":120,"wires":[]},{"id":"a16114fc.5a4cf8","type":"switch","z":"a7dc6ee3.1b8ce","name":"check fallback","property":"req.query.fallback","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":1580,"y":200,"wires":[["3b0560e0.5aec6"]]},{"id":"3b0560e0.5aec6","type":"function","z":"a7dc6ee3.1b8ce","name":"set delay","func":"msg.delay = msg.req.query.fallback * 1000;\nreturn msg;","outputs":1,"noerr":0,"x":1740,"y":200,"wires":[["f84e7cc4.5c3d3"]]},{"id":"f84e7cc4.5c3d3","type":"delay","z":"a7dc6ee3.1b8ce","name":"start","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1870,"y":200,"wires":[["9654e42b.11a468"]]},{"id":"71bcab9a.fd68a4","type":"function","z":"a7dc6ee3.1b8ce","name":"get token","func":"msg.req.params.token = msg.payload.token;\nreturn msg;","outputs":1,"noerr":0,"x":2180,"y":200,"wires":[["9e0acfb0.d3281"]]},{"id":"9e0acfb0.d3281","type":"link out","z":"a7dc6ee3.1b8ce","name":"","links":["3786b319.1476fc"],"x":2275,"y":200,"wires":[]},{"id":"3786b319.1476fc","type":"link in","z":"a7dc6ee3.1b8ce","name":"","links":["9e0acfb0.d3281"],"x":1275,"y":80,"wires":[["c846b949.24c028"]]},{"id":"6942db0c.af7ee4","type":"subflow:baab4476.d6ef08","z":"a7dc6ee3.1b8ce","name":"","env":[],"x":290,"y":180,"wires":[["f35c37b3.55a408"]]},{"id":"f35c37b3.55a408","type":"subflow:6570c2e7.7851dc","z":"a7dc6ee3.1b8ce","name":"","env":[],"x":410,"y":180,"wires":[["442f357a.e6b20c"]]},{"id":"c21bd8db.697568","type":"subflow:baab4476.d6ef08","z":"a7dc6ee3.1b8ce","name":"","x":350,"y":480,"wires":[["b6b50b31.fdb378"]]},{"id":"b6b50b31.fdb378","type":"subflow:6570c2e7.7851dc","z":"a7dc6ee3.1b8ce","name":"","x":470,"y":480,"wires":[["e88fc361.3faea"]]},{"id":"442f357a.e6b20c","type":"subflow:befaaff5.bfebb","z":"a7dc6ee3.1b8ce","name":"","env":[],"x":560,"y":180,"wires":[["2113950f.72b08a"]]},{"id":"e88fc361.3faea","type":"subflow:befaaff5.bfebb","z":"a7dc6ee3.1b8ce","name":"","x":620,"y":480,"wires":[["122f5dba.3e6952"]]},{"id":"122f5dba.3e6952","type":"subflow:d87bccd2.db412","z":"a7dc6ee3.1b8ce","name":"","env":[],"x":780,"y":480,"wires":[["6c297bf5.7d9ce4"],["a1736919.c81128"]]},{"id":"3ee6cf20.c3d6e","type":"subflow:d87bccd2.db412","z":"a7dc6ee3.1b8ce","name":"","env":[],"x":1680,"y":80,"wires":[["b85c8ed9.a0cd9"],["452ed668.2047b8"]]},{"id":"2113950f.72b08a","type":"subflow:d87bccd2.db412","z":"a7dc6ee3.1b8ce","name":"","env":[],"x":720,"y":180,"wires":[["1fd49d4c.e30383"],["f712c3b6.219fd"]]},{"id":"9654e42b.11a468","type":"subflow:d87bccd2.db412","z":"a7dc6ee3.1b8ce","name":"","x":2020,"y":200,"wires":[["71bcab9a.fd68a4"],[]]},{"id":"31e0d68e.ab470a","type":"function","z":"a7dc6ee3.1b8ce","name":"","func":"msg.req.params.number = msg.payload.To.substr(1);\nreturn msg;\n","outputs":1,"noerr":0,"x":1550,"y":80,"wires":[["3ee6cf20.c3d6e"]]},{"id":"c846b949.24c028","type":"start-call","z":"a7dc6ee3.1b8ce","name":"","account":"3fc3754f.17a8ba","from":"env.fromNumber","fromType":"msg","to":"req.params.number","toType":"msg","x":1420,"y":80,"wires":[["31e0d68e.ab470a"]]},{"id":"6abafcd1.77ff94","type":"send-sms","z":"a7dc6ee3.1b8ce","name":"","account":"3fc3754f.17a8ba","text":"Your authentication token: {{req.params.token}}","from":"env.fromNumber","fromType":"msg","to":"req.params.number","toType":"msg","x":1420,"y":200,"wires":[["a16114fc.5a4cf8"]]},{"id":"f465d170.f70758","type":"mongodb3","z":null,"uri":"${MONGODB_URI}","name":"","options":"","parallelism":"-1"},{"id":"613d6470.8a004c","type":"tts","z":"","name":"2FA token","text":"Your authentication token is: {{payload.token}}.","language":"en-US","voice":"alice"},{"id":"d50ce25c.2be51","type":"tts","z":"","name":"2FA token not found","text":"Token for your number was not found.","language":"en-US","voice":"alice"},{"id":"3fc3754f.17a8ba","type":"account","z":"","name":"twilio"}]
2 changes: 1 addition & 1 deletion examples/All flows.json

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions nodes/sms/send-sms.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script type="text/javascript">
RED.nodes.registerType('send-sms', {
category: 'twilio sms',
color: '#a6bbcf',
defaults: {
name: { value: '' },
account: { value: '', type: 'account' },
text: { value: '', required: true },
from: { value: '', required: true, validate: RED.validators.typedInput('fromType') },
fromType: { value: 'num' },
to: { value: '', required: true, validate: RED.validators.typedInput('toType') },
toType: { value: 'num' },
},
inputs: 1,
outputs: 1,
icon: 'file.png',
label: function() {
return this.name || 'send-sms';
},
oneditprepare: function() {
$('#node-input-from').typedInput({ default: this.fromType || 'num', types: ['num', 'msg'] });
$('#node-input-to').typedInput({ default: this.toType || 'num', types: ['num', 'msg'] });
},
oneditsave: function() {
this.fromType = $('#node-input-from').typedInput('type');
this.toType = $('#node-input-to').typedInput('type');
},
});
</script>

<script type="text/x-red" data-template-name="send-sms">
<div class="form-row">
<label for="node-input-account"><i class="fa fa-user"></i> Account</label>
<input type="text" id="node-input-account">
</div>
<div class="form-row">
<label for="node-input-text"><i class="fa fa-align-left"></i> Text</label>
<textarea rows="10" style="width: 70%;" id="node-input-text"/>
</div>
<div class="form-row">
<label for="node-input-from"><i class="fa fa-tag"></i> From</label>
<input type="text" id="node-input-from" style="width: 70%"/>
</div>
<div class="form-row">
<label for="node-input-to"><i class="fa fa-tag"></i> To</label>
<input type="text" id="node-input-to" style="width: 70%"/>
</div>
<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>
</script>

<script type="text/x-red" data-help-name="send-sms">
<p>Send an SMS.</p>
</script>
49 changes: 49 additions & 0 deletions nodes/sms/send-sms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module.exports = function(RED) {
'use strict';
var renderTemplate = require('../../utils/renderTemplate.js');

function SendSmsNode(config) {
RED.nodes.createNode(this, config);
this.account = RED.nodes.getNode(config.account);
this.text = config.text;
this.from = config.from;
this.fromType = config.fromType;
this.to = config.to;
this.toType = config.toType;
var node = this;

if (!node.account) {
this.warn('missing account configuration');
return;
}

var client = require('twilio')(node.account.credentials.sid, node.account.credentials.token);
client.api.baseUrl = node.account.credentials.apiUrl;

node.on('input', function(msg) {
var fromNumber;
var toNumber;
if (node.fromType === 'msg') {
fromNumber = RED.util.getMessageProperty(msg, node.from);
} else {
fromNumber = node.from;
}
if (node.toType === 'msg') {
toNumber = RED.util.getMessageProperty(msg, node.to);
} else {
toNumber = node.to;
}
client.messages
.create({
body: renderTemplate(msg, node.text),
from: fromNumber,
to: toNumber,
})
.then(message => {
msg.payload = message;
node.send(msg);
});
});
}
RED.nodes.registerType('send-sms', SendSmsNode);
};
51 changes: 51 additions & 0 deletions nodes/voice/start-call.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script type="text/javascript">
RED.nodes.registerType('start-call', {
category: 'twilio voice',
color: '#a6bbcf',
defaults: {
name: { value: '' },
account: { value: '', type: 'account' },
from: { value: '', required: true, validate: RED.validators.typedInput('fromType') },
fromType: { value: 'num' },
to: { value: '', required: true, validate: RED.validators.typedInput('toType') },
toType: { value: 'num' },
},
inputs: 1,
outputs: 1,
icon: 'file.png',
label: function() {
return this.name || 'start-call';
},
oneditprepare: function() {
$('#node-input-from').typedInput({ default: this.fromType || 'num', types: ['num', 'msg'] });
$('#node-input-to').typedInput({ default: this.toType || 'num', types: ['num', 'msg'] });
},
oneditsave: function() {
this.fromType = $('#node-input-from').typedInput('type');
this.toType = $('#node-input-to').typedInput('type');
},
});
</script>

<script type="text/x-red" data-template-name="start-call">
<div class="form-row">
<label for="node-input-account"><i class="fa fa-user"></i> Account</label>
<input type="text" id="node-input-account">
</div>
<div class="form-row">
<label for="node-input-from"><i class="fa fa-tag"></i> From</label>
<input type="text" id="node-input-from" style="width: 70%"/>
</div>
<div class="form-row">
<label for="node-input-to"><i class="fa fa-tag"></i> To</label>
<input type="text" id="node-input-to" style="width: 70%"/>
</div>
<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>
</script>

<script type="text/x-red" data-help-name="start-call">
<p>Start a call.</p>
</script>
Loading