|
[{"id":"5d8ce437.30a97c","type":"http in","z":"a963f616.6aebe8","name":"","url":"/google/oauth-auth","method":"get","swaggerDoc":"","x":140,"y":320,"wires":[["92962c27.e26ba","3e1d718f.2973ce"]]},{"id":"814cb966.112578","type":"http response","z":"a963f616.6aebe8","name":"","x":990,"y":360,"wires":[]},{"id":"11b23fee.e932c","type":"http in","z":"a963f616.6aebe8","name":"","url":"/google/oauth-token","method":"get","swaggerDoc":"","x":150,"y":760,"wires":[["65306936.ee6b78","7b66e8bd.0c4998"]]},{"id":"6e78d24c.36084c","type":"http response","z":"a963f616.6aebe8","name":"","x":1250,"y":860,"wires":[]},{"id":"92962c27.e26ba","type":"switch","z":"a963f616.6aebe8","name":"Check response_type","property":"req.query.response_type","propertyType":"msg","rules":[{"t":"neq","v":"code","vt":"str"},{"t":"else"}],"checkall":"false","outputs":2,"x":400,"y":320,"wires":[["5f1ce470.ff6eec"],["75c4e86f.ff44b8"]]},{"id":"5f1ce470.ff6eec","type":"function","z":"a963f616.6aebe8","name":"Error: response_type incorrect","func":"msg.statusCode = 500;\nmsg.payload = 'response_type ' + msg.req.query.response_type + ' must equal \"code\"';\n\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":300,"wires":[["814cb966.112578"]]},{"id":"75c4e86f.ff44b8","type":"switch","z":"a963f616.6aebe8","name":"Check client_id","property":"req.query.client_id","propertyType":"msg","rules":[{"t":"neq","v":"googleClientId","vt":"flow"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":380,"y":360,"wires":[["f020e980.c985c8"],["d6bb41c3.6d21a"]]},{"id":"f020e980.c985c8","type":"function","z":"a963f616.6aebe8","name":"Error: client_id incorrect","func":"msg.statusCode = 500;\nmsg.payload = 'client_id ' + msg.req.query.client_id + ' invalid';\n\nreturn msg;","outputs":1,"noerr":0,"x":650,"y":340,"wires":[["814cb966.112578"]]},{"id":"d6bb41c3.6d21a","type":"switch","z":"a963f616.6aebe8","name":"Check auth_code","property":"req.query.code","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"false","outputs":2,"x":390,"y":400,"wires":[["ee531b6f.30f228"],["ecb89c32.50ed2"]]},{"id":"ee531b6f.30f228","type":"function","z":"a963f616.6aebe8","name":"Return existing code","func":"msg.statusCode = 302;\nmsg.headers = {\n \"Location\": msg.req.query.redirect_uri + '?code=' + msg.req.query.code + '&state=' + msg.req.query.state\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":380,"wires":[["814cb966.112578"]]},{"id":"f62893.0a09577","type":"switch","z":"a963f616.6aebe8","name":"Check client_id","property":"client_id","propertyType":"msg","rules":[{"t":"neq","v":"googleClientId","vt":"flow"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":420,"y":840,"wires":[["707a6532.7e10ac"],["f43b1f75.1e069"]]},{"id":"707a6532.7e10ac","type":"function","z":"a963f616.6aebe8","name":"Error: client_id invalid","func":"msg.statusCode = 400;\nmsg.payload = 'client_id invalid';\n\nreturn msg;","outputs":1,"noerr":0,"x":680,"y":820,"wires":[["6e78d24c.36084c"]]},{"id":"f43b1f75.1e069","type":"switch","z":"a963f616.6aebe8","name":"Check client_secret","property":"client_secret","propertyType":"msg","rules":[{"t":"neq","v":"googleClientSecret","vt":"flow"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":430,"y":880,"wires":[["31493749.7b86b8"],["783716da.4fdad8"]]},{"id":"31493749.7b86b8","type":"function","z":"a963f616.6aebe8","name":"Error: client_secret invalid","func":"msg.statusCode = 400;\nmsg.payload = 'client_secret invalid';\n\nreturn msg;","outputs":1,"noerr":0,"x":690,"y":860,"wires":[["6e78d24c.36084c"]]},{"id":"783716da.4fdad8","type":"switch","z":"a963f616.6aebe8","name":"grant_type","property":"grant_type","propertyType":"msg","rules":[{"t":"eq","v":"authorization_code","vt":"str"},{"t":"eq","v":"refresh_token","vt":"str"},{"t":"else"}],"checkall":"false","outputs":3,"x":410,"y":960,"wires":[["cc413bc4.12e038"],["c5d30db5.28857"],["d6fa41a0.987f1"]]},{"id":"d6fa41a0.987f1","type":"function","z":"a963f616.6aebe8","name":"Error: grant_type unsupported","func":"msg.statusCode = 400;\nmsg.payload = 'grant_type ' + msg.req.query.grant_type + ' is not supported';\n\nreturn msg;","outputs":1,"noerr":0,"x":850,"y":1020,"wires":[["7b406925.9afee8"]]},{"id":"719f461b.bbbe48","type":"inject","z":"a963f616.6aebe8","name":"On Startup","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":"","x":110,"y":180,"wires":[["96d6601f.9e47b","dc17a5a1.c3d5a8","dabc2ebc.452a7"]]},{"id":"96ed41b8.5f27c","type":"function","z":"a963f616.6aebe8","name":"Handle refresh_token","func":"let refreshTokenStore = global.get(\"googleRefreshTokenStore\") || {};\nlet refreshToken = refreshTokenStore[msg.refresh_token];\nif (refreshToken === null) {\n msg.statusCode = 400;\n msg.payload = 'invalid code';\n return msg;\n}\n\nif (new Date(refreshToken.expiresAt) < Date.now()) {\n // Remove bad auth code\n delete refreshTokenStore[msg.refresh_token];\n \n msg.statusCode = 400;\n msg.payload = 'expired code';\n return msg;\n}\n\nif (refreshToken.clientId != msg.client_id) {\n msg.statusCode = 400;\n msg.payload = 'invalid code - wrong client';\n return msg;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":940,"wires":[["e4e16182.f31da"]]},{"id":"cc413bc4.12e038","type":"function","z":"a963f616.6aebe8","name":"Handle authorization_code","func":"let authStore = flow.get(\"authStore\") || {};\nlet authCode = authStore[msg.code];\nif (authCode === null) {\n msg.statusCode = 400;\n msg.payload = 'invalid code';\n return msg;\n}\n\nif (new Date(authCode.expiresAt) < Date.now()) {\n // Remove expired auth code\n delete authStore[msg.code];\n flow.set(\"authStore\", authStore);\n \n msg.statusCode = 400;\n msg.payload = 'expired code';\n return msg;\n}\n\nif (authCode.clientId != msg.client_id) {\n msg.statusCode = 400;\n msg.payload = 'invalid code - wrong client';\n return msg;\n}\n\n// Remove used auth code\ndelete authStore[msg.code];\nflow.set(\"authStore\", authStore);\n\nreturn msg;","outputs":1,"noerr":0,"x":840,"y":900,"wires":[["e4e16182.f31da"]]},{"id":"c5d30db5.28857","type":"switch","z":"a963f616.6aebe8","name":"refresh_token?","property":"refresh_token","propertyType":"msg","rules":[{"t":"nnull"},{"t":"null"}],"checkall":"false","repair":false,"outputs":2,"x":600,"y":960,"wires":[["96ed41b8.5f27c"],["bcf202b7.8f9d"]]},{"id":"bcf202b7.8f9d","type":"function","z":"a963f616.6aebe8","name":"Error: refresh_token incorrect","func":"msg.statusCode = 400;\nmsg.payload = 'missing required parameter';\n\nreturn msg;","outputs":1,"noerr":0,"x":840,"y":980,"wires":[["7b406925.9afee8"]]},{"id":"5b266b4a.ad76f4","type":"comment","z":"a963f616.6aebe8","name":"OAuth Auth","info":"Google will direct the client to this endpoint to do\nlogin and generation of a temporary authorization code\nfor a specific user.","x":110,"y":280,"wires":[]},{"id":"f00f308e.4e003","type":"comment","z":"a963f616.6aebe8","name":"OAuth Token","info":"","x":110,"y":720,"wires":[]},{"id":"3e1d718f.2973ce","type":"debug","z":"a963f616.6aebe8","name":"","active":false,"console":"false","complete":"true","x":350,"y":280,"wires":[]},{"id":"65306936.ee6b78","type":"debug","z":"a963f616.6aebe8","name":"","active":false,"console":"false","complete":"true","x":390,"y":760,"wires":[]},{"id":"aebfe219.4c443","type":"http in","z":"a963f616.6aebe8","name":"","url":"/google/oauth-token","method":"post","swaggerDoc":"","x":150,"y":800,"wires":[["65306936.ee6b78","7b66e8bd.0c4998"]]},{"id":"7b66e8bd.0c4998","type":"function","z":"a963f616.6aebe8","name":"Unify GET / POST","func":"msg.client_id = msg.req.query.client_id ? msg.req.query.client_id : msg.req.body.client_id;\nmsg.client_secret = msg.req.query.client_secret ? msg.req.query.client_secret : msg.req.body.client_secret ;\nmsg.grant_type = msg.req.query.grant_type ? msg.req.query.grant_type : msg.req.body.grant_type;\nmsg.refresh_token = msg.req.query.refresh_token ? msg.req.query.refresh_token : msg.req.body.refresh_token;\nmsg.code = msg.req.query.code ? msg.req.query.code : msg.req.body.code;\n\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":800,"wires":[["f62893.0a09577"]]},{"id":"e09b5331.a189e","type":"inject","z":"a963f616.6aebe8","name":"Debug","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":90,"y":100,"wires":[["b8289254.47d5e"]]},{"id":"b8289254.47d5e","type":"function","z":"a963f616.6aebe8","name":"Check stores","func":"let authStore = flow.get(\"authStore\") || {};\nlet accessTokenStore = global.get(\"googleAccessTokenStore\") || {};\nlet refreshTokenStore = global.get(\"googleRefreshTokenStore\") || {};\nlet googleClientId = flow.get(\"googleClientId\");\nlet googleClientSecret = flow.get(\"googleClientSecret\");\nlet older = new Date(Date.now() - (60 * 60000));\n\nfor (let code in authStore)\n if (new Date(authStore[code].expiresAt) <= older)\n delete authStore[code];\n\nfor (let code in accessTokenStore)\n if (new Date(accessTokenStore[code].expiresAt) <= older)\n delete accessTokenStore[code];\n\nfor (let code in refreshTokenStore)\n if (new Date(refreshTokenStore[code].expiresAt) <= older)\n delete refreshTokenStore[code];\n\nreturn { \n payload: {\n googleClientId: googleClientId,\n googleClientSecret: googleClientSecret,\n authStore: authStore,\n accessTokenStore: accessTokenStore,\n refreshTokenStore: refreshTokenStore\n } \n};","outputs":1,"noerr":0,"x":330,"y":100,"wires":[["e19fcabf.b98138"]]},{"id":"e19fcabf.b98138","type":"debug","z":"a963f616.6aebe8","name":"","active":true,"console":"false","complete":"false","x":1010,"y":100,"wires":[]},{"id":"2dfc1c3a.133194","type":"comment","z":"a963f616.6aebe8","name":"Login method","info":"Login page will post to this, which will generate a temporary\nauthorization code and redirect back to the Google endpoint.","x":110,"y":480,"wires":[]},{"id":"a0833812.3d4378","type":"http in","z":"a963f616.6aebe8","name":"","url":"/google/oauth-login","method":"post","swaggerDoc":"","x":150,"y":520,"wires":[["b251caab.8f2938"]]},{"id":"b251caab.8f2938","type":"switch","z":"a963f616.6aebe8","name":"Check redirect_uri","property":"req.body.redirect_uri","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"false","outputs":2,"x":390,"y":520,"wires":[["4e93bab2.afaa04"],["89de32c1.804d3"]]},{"id":"6a11568.a9e43a8","type":"function","z":"a963f616.6aebe8","name":"Generate new code","func":"// Validate auth_key\nlet authKey = flow.get(\"authKey\") || \"NONE\";\nif (msg.req.body.auth_key != authKey || authKey === \"NONE\") {\n msg.statusCode = 403;\n msg.payload = \"Incorrect key\";\n\n return msg;\n}\n\n// Validated, grant auth_code\nlet authCode = Math.floor(Math.random() * 10000000000000000000000000000000000000000).toString(36);\nlet authStore = flow.get(\"authStore\") || {};\n\nauthStore[authCode] = {\n type: 'AUTH_CODE',\n uid: 'homeautio',\n clientId: msg.req.body.client_id,\n expiresAt: new Date(Date.now() + (60 * 10000))\n};\n\nflow.set(\"authStore\", authStore);\n\nmsg.statusCode = 302;\nmsg.headers = {\n \"Location\": msg.req.body.redirect_uri + '?code=' + authCode + '&state=' + msg.req.body.state\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":630,"y":660,"wires":[["aff8e338.cf175"]]},{"id":"aff8e338.cf175","type":"http response","z":"a963f616.6aebe8","name":"","x":990,"y":580,"wires":[]},{"id":"4e93bab2.afaa04","type":"function","z":"a963f616.6aebe8","name":"Error: redirect_uri missing","func":"msg.statusCode = 500;\nmsg.payload = 'redirect_uri missing';\n\nreturn msg;","outputs":1,"noerr":0,"x":650,"y":500,"wires":[["aff8e338.cf175"]]},{"id":"89de32c1.804d3","type":"switch","z":"a963f616.6aebe8","name":"Check state","property":"req.body.state","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"false","outputs":2,"x":370,"y":560,"wires":[["9ab324eb.965e38"],["b2c1b653.bffbf8"]]},{"id":"9ab324eb.965e38","type":"function","z":"a963f616.6aebe8","name":"Error: state missing","func":"msg.statusCode = 500;\nmsg.payload = 'state missing';\n\nreturn msg;","outputs":1,"noerr":0,"x":630,"y":540,"wires":[["aff8e338.cf175"]]},{"id":"8e161445.7b6598","type":"switch","z":"a963f616.6aebe8","name":"Check auth_key","property":"req.body.auth_key","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"false","outputs":2,"x":380,"y":640,"wires":[["17f6f63b.08349a"],["6a11568.a9e43a8"]]},{"id":"17f6f63b.08349a","type":"function","z":"a963f616.6aebe8","name":"Error: auth_key missing","func":"msg.statusCode = 500;\nmsg.payload = 'auth_key missing';\n\nreturn msg;","outputs":1,"noerr":0,"x":650,"y":620,"wires":[["aff8e338.cf175"]]},{"id":"b2c1b653.bffbf8","type":"switch","z":"a963f616.6aebe8","name":"Check client_id","property":"req.body.client_id","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"false","outputs":2,"x":380,"y":600,"wires":[["fb21f3f9.fbdba"],["8e161445.7b6598"]]},{"id":"fb21f3f9.fbdba","type":"function","z":"a963f616.6aebe8","name":"Error: client_id missing","func":"msg.statusCode = 500;\nmsg.payload = 'client_id ' + msg.req.body.client_id + ' invalid';\n\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":580,"wires":[["aff8e338.cf175"]]},{"id":"ecb89c32.50ed2","type":"template","z":"a963f616.6aebe8","name":"Display login form","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<html>\n <head>\n <title>Login</title>\n </head>\n <body>\n <form action=\"/google/oauth-login\" method=\"post\">\n <input type=\"hidden\" name=\"redirect_uri\" value=\"{{req.query.redirect_uri}}\" />\n <input type=\"hidden\" name=\"state\" value=\"{{req.query.state}}\" />\n <input type=\"hidden\" name=\"client_id\" value=\"{{req.query.client_id}}\" />\n Key: <input type=\"text\" name=\"auth_key\"> \n <input type=\"submit\" value=\"Submit\" />\n </form>\n </body>\n</html>","x":630,"y":420,"wires":[["814cb966.112578"]]},{"id":"e4e16182.f31da","type":"function","z":"a963f616.6aebe8","name":"Generate token","func":"// Carry through errors\nif (msg.statusCode == 400 || msg.statusCode == 500)\n return msg;\n\n// Create tokens\nlet accessTokenStore = global.get(\"googleAccessTokenStore\") || {};\nlet accessTokenId = Math.floor(Math.random() * 10000000000000000000000000000000000000000).toString(36);\naccessTokenStore[accessTokenId] = {\n type: 'ACCESS',\n uid: 'homeautio',\n clientId: msg.client_id,\n expiresAt: new Date(Date.now() + (60 * 30000))\n};\n\nlet refreshTokenStore = global.get(\"googleRefreshTokenStore\") || {};\nlet refreshTokenId = Math.floor(Math.random() * 10000000000000000000000000000000000000000).toString(36);\nrefreshTokenStore[refreshTokenId] = {\n type: 'REFRESH',\n uid: 'homeautio',\n clientId: msg.client_id,\n expiresAt: new Date(Date.now() + (60 * 60000 * 336))\n};\n\nlet access_token = {\n accessToken: accessTokenId,\n refreshToken: refreshTokenId\n};\n\nglobal.set(\"googleAccessTokenStore\", accessTokenStore);\n\n// Success\nmsg.statusCode = 200;\nmsg.payload = {\n token_type: \"bearer\",\n access_token: access_token.accessToken,\n refresh_token: access_token.refreshToken,\n expires_in: 600\n};\n\n// Remove now invalid auth code\nif (msg.refresh_token !== null)\n delete refreshTokenStore[msg.refresh_token];\n\nglobal.set(\"googleRefreshTokenStore\", refreshTokenStore);\n\nreturn [ \n msg, \n { payload: accessTokenStore }, \n { payload: refreshTokenStore}\n];","outputs":"3","noerr":0,"x":1080,"y":900,"wires":[["d0a21770.31c528","6e78d24c.36084c"],["8f49db52.3e3708","d0a21770.31c528"],["b82d5bd3.681b68","d0a21770.31c528"]]},{"id":"8d08768e.c38d98","type":"debug","z":"a963f616.6aebe8","name":"","active":false,"console":"false","complete":"true","x":990,"y":180,"wires":[]},{"id":"96d6601f.9e47b","type":"file in","z":"a963f616.6aebe8","name":"Read file","filename":"/data/flowData/googleAccessTokenStore","format":"utf8","sendError":true,"x":320,"y":180,"wires":[["8ef9b1d.99da95"]]},{"id":"8ef9b1d.99da95","type":"json","z":"a963f616.6aebe8","name":"","x":470,"y":180,"wires":[["4dc08849.9db2b8"]]},{"id":"4dc08849.9db2b8","type":"function","z":"a963f616.6aebe8","name":"Update googleAccessTokenStore","func":"if (msg.payload !== null) {\n global.set(\"googleAccessTokenStore\", msg.payload);\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":700,"y":180,"wires":[["8d08768e.c38d98"]]},{"id":"f511f2c.938251","type":"debug","z":"a963f616.6aebe8","name":"","active":false,"console":"false","complete":"true","x":990,"y":220,"wires":[]},{"id":"dc17a5a1.c3d5a8","type":"file in","z":"a963f616.6aebe8","name":"Read file","filename":"/data/flowData/googleRefreshTokenStore","format":"utf8","sendError":true,"x":320,"y":220,"wires":[["8d20feac.0335"]]},{"id":"8d20feac.0335","type":"json","z":"a963f616.6aebe8","name":"","x":470,"y":220,"wires":[["c5339dd2.06dd9"]]},{"id":"c5339dd2.06dd9","type":"function","z":"a963f616.6aebe8","name":"Update googleRefreshTokenStore","func":"if (msg.payload !== null) {\n global.set(\"googleRefreshTokenStore\", msg.payload);\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":700,"y":220,"wires":[["f511f2c.938251"]]},{"id":"8f49db52.3e3708","type":"file","z":"a963f616.6aebe8","name":"Write file","filename":"/data/flowData/googleAccessTokenStore","appendNewline":false,"createDir":false,"overwriteFile":"true","x":1260,"y":900,"wires":[]},{"id":"b82d5bd3.681b68","type":"file","z":"a963f616.6aebe8","name":"Write file","filename":"/data/flowData/googleRefreshTokenStore","appendNewline":false,"createDir":false,"overwriteFile":"true","x":1260,"y":940,"wires":[]},{"id":"d0a21770.31c528","type":"debug","z":"a963f616.6aebe8","name":"","active":false,"console":"false","complete":"false","x":1270,"y":980,"wires":[]},{"id":"dabc2ebc.452a7","type":"credentials","z":"a963f616.6aebe8","name":"","props":[{"value":"googleClientId","type":"flow"},{"value":"googleClientSecret","type":"flow"},{"value":"authKey","type":"flow"}],"x":330,"y":140,"wires":[[]]},{"id":"7b406925.9afee8","type":"http response","z":"a963f616.6aebe8","name":"","x":1070,"y":980,"wires":[]}] |