nodered-contrib-saml-flow
This flow allows usage of SAML within node-red.
It depends on https://www.npmjs.com/package/saml2-js. Pls install the module to use this flow.
It has been created by the greatest of all saml guru, addict and insane man you would ever imagine: Fanshan (https://github.com/fanshan)
Thank you for this !
[{"id":"95d56a5d.e5ccf8","type":"http in","z":"669c0d6d.d65b14","name":"","url":"/login","method":"get","upload":false,"swaggerDoc":"","x":100,"y":100,"wires":[["5ec98336.1b3e7c"]]},{"id":"62e64d50.e57654","type":"http response","z":"669c0d6d.d65b14","name":"","statusCode":"303","headers":{},"x":520,"y":100,"wires":[]},{"id":"5ec98336.1b3e7c","type":"function","z":"669c0d6d.d65b14","name":"Create AuthnResquest","func":"const saml2 = global.get('saml2');\nconst uuid = global.get('uuid');\nconst Memcached = global.get('memcached');\n\nconst relay_state = uuid.v4();\n\nconst host = msg.req.headers.host;\n\nconst idp = new saml2.IdentityProvider(global.get('idp_options'));\nconst sp = new saml2.ServiceProvider(global.get('sp_options')[host]);\n \nconst memcached = new Memcached(global.get('memcached_settings').hosts);\n\nmemcached.set(\n global.get('memcached_settings').key_prefix + 'ReturnTo_' + relay_state,\n msg.req.query.ReturnTo || '/',\n global.get('login_session').ttl,\n error => {}\n);\n\nmemcached.end();\n\nsp.create_login_request_url(\n idp,\n {\n relay_state: relay_state\n },\n (error, login_url, request_id) => {\n msg.headers = {\n Location: login_url\n };\n node.send(msg);\n \n return;\n }\n);","outputs":1,"noerr":0,"initialize":"","finalize":"","x":320,"y":100,"wires":[["62e64d50.e57654"]]},{"id":"717f5d0b.b47f5c","type":"http in","z":"669c0d6d.d65b14","name":"","url":"/saml/postResponse","method":"post","upload":false,"swaggerDoc":"","x":150,"y":260,"wires":[["965c3f76.914d"]]},{"id":"8f2c800f.bf5ee","type":"http response","z":"669c0d6d.d65b14","name":"","statusCode":"303","headers":{},"x":840,"y":240,"wires":[]},{"id":"965c3f76.914d","type":"function","z":"669c0d6d.d65b14","name":"Read SamlResponse","func":"const saml2 = global.get('saml2');\nconst url = global.get('url');\nconst uuid = global.get('uuid');\nconst Memcached = global.get('memcached');\n\nconst host = msg.req.headers.host;\n\nconst idp = new saml2.IdentityProvider(global.get('idp_options'));\nconst sp = new saml2.ServiceProvider(global.get('sp_options')[host]);\n\nsp.post_assert(\n idp,\n {\n request_body: msg.payload\n },\n (error, response) => {\n if (error) {\n msg.error = error;\n node.send(msg);\n \n return;\n }\n \n const memcached = new Memcached(global.get('memcached_settings').hosts);\n \n memcached.get(\n global.get('memcached_settings').key_prefix + 'ReturnTo_' + msg.payload.RelayState,\n function (error, data) {\n if (error) {\n msg.error = error;\n node.send(msg);\n \n return;\n }\n \n let sessionValue = uuid.v4();\n \n memcached.set(\n global.get('memcached_settings').key_prefix + sessionValue,\n response,\n global.get('login_session').ttl,\n error => {}\n );\n \n memcached.end();\n \n const entityId = global.get('sp_options')[host].entity_id;\n \n msg.cookies = {};\n \n msg.cookies[global.get('login_session').cookie_user_name] = {\n value: response.user.attributes.user_entity[0],\n domain: new url.parse(entityId).hostname,\n maxAge: global.get('login_session').ttl * 1000\n };\n \n msg.cookies[global.get('login_session').cookie_name] = {\n value: sessionValue,\n domain: new url.parse(entityId).hostname,\n maxAge: global.get('login_session').ttl * 1000\n };\n \n msg.headers = {\n Location: entityId + (data === undefined ? '' : data)\n };\n \n node.send(msg);\n \n return;\n }\n );\n }\n);","outputs":1,"noerr":0,"initialize":"","finalize":"","x":420,"y":260,"wires":[["1bdad9d1.75c40e"]]},{"id":"fe55ea8.0b39318","type":"comment","z":"669c0d6d.d65b14","name":"Read SamlResponse","info":"","x":140,"y":200,"wires":[]},{"id":"1bdad9d1.75c40e","type":"switch","z":"669c0d6d.d65b14","name":"","property":"error","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":630,"y":260,"wires":[["8f2c800f.bf5ee"],["bb009eb1.eabb38"]]},{"id":"bdfc04b1.25b8b8","type":"http response","z":"669c0d6d.d65b14","name":"","statusCode":"500","headers":{},"x":1040,"y":300,"wires":[]},{"id":"bb009eb1.eabb38","type":"change","z":"669c0d6d.d65b14","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"error","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":300,"wires":[["bdfc04b1.25b8b8"]]},{"id":"925aa566.663dc8","type":"http in","z":"669c0d6d.d65b14","name":"","url":"/saml/logout","method":"get","upload":false,"swaggerDoc":"","x":120,"y":500,"wires":[["3f84fc9f.1a61fc"]]},{"id":"3f84fc9f.1a61fc","type":"switch","z":"669c0d6d.d65b14","name":"","property":"req.query.ReturnTo","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":290,"y":500,"wires":[["84324e17.e089f8"],["edf5bd02.2e3c1"]]},{"id":"7d139601.ba8bb8","type":"comment","z":"669c0d6d.d65b14","name":"Logout endpoint","info":"","x":120,"y":440,"wires":[]},{"id":"edf5bd02.2e3c1","type":"function","z":"669c0d6d.d65b14","name":"Create SAML Logout Request","func":"const saml2 = global.get('saml2');\nconst Memcached = global.get('memcached');\nconst uuid = global.get('uuid');\n\nconst host = msg.req.headers.host;\n \nconst idp = new saml2.IdentityProvider(global.get('idp_options'));\nconst sp = new saml2.ServiceProvider(global.get('sp_options')[host]);\n\nconst memcached = new Memcached(global.get('memcached_settings').hosts);\n\nif (!(global.get('login_session').cookie_name in msg.req.cookies)) {\n msg.headers = {\n Location: global.get('sp_options')[host].entity_id\n };\n \n return msg;\n}\n\nmemcached.get(\n global.get('memcached_settings').key_prefix + msg.req.cookies[global.get('login_session').cookie_name],\n (error, data) => {\n if (!error && data !== undefined) {\n sp.create_logout_request_url(\n idp,\n {\n name_id: data.user.name_id,\n relay_state: uuid.v4()\n },\n (error, request_url) => {\n msg.headers = {\n Location: request_url\n };\n node.send(msg);\n \n return;\n }\n );\n } else {\n msg.headers = {\n Location: global.get('sp_options')[host].entity_id\n };\n node.send(msg);\n \n return;\n }\n }\n);\n\nmemcached.end();","outputs":1,"noerr":0,"x":650,"y":660,"wires":[["a66b5f7d.96046"]]},{"id":"84324e17.e089f8","type":"switch","z":"669c0d6d.d65b14","name":"","property":"req.query","propertyType":"msg","rules":[{"t":"hask","v":"SAMLRequest","vt":"str"},{"t":"hask","v":"SAMLResponse","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":430,"y":480,"wires":[["781ee67e.8617f"],["5ea26b08.af76f4"],["637d9f66.55c02"]]},{"id":"f90b7220.31d28","type":"http response","z":"669c0d6d.d65b14","name":"","statusCode":"400","headers":{},"x":580,"y":600,"wires":[]},{"id":"637d9f66.55c02","type":"change","z":"669c0d6d.d65b14","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"error\":\"SAMLRequest or SAMLResponse missing.\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":600,"y":540,"wires":[["f90b7220.31d28"]]},{"id":"781ee67e.8617f","type":"function","z":"669c0d6d.d65b14","name":"Read SAML Logout Request","func":"const saml2 = global.get('saml2');\n\nconst host = msg.req.headers.host;\n \nconst idp = new saml2.IdentityProvider(global.get('idp_options'));\nconst sp = new saml2.ServiceProvider(global.get('sp_options')[host]);\n\nsp.redirect_assert(\n idp,\n {\n request_body: msg.payload\n },\n (error, response) => {\n if (error) {\n msg.error = error;\n node.send(msg);\n \n return;\n }\n \n sp.create_logout_response_url(\n idp,\n {\n in_response_to: response.response_header.id\n },\n (error, request_url) => {\n if (error) {\n msg.error = error;\n node.send(msg);\n \n return;\n }\n \n msg.headers = {\n Location: request_url\n };\n \n node.send(msg);\n \n return;\n }\n );\n \n return;\n }\n);","outputs":1,"noerr":0,"initialize":"","finalize":"","x":640,"y":420,"wires":[["a66b5f7d.96046"]]},{"id":"5ea26b08.af76f4","type":"function","z":"669c0d6d.d65b14","name":"Read SAML Logout Response","func":"const saml2 = global.get('saml2');\n\nconst host = msg.req.headers.host;\n \nconst idp = new saml2.IdentityProvider(global.get('idp_options'));\nconst sp = new saml2.ServiceProvider(global.get('sp_options')[host]);\n\nsp.redirect_assert(\n idp,\n {\n request_body: msg.payload\n },\n (error, response) => {\n if (error) {\n msg.error = error;\n node.send(msg);\n \n return;\n }\n \n msg.headers = {\n Location: global.get('sp_options')[host].entity_id\n };\n node.send(msg);\n \n return;\n }\n);","outputs":1,"noerr":0,"x":650,"y":480,"wires":[["a66b5f7d.96046"]]},{"id":"a66b5f7d.96046","type":"function","z":"669c0d6d.d65b14","name":"Destroy Session","func":"const Memcached = global.get('memcached');\n\nif (global.get('login_session').cookie_name in msg.req.cookies) {\n const memcached = new Memcached(global.get('memcached_settings').hosts);\n memcached.del(global.get('memcached_settings').key_prefix + msg.req.cookies[global.get('login_session').cookie_name], error => {});\n memcached.end();\n}\n\nmsg.cookies = {};\n\nmsg.cookies[global.get('login_session').cookie_user_name] = null;\nmsg.cookies[global.get('login_session').cookie_name] = null;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":940,"y":420,"wires":[["1bdad9d1.75c40e"]]},{"id":"f4584711.c7506","type":"comment","z":"669c0d6d.d65b14","name":"Test session and renew","info":"","x":140,"y":740,"wires":[]},{"id":"8acf34cf.0a022","type":"http in","z":"669c0d6d.d65b14","name":"","url":"/session","method":"get","upload":false,"swaggerDoc":"","x":110,"y":820,"wires":[["1e885bc4.56c194"]]},{"id":"1e885bc4.56c194","type":"function","z":"669c0d6d.d65b14","name":"Read session and renew","func":"const Memcached = global.get('memcached');\nconst url = global.get('url');\n\nconst host = msg.req.headers.host;\n\nif (global.get('login_session').cookie_name in msg.req.cookies) {\n const memcached = new Memcached(global.get('memcached_settings').hosts);\n \n memcached.get(\n global.get('memcached_settings').key_prefix + msg.req.cookies[global.get('login_session').cookie_name],\n (error, data) => {\n if (!error && data !== undefined) {\n const entityId = global.get('sp_options')[host].entity_id;\n \n msg.cookies = {};\n \n msg.cookies[global.get('login_session').cookie_user_name] = {\n value: data.user.attributes.user_entity[0],\n domain: new url.parse(entityId).hostname,\n maxAge: global.get('login_session').ttl * 1000\n };\n \n msg.cookies[global.get('login_session').cookie_name] = {\n value: msg.req.cookies[global.get('login_session').cookie_name],\n domain: new url.parse(entityId).hostname,\n maxAge: global.get('login_session').ttl * 1000\n };\n } else {\n msg.cookies = {};\n \n msg.cookies[global.get('login_session').cookie_user_name] = null;\n msg.cookies[global.get('login_session').cookie_name] = null;\n \n msg.error = 'Unable to find session';\n }\n \n node.send(msg);\n \n return;\n }\n );\n \n memcached.end();\n} else {\n msg.cookies = {};\n \n msg.cookies[global.get('login_session').cookie_user_name] = null;\n msg.cookies[global.get('login_session').cookie_name] = null;\n \n msg.error = 'No session provided';\n \n return msg;\n}","outputs":1,"noerr":0,"x":350,"y":820,"wires":[["effbeca5.f8df2"]]},{"id":"ee7dee97.ecd08","type":"http response","z":"669c0d6d.d65b14","name":"","statusCode":"200","headers":{},"x":920,"y":780,"wires":[]},{"id":"effbeca5.f8df2","type":"switch","z":"669c0d6d.d65b14","name":"","property":"error","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":550,"y":820,"wires":[["f06d9249.d9bed"],["f5bb2c5d.8889c8"]]},{"id":"9797caaa.1cfee","type":"http response","z":"669c0d6d.d65b14","name":"","statusCode":"404","headers":{"Content-Type":"application/json"},"x":920,"y":860,"wires":[]},{"id":"f5bb2c5d.8889c8","type":"change","z":"669c0d6d.d65b14","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"'{\"message\": \"' & msg.error & '\"}'","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":860,"wires":[["9797caaa.1cfee"]]},{"id":"f06d9249.d9bed","type":"change","z":"669c0d6d.d65b14","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"message\":\"OK\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":780,"wires":[["ee7dee97.ecd08"]]}]