Home Tasker Two-Factor Authentication in NodeRED

Two-Factor Authentication in NodeRED

A way to introduce 2 form authentication in your personal automation setups.

Two-Factor Authentication is usually associated with access and security. It’s prompt, that you get on your mobile device when you are trying to log in to a Gmail account with the 2FA enabled. You can employ the same tactics for your NodeRED server and home automation! Let’s talk about Two-Factor Authentication in NodeRED.

Two-Factor Authentication: basics

When a login action is triggered 2FA issues a prompt to “Allow” or “Decline” the sign in from a new machine. Access to the secured account is only granted when that action is authorised on a secured device.

Two-Factor Authentication in NodeRED can work as a safety net. Selected automated actions can trigger the 2FA warning to authorise the action. To make this even more useful I introduced a timeout and a default timeout action.

Example:

This PC Dashboard uses 2FA to prevent desktop from being disconnected from AC.

A geo-fence trigger to open garage door can be prone to false-positive triggers. Since no one wants their garage to open without permission, sending a 2FA prompt to your mobile could prove beneficial. The 2FA notification would show up for 1 min when you are inside the geo-fence, and automatically stop opening the garage door unless authorised.

Two-Factor Authentication in NodeRED

To create your own Two-Factor Authentication in NodeRED you will need:

  • Tasker
  • AutoRemote
  • AutoNotification
  • AutoTools

These are the apps needed to create the Android responder system. NodeRED set up consist of a subflow and receiver node to deal with HTTP requests. The Two-Factor Authentication in NodeRED is customisable and you will be able to specify:

  • Message Title and Text
  • Timeout length
  • Timeout Action
  • Comes with a progress bar, automatic updates and a timer

NodeRED 2FA

Easy to use

I needed Two-Factor Authentication in NodeRED to be as easy to use as possible, and I managed to push all of the code inside a single subflow that you can drop between nodes in a need of 2FA.

To make it work, you will need to add an HTTP in node to send the authorisation calls from your mobile.

Message true|false received from the mobile device will allow the looped message to travel through the flow.

Subflow

To use the subflow, you need to set up environmental variables that will shape the message that is displayed on your Android device:

  • default2fa – timeout behaviour (true|false)
  • timer – how much time is allowed for the message
  • ARdevice – the name of the device for AR prompt (see Perfect Notification)
  • title – the title of the 2FA notification
  • text – the text of the 2FA notification
  • 2FApath – path for the HTTP in (keep unique for each instance)
  • ID – Notification ID (keep unique for each message)

In the subflow, there are 3 function nodes responsible for 2FA behaviour.

Set alarm
FUNCTION NODE: Set Alarm
var x = env.get("timer");
var timer = x *1000;
var time = new Date();
var timeseconds = time.getTime();
var alarm = timeseconds + timer;


flow.set("$parent.alarm", alarm);
flow.set("$parent.time", timeseconds);
flow.set("$parent.authentication", "pending");

return msg;

To make the 2FA timeout, I have monitor the current time and create the alarm at which the message expires. Quick time manipulation is needed to get the Epoch time and set the alarm (all done in ms). These are stored in flow variables for later

2FA Push
FUNCTION NODE: 2FA Push
var device = env.get("ARdevice");
var timer = env.get("timer");
var title = env.get("title");
var text = env.get("text");
var path = env.get("2FApath");
var ID = env.get("ID");
var default2fa = env.get("default2fa");
var status2fa;

if(default2fa === true){
    status2fa = "approved";
}

if(default2fa === false){
    status2fa = "declined";
}


var key = global.get(device);
var time = flow.get("$parent.time");
var alarm = flow.get("$parent.alarm");
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "2FA";
var body = {
    "title": {
        "title": title,
        "titleexpanded": title
  },
  "text": {
        "text": text,
        "textexpanded": text
  },
  "icons": {
    "navbaricon": "android.resource://net.dinglisch.android.taskerm/hl_device_access_new_account",
    "bigicon": "android.resource://net.dinglisch.android.taskerm/hl_device_access_new_account"
  },
  "notificationid": ID,
  "persistent": true,
  "priority": 1,
  "default2fa": default2fa,
  "status2fa" : status2fa,
  "path" : path,
  "timer": timer,  // in seconds
  "start": time,   // in ms
  "alarm": alarm,  // in ms
  "buttons": [
    {
      "button1": {
        "icon": "",
        "label": "Allow",
        "command": "2faresponse_true"
      },
     
      "button2": {
        "icon": "",
        "label": "Deny",
        "command": "2faresponse_false"
      }
    
    }
  ]
};
msg.data = body;
var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);

msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;
return msg;

At the same time, an AutoRemote message is sent to a mobile device. I modified the Perfect Notification script to support progress bar. I wanted to have a visual aid in the notification. Here is the new message body:

