Node Red integration

I have broken the back of integrating node red with bass

  • Added partial-red and partial-red.html to display NR in an iframe
  • Added a global module in the settings.js NR file that gets loaded; listens for RED.events; and notifies hass of node reloads
  • Crossed reference respective config files

Todo: create entities dynamically based on node red nodes.

For security, I tried overcoming CORS by using haproxy and routing the backend based on path.

i.e. /nodered -> :1880 /* -> 8010 otherwise, and using a custom authentication scheme in node-red that uses the api_password from hass. However, this has not worked ideally as I can’t seem to resolve the single sign-on when starting a new iframe connection.

Have reverted to removing node-red authentication, and performing auth via haproxy which can check a Hosts file dynamically updated via hass based on authenticated hosts and timeouts.

Anyway, the reason for writing is this:

I wanted to expose some simple config parameters to the UI and am wondering if there isn’t a simpler way.

I tried creating a service call, but these don’t seem to return values that are unchanged.

So I created an entity and the service call updates the entity. However, I then have unwanted cards showing up in the UI!

Use case - use API to read custom config parameters via the UI. Anyone?

And if you want to integrate node-red, feel free to get in touch so I can share my solution. Still a bit of work to go.

thanks

1 Like

You could register your own API endpoint like this: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/api.py#L38 and return any data you want.

1 Like

Have refined my approach to avoid touching any core files. Single Sign on now entirely handled via nginx+lua.

(in node-red’s settings.js I parse the configuration.yaml, then sync the NR password with the hass password.

In summary, both NR and hass live behind a nginx firewall (currently exposing port 8888)

As I had to learn nginx+lua to write this, feel free to suggest improvements.

#
# nginx-lua config to provide single front end
# to hass (farm assistant) and nodered
#
# Assumes token based auth and shared access to
# api_password
#

	worker_processes  1;
	daemon off;

	error_log /dev/stderr debug;

	events {
	  worker_connections  255;
	}

	http {
	error_log /dev/stderr debug;
	access_log /dev/stdout;

	lua_need_request_body on;

	access_log /dev/stdout;
	  map $http_upgrade $connection_upgrade {
	    default upgrade;
	    '' close;
	  }

	  upstream nodered {
	    server 127.0.0.1:1880;
	  }

	  upstream fa {
	    server 127.0.0.1:8123;
	  }

	  server {
	   listen  8888;
    
    # custom auth as sub request
    # change user from admin???

    location /_nrauth {
        set $rargs  "client_id=node-red-editor&grant_type=password&scope=&username=admin&password=";
        rewrite_by_lua '
            ngx.var.rargs = ngx.var.rargs .. ngx.var.pass
            ngx.log(ngx.NOTICE,"Args :" .. ngx.var.rargs)
            ';

            internal;
            proxy_method POST;
            proxy_set_body $rargs;
            proxy_set_header Accept application/json;
            proxy_set_header Content-Type application/x-www-form-urlencoded;
            proxy_pass   http://127.0.0.1:1880/nodered/auth/token;
            proxy_redirect  http://127.0.0.1:1880/nodered/auth/token /;
            proxy_read_timeout 2s;
          
             # May not need or want to set Host. Should default to the above hostname.
            proxy_set_header          Host            $host;
            proxy_set_header          X-Real-IP       $remote_addr;
            proxy_set_header          X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
        }

    # default nodered handler

    location /nodered {
        set $token  "";
        access_by_lua '
           if ngx.var.cookie_access_token ~= nil then
            ngx.var.token = "Bearer " .. ngx.var.cookie_access_token
            ngx.log(ngx.NOTICE,"Node Red added header!")
           else
            ngx.log(ngx.NOTICE,"Node Red failed to add header!")
            ngx.log(ngx.ERR, "Failed to authenticate")
            ngx.redirect("http://127.0.0.1:8888/")
           end
        ';
        proxy_pass http://nodered;
        proxy_http_version 1.1;
        proxy_set_header Authorization $token;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location / {
      proxy_pass http://fa;
    }
 
 # Handle custom nodered login
  
    location /nodered/login {
        set $token ='';	
        set $pass ='';
        access_by_lua '
        ngx.log(ngx.NOTICE, "Node Red Login attempt!")

        if ngx.var.arg_api_password == nil  then
            ngx.ctx.red = 0
            ngx.log(ngx.ERR, "API Password not provided")
                return ngx.exit(403)
        end

        local ck = require("lcookies")
        local json = require("cjson")
        ngx.log(ngx.NOTICE,"Query string: [" .. ngx.var.arg_api_password .. "]")
        ngx.var.pass= ngx.var.arg_api_password;
      
        local res= ngx.location.capture (
                              "/_nrauth",
          {  always_forward_body = true, copy_all_vars = true}
         )
        ngx.log(ngx.NOTICE,"Result: [" .. res.status  .. "]")
        if string.sub(res.status,0,3) ~= "200" then
            ngx.ctx.red = 0
            ngx.log(ngx.ERR, "Failed to authenticate")
            return ngx.exit(403)
        end
        -- ngx.log(ngx.NOTICE,res.body)
        local tab = json.decode(res.body);
        if tab["access_token"] ~= nil then
            ngx.var.token =  tab["access_token"];
            local expires =  tab["expires_in"];
            local cooki = "access_token=" .. tab["access_token"]  .. "; Path=/"
            ck.add_cookie(cooki,expires)
            ngx.header["Authorization"] = "Bearer " .. ngx.var.token
            ngx.req.set_uri_args({})
        else
            ngx.log(ngx.ERR, "Failed to authenticate")
            return ngx.exit(403)
        end
       ';
    
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_pass http://127.0.0.1:1880/nodered;
    }

    # If cookie set to "" (log_out)
    # recreate, as cached partial-red does not reload on login

    location /api/stream {
       access_by_lua '
        ngx.log(ngx.NOTICE,"Refresh cookie attempt!")
        if ngx.var.request_method == ngx.HTTP_GET and ngx.var.cookie_access_token ~= nil and ngx.var.cookie_access_token == ""  then
           ngx.log(ngx.NOTICE,"Refresh cookie!")
           ngx.log(ngx.NOTICE,"Query string: [" .. ngx.var.arg_api_password .. "]")
           ngx.var.pass= ngx.var.arg_api_password;
          
           local res= ngx.location.capture (
                                  "/_nrauth",
              { always_forward_body = true, copy_all_vars = true }
            )
           
           if res.status ~= nil and string.sub(res.status,0,3) ~= "200" then
                ngx.log(ngx.NOTICE,"Status returned: [" ..  string.sub(res.status,0,3)   .. "]")
                ngx.log(ngx.ERR, "Failed to authenticate")
            else	
                local ck = require("lcookies")
                local json = require("cjson")
                local tab = json.decode(res.body);
                ngx.log(ngx.NOTICE,res.body)
                if tab["access_token"] ~= nil then
                    ngx.log(ngx.NOTICE, "Updated cookie")
                    ngx.var.token =  tab["access_token"];
                    local expires =  tab["expires_in"];
                    local cooki = "access_token=" .. tab["access_token"]  .. "; Path=/"
                    ck.add_cookie(cooki,expires)
                    ngx.redirect("http://127.0.0.1:8888/api/stream?" .. ngx.var.query_string)
                    ngx.exit(ngx.OK)
                end
            end	
        end
       ';

        proxy_pass http://fa;
    }
    
    # Handle hass logout
    # remove cookie
     
    location /api/log_out {
        access_by_lua '
            ngx.log(ngx.NOTICE,"Logout!")
            if ngx.var.cookie_access_token ~= nil then
                local ck = require("lcookies")
                ngx.log(ngx.NOTICE,"Node Red reset cookie!")
                local cooki = "access_token=; Path=/"
                ck.add_cookie(cooki,0)
            end
           ';

        proxy_pass http://fa;
    }
  }
}