var body = {
     "title": {
         "title": title,
         "titleexpanded": title
   },
   "text": {
         "text": text,
         "textexpanded": text
   },
   "icons": {
     "navbaricon": "android.resource://net.dinglisch.android.taskerm/hl_device_access_new_account",
     "bigicon": "android.resource://net.dinglisch.android.taskerm/hl_device_access_new_account"
   },
   "notificationid": ID,
   "persistent": true,
   "priority": 1,
   "default2fa": default2fa,
   "status2fa" : status2fa,
   "path" : path,
   "timer": timer,  // in seconds
   "start": time,   // in ms
   "alarm": alarm,  // in ms
   "buttons": [
     {
       "button1": {
         "icon": "",
         "label": "Allow",
         "command": "2faresponse_true"
       },
   "button2": {     "icon": "",     "label": "Deny",     "command": "2faresponse_false"   } }
 ]
 };
Timer
FUNCTION NODE: Timer
var authentication = flow.get("$parent.authentication");
var default2fa = env.get("default2fa");
var alarm = flow.get("$parent.alarm");

var time = new Date();
var timeseconds = time.getTime();

//2FA message approved (1)
if(authentication === "approved"){    
    flow.set("$parent.authentication", "pending");
    return[msg, null, null];
}

//2FA message declined (2)
if(authentication === "declined"){
    flow.set("$parent.authentication", "pending");
    return[null, msg, null];
}

//pending for 2FA message (3)
if(authentication === "pending"){
    if(timeseconds < alarm){           //loop
        return[null, null, msg];
    }
    
    if(timeseconds => alarm){
        if(default2fa === true){         // timeout & default approved
            
            return[msg, null, null];
        }
        if(default2fa === false){        // timeout & default declined
            
            return[null, msg, null];
        }
    }
}

When a trigger hits the subflow, this function node sends the message to one of 3 destinations: to loop (3-sec loop cycle), to approve or decline. With every loop, a flow variable authentication is checked for updates.

If the response is received, on the next loop cycle the decision is made, otherwise, the function node will assign the outcome at the timeout based on the default setting of default2fa.

Tasker 2FA

In principle, the Tasker profile isn’t complicated. It receives AR message, using AutoTools JSON action an AutoNotification is created with a live progress bar and AutoApps commands.

On authentication, an HTTP request is sent to the server, a confirmation message is displayed.

2FA received

TASKER PROFILE: 2FA Message
Profile: 2FA message 
Restore: no
Event: AutoRemote [ Configuration:2FA ]
Enter: Timer 
    
    A1: Variable Set [ Name:%Test To:%arcomm Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:0 ] If [ %arcomm Set ]
    A2: AutoTools Json Read [ Configuration:Input Format: Json
        Json: %Test
        Fields: text.text,
        text.textexpanded,
        title.title,
        title.titleexpanded,
        icons.navbaricon,
        icons.bigicon,
        icons.iconexpanded,
        buttons[0].button1.label,
        buttons[0].button1.icon,
        buttons[0].button1.command,
        buttons[0].button2.label,
        buttons[0].button2.icon,
        buttons[0].button2.command,
        priority,
        start,
        timer,
        alarm,
        notificationid
        ,status2fa
        ,path
        Separator: , Timeout (Seconds):60 ] 
        A3: Variable Set [ Name:%NotificationID To:%notificationid Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:3 ] 
        A4: Variable Set [ Name:%2FApath To:%path Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:3 ] 
    
    A5: AutoNotification [ Configuration:Title: %title_title
        Text: %text_text
        Action on Dismiss: declined
        Icon: %icons_bigicon
        Status Bar Icon Manual: %icons_navbaricon
        Status Bar Text Size: 16
        Id: %notificationid
        Timeout: %timer
        Priority: 2
        Visibility: Private
        Time: %alarm
        Chronometer Count Down: true
        Max Progress: %timer
        Progress: %remaining
        Persistent: %persistent
        Title Expanded: %title_titleexpanded
        Text Expanded: %text_expanded
        Badge Type: None
        Separator: ,
        Button 1: FA2response=:=true
        Label 1: %buttons_button1_label
        Icon 1: check
        Button 2: FA2response=:=false
        Label 2: %buttons_button2_label
        Icon 2: navigation_cancel Timeout (Seconds):20 ] 
    
    A6: Variable Set [ Name:%ellapsed To:floor((%TIMEMS - %start)/1000) Recurse Variables:Off Do Maths:On Append:Off Max Rounding Digits:1 ] 
    A7: Variable Set [ Name:%remaining To:floor(%timer - %ellapsed) Recurse Variables:Off Do Maths:On Append:Off Max Rounding Digits:3 ] 
    
    A8: AutoNotification [ Configuration:Action on Dismiss: declined
        Icon: %icons_bigicon
        Status Bar Icon Manual: %icons_navbaricon
        Status Bar Text Size: 16
        Id: %notificationid
        Priority: 2
        Visibility: Private
        Use Chronometer: true
        Chronometer Count Down: true
        Progress: %remaining
        Category Id: Default Notifications
        Badge Type: None
        Separator: ,
        Button 1: FA2response=:=true
        Label 1: Allow
        Icon 1: check
        Button 2: FA2response=:=false
        Label 2: Deny
        Icon 2: navigation_cancel
        Update Notification: true Timeout (Seconds):20 ] 
    A9: Wait [ MS:190 Seconds:0 Minutes:0 Hours:0 Days:0 ] 
    A10: Goto [ Type:Action Label Number:1 Label:loop top ] If [ %ellapsed < %timer & %2FAreply ~ false ]
    A11: AutoNotification [ Configuration:Title: %title_title
        Text: Your actions had been automatically %status2fa
        Status Bar Icon: check
        Status Bar Text Size: 16
        Id: %notificationid
        Separator: , Timeout (Seconds):20 ] If [ %2FAreply ~ false ]
    A12: AutoNotification [ Configuration:Title: %title_title
        Text: Your actions had been %Action2fa
        Status Bar Icon: check
        Status Bar Text Size: 16
        Id: %notificationid
        Separator: , Timeout (Seconds):20 ] If [ %2FAreply ~ true ]
    A13: Variable Set [ Name:%2FAreply To:false Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:3 ]

This is a modified Perfect AutoNotification project. I have added variables for time, progress bar and chronometer to create a complete notification. AutoRemote passes the JSON body of the message to AutoTools JSON action.

With the exception of the ID and Path, these are used as local variables. ID and Path are saved globally, as I need these in the 2nd profile. To update the progress bar, I have to create a loop which checks the current time vs alarm:

IF %ellapsed < timer

and updates the AutoNotification. This loop stops if the timeout is reached, or the notification button is pressed. Lastly, a confirmation message is issued based on the timeout (with a correct default action) or as soon as the allow or decline is pressed.

2FA commands

TASKER PROFILE: 2FA Message
Profile: 2FA buttons 
Restore: no
Event: AutoApps Command [ Configuration:Command Filter: FA2response ]
Enter: 2FA response 
    A1: Variable Set [ Name:%2FAreply To:true 
        Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:3 ] 
    A2: Variable Set [ Name:%Action2fa To:denied. 
        Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:3 ] 
        If [ %aacomm ~ false ]
    A3: Variable Set [ Name:%Action2fa To:approved. 
        Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:3 ] 
        If [ %aacomm ~ true ]
    A4: AutoNotification Cancel [ Configuration:Id: %NotificationID Timeout (Seconds):20 ] 
    A5: HTTP Request [  Method:POST URL:https://%HTTPuser:%HTTPpass@%HOMESERVER:1880%2FApath 
        Headers: Query Parameters: 
        Body:{"authentication":%aacomm} 
        File To Send: File/Directory To Save With Output: 
        Timeout (Seconds):30 
        Trust Any Certificate:On ] 

Since I'm sending sensitive info via HTTP, I used this profile to enable sharing my file without compromising my security.

After capturing AutoApps command with a profile, the true|false message is sent via HTTP POST back to the NodeRED server. Note that to complete the request I need to know the path (%2FApath). This is why I set the %2FApath as a global variable.

I need to cancel the original AutoNotification message as well and to do so, I'm using the ID stored globally (%NotificationID).

Conclusion

Why am I doing this? I got NETIO connected power cables and one of the cables comes with a PC connector. From time to time, my desktop computer becomes inaccessible via remote desktop and the only way to fix this is to reboot it. I can kill the power and take advantage of the BIOS setting to restore the power on AC loss. For obvious reasons, I don't want this action to be performed accidentally! What would you use Two-Factor Authentication in NodeRED for? Let me know in this Reddit thread.

Project Download

Download project files here. Bear in mind that Patreon supporters have early access to project files and videos.

PayPal

Nothing says "Thank you" better than keeping my coffee jar topped up!

Patreon

Support me on Patreon and get an early access to tutorial files and videos.

image/svg+xml

Bitcoin (BTC)

Use this QR to keep me caffeinated in style with crypto-currency

New to Tasker?

Tasker Quick Start – Getting started with Tasker

0
From newb to not so newbie in 10 min

Best Tasker Projects

Tasker PC control – #Tutorial 3 (Apps menu)

0
Launch apps and websites from the mobile!

Tasker PC control – #Tutorial 2 (Volume Menu)

0
If you missed the part one, I strongly recommend you to check it out. It tells you all about the framework used,...

How to control your PC like a boss – Tasker Project

0
Control PC, and other devices from a custom notification system.

Bluetooth battery monitor – monitor any Bluetooth battery with Tasker

0
Measure the battery use of Bluetooth devices

The Minimal & Elegant giveaway WINNERS

0
Picking the winners live! Livestream and making a Tasker Winners project!

Essential Guides

Tasker: Seconds into DD:HH:MM:SS (dynamic)

0
It's time to.... ok it's a pun, but I will show you how to master time and convert seconds to DD:HH:MM:SS dynamically

4 ways to organise Tasker projects

0
Keep your Tasker tidy!

A better way to store Tasker credentials

0
The more clever way of managing credentials

Annoyed with dozens of AutoApps populating your app drawer? Here is a fix!

0
Clear your app drawer from the clutter in seconds

Putting AutoTools pie chart to a good use – SSID logger

0
Who wants a piece of the pie (chart)?