From 88e39f48016f4553c8ee309fcf4c76a53c719793 Mon Sep 17 00:00:00 2001 From: Raul Glogovetan Date: Thu, 31 Oct 2024 02:40:19 +0200 Subject: [PATCH 1/3] Experimental: initial fields filtering prototype --- package-lock.json | 230 +++++++++++++++++++++++- package.json | 3 + src/routes/graphql/graphql.ts | 318 +++++++++++++++++++++++++++++++++- 3 files changed, 541 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24eed05..9ec0ec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,14 @@ "license": "MIT", "dependencies": { "@colony/core": "^2.0.1", + "@tsmx/json-traverse": "^1.0.8", "cors": "^2.8.5", "express": "^4.18.2", "express-session": "^1.17.3", + "graphql": "^16.9.0", "http-proxy-middleware": "^2.0.6", "node-fetch": "2.6", + "node-http-proxy-json": "^0.1.9", "siwe": "^2.1.4", "ws": "^8.16.0" }, @@ -941,6 +944,16 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@tsmx/json-traverse": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsmx/json-traverse/-/json-traverse-1.0.8.tgz", + "integrity": "sha512-rdtq+BzSfRXrAtz3xOfBOa6dc9ywsxDBjRboTrr/XXWeYeV7+ER3rCqrEiVgupBPu5AUgvbYlxOcH/zn1kjaig==", + "license": "MIT", + "engines": { + "node": ">=10.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -1259,6 +1272,21 @@ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "peer": true }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bufferhelper": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/bufferhelper/-/bufferhelper-0.2.1.tgz", + "integrity": "sha512-asncN5SO1YOZLCV3u26XtrbF9QXhSyq01nQOc1TFt9/gfOn7feOGJoVKk5Ewtj7wvFGPH/eGSKZ5qq/A4/PPfw==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1352,6 +1380,21 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1384,6 +1427,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1956,10 +2005,10 @@ } }, "node_modules/graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", - "dev": true, + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -2187,6 +2236,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2406,6 +2461,16 @@ } } }, + "node_modules/node-http-proxy-json": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-http-proxy-json/-/node-http-proxy-json-0.1.9.tgz", + "integrity": "sha512-WrKAR/y09BWaz5WqgbxuE6D/XsdhQFkLkSdnRk0a5uBKSINtApMV085MN7JMh+stiyBBltvgSR9SYVIZIpKKKQ==", + "license": "MIT", + "dependencies": { + "bufferhelper": "^0.2.1", + "concat-stream": "^1.5.1" + } + }, "node_modules/nodemon": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", @@ -2587,6 +2652,12 @@ "node": ">=12" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2672,6 +2743,27 @@ "node": ">= 0.6" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2935,6 +3027,21 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -3168,6 +3275,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -3219,6 +3332,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3958,6 +4077,11 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "@tsmx/json-traverse": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsmx/json-traverse/-/json-traverse-1.0.8.tgz", + "integrity": "sha512-rdtq+BzSfRXrAtz3xOfBOa6dc9ywsxDBjRboTrr/XXWeYeV7+ER3rCqrEiVgupBPu5AUgvbYlxOcH/zn1kjaig==" + }, "@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -4239,6 +4363,16 @@ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "peer": true }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "bufferhelper": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/bufferhelper/-/bufferhelper-0.2.1.tgz", + "integrity": "sha512-asncN5SO1YOZLCV3u26XtrbF9QXhSyq01nQOc1TFt9/gfOn7feOGJoVKk5Ewtj7wvFGPH/eGSKZ5qq/A4/PPfw==" + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4306,6 +4440,17 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -4329,6 +4474,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -4768,10 +4918,9 @@ } }, "graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", - "dev": true + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==" }, "has-flag": { "version": "3.0.0", @@ -4928,6 +5077,11 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5076,6 +5230,15 @@ "whatwg-url": "^5.0.0" } }, + "node-http-proxy-json": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/node-http-proxy-json/-/node-http-proxy-json-0.1.9.tgz", + "integrity": "sha512-WrKAR/y09BWaz5WqgbxuE6D/XsdhQFkLkSdnRk0a5uBKSINtApMV085MN7JMh+stiyBBltvgSR9SYVIZIpKKKQ==", + "requires": { + "bufferhelper": "^0.2.1", + "concat-stream": "^1.5.1" + } + }, "nodemon": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", @@ -5203,6 +5366,11 @@ "queue-lit": "^1.5.1" } }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5253,6 +5421,27 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5432,6 +5621,21 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5600,6 +5804,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -5638,6 +5847,11 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 5054dec..d01cd79 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,14 @@ "main": "build/index.js", "dependencies": { "@colony/core": "^2.0.1", + "@tsmx/json-traverse": "^1.0.8", "cors": "^2.8.5", "express": "^4.18.2", "express-session": "^1.17.3", + "graphql": "^16.9.0", "http-proxy-middleware": "^2.0.6", "node-fetch": "2.6", + "node-http-proxy-json": "^0.1.9", "siwe": "^2.1.4", "ws": "^8.16.0" }, diff --git a/src/routes/graphql/graphql.ts b/src/routes/graphql/graphql.ts index 5a3283c..0469caf 100644 --- a/src/routes/graphql/graphql.ts +++ b/src/routes/graphql/graphql.ts @@ -2,6 +2,12 @@ import dotenv from 'dotenv'; import { fixRequestBody, Options, RequestHandler } from 'http-proxy-middleware'; import { Response, Request, NextFunction } from 'express-serve-static-core'; import { ClientRequest, IncomingMessage } from 'http'; +// Needs to be ignored since it doesn't have types and TS goe crazy +//@ts-ignore +import modifyResponse from 'node-http-proxy-json'; +import { parse, visit, print } from 'graphql'; +//@ts-ignore +import jt from '@tsmx/json-traverse'; import { getStaticOrigin, @@ -42,6 +48,151 @@ export const operationExecutionHandler: RequestHandler = async ( try { response.locals.canExecute = await addressCanExecuteMutation(request); + response.locals.requestSanitation = {}; + response.locals.aliases = {}; + + // @ts-ignore + // console.log(request.body?.query); + // request.body.query = request.body.query.replace('email', ''); + // console.log(request.body.query); + // console.log(parse(request.body.query)); + const edited = visit(parse(request.body.query), { + enter(node, key, parent, path, acestors) { + console.log(node.kind); + // @ts-ignore + console.log(node.name); + // console.log('enter'); + // console.log('enter', node); + // console.log(node.kind); + // @ts-ignore + // console.log(node.name); + // @ts-ignore + // console.log(node.value);' + /** + * getUser: { abc: { aliased: email }} + * + */ + /** + * requestSanitation: { + * 'getUser.abc.aliased': 'email' + * } + * + * INSIDE RESPONSE: + * Fields to delete per type: { + * 'Profile': [{ + * field: 'email', + * condition: (value) => value !== userAddress + * }] + * } + * + * ['getUser', 'abc', 'aliased'] + * const aliasOrRealName = requestSanitation[['getUser', 'abc', 'aliased'].join('.)] ?? jsonKey; + * + * + * rules[Profile] - do they have aliasOrRealName inside? If they do, delete that field + */ + // @ts-ignore + if (node.alias) { + // if (node.kind === 'Field') { + // let key = ''; + // if (node?.name?.value === 'email') { + // key = 'email'; + // } + // if (node?.name?.value === 'profile') { + // key = 'profile'; + // } + // response.locals.requestSanitation = { + // ...response.locals.requestSanitation, + // [key]: { + // name: node.name.value, + // // @ts-ignore + // alias: node.alias.value, + // path: [...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { + // return entry?.alias?.value || entry?.name?.value || 'unknown'; + // }), + // }, + // }; + // } + const path = [...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { + return entry?.alias?.value || entry?.name?.value || 'unknown'; + }); + response.locals.requestSanitation = { + ...response.locals.requestSanitation, + // @ts-ignore + [node.name.value]: [...path, node.alias.value].join('.'), + }; + + // @ts-ignore + const aliasPath = [...path, node.alias.value ?? node.name.value].join('.'); + response.locals.aliases = { + ...response.locals.aliases, + // @ts-ignore + [aliasPath]: node.name.value, + } + } + // if (node.kind === 'Field' && node?.name?.value === 'email') { + // response.locals.requestSanitation = { + // ...response.locals.requestSanitation, + // email: { + // name: node.name.value, + // // @ts-ignore + // alias: node.alias.value, + // // path: + // }, + // }; + // } + // if (node.kind === 'Field' && node.name.value === 'profile') { + // response.locals.requestSanitation = { + // ...response.locals.requestSanitation, + // profile: { + // name: node.name.value, + // // @ts-ignore + // alias: node.alias.value, + // // path: + // }, + // }; + // } + if (node.kind === 'SelectionSet') { + // console.log(node); + return { + ...node, + selections: [ + ...node.selections, + { + kind: 'Field', + name: { + kind: 'Name', + value: '__typename' + } + } + ] + }; + } + // @ts-ignore + // if (node.alias) { + // // @ts-ignore + // console.log(node?.alias?.value, node?.name?.value); + // // console.log({ key, path, parent, acestors }); + // console.log([...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { + // return entry?.alias?.value || entry?.name?.value || 'unknown'; + // })); + // } + }, + // leave(node) { + // console.log('leave'); + // // console.log('leave', node); + // // console.log(node.kind); + // // @ts-ignore + // console.log(node.name); + // // @ts-ignore + // console.log(node.value); + // } + }); + // console.log(print(edited)); + request.body.query = print(edited); + + // console.log(JSON.stringify(request.body, null, 2)); + return nextFn(); } catch (error: any) { logger( @@ -76,7 +227,7 @@ export const graphQlProxyRouteHandler: Options = { /* * Used for UI only, the real magic with detection happens in operationExecutionHandler */ - const { operationType, operations, variables } = detectOperation( + const { operationType, operations, variables = {} } = detectOperation( request.body, ); @@ -170,11 +321,174 @@ export const graphQlProxyRouteHandler: Options = { } }, // selfHandleResponse: true, - onProxyRes: (proxyResponse: IncomingMessage, request: Request) => { + onProxyRes: (proxyResponse: IncomingMessage, request: Request, response: Response) => { proxyResponse.headers[Headers.AllowOrigin] = getStaticOrigin( request.headers.origin, ); + + console.log('requestSanitization', response.locals.requestSanitation); + console.log('aliases', response.locals.aliases); + proxyResponse.headers[Headers.PoweredBy] = 'Colony'; + modifyResponse(response, proxyResponse, (body: Record) => { + try { + if (body) { + // Don't return the API key to the client + // so that it doesn't get exposed + // delete body.integrations['Segment.io'].apiKey; + // console.log({body: JSON.stringify(body, null, 2)}); + // delete body.getUser.abc. + const modifiedBody = { ...body }; + // console.log(modifiedBody); + const callbacks = { + processValue: (key: any, value: any, level: any, path: any, isObjectRoot: any, isArrayElement: any, cbSetValue: any) => { + // console.log({ key, value, isObjectRoot, level, path }); + // ['data', 'getUser', 'profile', 'metadata', '__typename'] + // { data: { getUser: { profile }}} + + console.log({key}) + + const config = { + 'Profile': [{ + field: 'email', + deleteIf: (value: any) => !value?.id || value?.id !== request.session.auth?.address + }] + } + + // const realFieldName = response.locals.requestSanitation[path] ?? key; + // const parentTypename = <>.__typename; + + // const matchingRule = config[parentTypename]; + // if(matchingRule && matchingRule.includes(realFieldName)) { + // delete <>[key] + // } + + // console.log({ }) + /** + * profile1: profile { + * alias1: email + * } + * + * profile2: profile { + * alias2: email + * } + */ + + try { +// @ts-ignore +const matchingConfig = config[value?.__typename]; + +if(matchingConfig) { + for(const property of Object.keys(value)) { + const realFieldName = response.locals.aliases[[...(path.filter((segment: any) => segment !== 'data')), key, property].join('.')] ?? property; + + const matchingRule = matchingConfig.find((rule: any) => rule.field === realFieldName); + + console.log({realFieldName, matchingRule, deleteIf: matchingRule?.deleteIf(value)}) + + if(matchingRule && matchingRule.deleteIf(value)) { + console.log('delete email') + delete value[property]; + } + } +} + } catch(error) { + console.error(error) + } + + + // if(value?.__typename === 'Profile') { + + // const hasAccessToPrivateData = !!value?.id && value?.id === request.session.auth?.address; + + + // for(const property of Object.keys(value)) { + // console.log('alias path: ', [...path, key, property].join('.')) + // const realFieldName = response.locals.aliases[[...(path.filter((segment: any) => segment !== 'data')), key, property].join('.')] ?? property; + // console.log({property, realFieldName}) + // const profileConfig = config['Profile']; + + + // if(profileConfig.includes(realFieldName)) { + // console.log('delete email') + // if(!hasAccessToPrivateData) { + // delete value[property]; + // } + // } + // } + + // // console.log('hello', value, key) + + // // const emailFieldName = response.locals.requestSanitation['email'] ? response.locals.requestSanitation['email'].split('.').pop() : 'email'; + + // // const emailPath = response.locals.requestSanitation['email'] ? ['data', ...response.locals.requestSanitation['email'].split('.')] : [...path, key, 'email']; + // // console.log(emailFieldName) + + + // // console.log({ hasAccessToPrivateData}) + + // // const copiedValue = { + // // ...value + // // } + + // // if (!hasAccessToPrivateData) { + // // delete copiedValue[emailFieldName] + // // } + + // // cbSetValue(copiedValue) + + // // let currentObj = modifiedBody; + // // let idx = 0; + // // while (idx < emailPath.length - 1) { + // // currentObj = currentObj[emailPath[idx]]; + // // if (!currentObj) { + // // delete currentObj[emailPath[emailPath.length - 1]]; + // // } + // // idx++; + // // } + // // if (!hasAccessToPrivateData) { + // // delete currentObj[emailPath[emailPath.length - 1]]; + // // } + + // } + + // if (key === '__typename' && value === 'Profile') { + // const emailPath = response.locals.requestSanitation['email'] ? ['data', ...response.locals.requestSanitation['email'].split('.')] : [...path, 'email']; + // console.log(emailPath) + // // const fieldToDeleteOnProfile = ['email']; + // // delete modifiedBody.data.getUser.profile.email; + // // const hasAlias = !!response.locals.requestSanitation[path.slice(1).join('.')]; + // // console.log({ hasAlias}) + // // console.log(path.join('.')); + // let currentObj = modifiedBody; + // let idx = 0; + // while (idx < emailPath.length -1 ) { + // currentObj = currentObj[emailPath[idx]]; + // if (!currentObj) { + // delete currentObj[emailPath[emailPath.length - 1]]; + // } + // idx++; + // } + // delete currentObj[emailPath[emailPath.length - 1]]; + // } + /* your logic here */ + }, + enterLevel: (level: any, path: any) => { + /* your logic here */ + }, + exitLevel: (level: any, path: any) => { + /* your logic here */ + } + }; + + jt.traverse(body, callbacks); + return modifiedBody; + } + } catch (error) { + // proxyResponse.destroy(); + } + return body; + }); }, logProvider: () => ({ log: logger, From f6d1410b631cd3d26f02a544a5326bf287bf15cb Mon Sep 17 00:00:00 2001 From: Raul Glogovetan Date: Thu, 31 Oct 2024 16:24:25 +0200 Subject: [PATCH 2/3] Refactor: introduce query permissions checks --- src/routes/graphql/graphql.ts | 597 +++++++++++++++++----------------- src/types.ts | 4 + 2 files changed, 304 insertions(+), 297 deletions(-) diff --git a/src/routes/graphql/graphql.ts b/src/routes/graphql/graphql.ts index 0469caf..3d4d7fc 100644 --- a/src/routes/graphql/graphql.ts +++ b/src/routes/graphql/graphql.ts @@ -27,6 +27,7 @@ import { } from '~types'; import addressCanExecuteMutation from './mutations'; +import addressCanExecuteQuery from './queries'; dotenv.config(); @@ -47,7 +48,10 @@ export const operationExecutionHandler: RequestHandler = async ( const requestRemoteAddress = getRemoteIpAddress(request); try { - response.locals.canExecute = await addressCanExecuteMutation(request); + const canExecuteMutation = await addressCanExecuteMutation(request); + const canExecuteQuery = await addressCanExecuteQuery(request); + + response.locals.canExecute = canExecuteMutation || canExecuteQuery; response.locals.requestSanitation = {}; response.locals.aliases = {}; @@ -56,140 +60,140 @@ export const operationExecutionHandler: RequestHandler = async ( // request.body.query = request.body.query.replace('email', ''); // console.log(request.body.query); // console.log(parse(request.body.query)); - const edited = visit(parse(request.body.query), { - enter(node, key, parent, path, acestors) { - console.log(node.kind); - // @ts-ignore - console.log(node.name); - // console.log('enter'); - // console.log('enter', node); - // console.log(node.kind); - // @ts-ignore - // console.log(node.name); - // @ts-ignore - // console.log(node.value);' - /** - * getUser: { abc: { aliased: email }} - * - */ - /** - * requestSanitation: { - * 'getUser.abc.aliased': 'email' - * } - * - * INSIDE RESPONSE: - * Fields to delete per type: { - * 'Profile': [{ - * field: 'email', - * condition: (value) => value !== userAddress - * }] - * } - * - * ['getUser', 'abc', 'aliased'] - * const aliasOrRealName = requestSanitation[['getUser', 'abc', 'aliased'].join('.)] ?? jsonKey; - * - * - * rules[Profile] - do they have aliasOrRealName inside? If they do, delete that field - */ - // @ts-ignore - if (node.alias) { - // if (node.kind === 'Field') { - // let key = ''; - // if (node?.name?.value === 'email') { - // key = 'email'; - // } - // if (node?.name?.value === 'profile') { - // key = 'profile'; - // } - // response.locals.requestSanitation = { - // ...response.locals.requestSanitation, - // [key]: { - // name: node.name.value, - // // @ts-ignore - // alias: node.alias.value, - // path: [...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { - // return entry?.alias?.value || entry?.name?.value || 'unknown'; - // }), - // }, - // }; - // } - const path = [...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { - return entry?.alias?.value || entry?.name?.value || 'unknown'; - }); - response.locals.requestSanitation = { - ...response.locals.requestSanitation, - // @ts-ignore - [node.name.value]: [...path, node.alias.value].join('.'), - }; - - // @ts-ignore - const aliasPath = [...path, node.alias.value ?? node.name.value].join('.'); - response.locals.aliases = { - ...response.locals.aliases, - // @ts-ignore - [aliasPath]: node.name.value, - } - } - // if (node.kind === 'Field' && node?.name?.value === 'email') { - // response.locals.requestSanitation = { - // ...response.locals.requestSanitation, - // email: { - // name: node.name.value, - // // @ts-ignore - // alias: node.alias.value, - // // path: - // }, - // }; - // } - // if (node.kind === 'Field' && node.name.value === 'profile') { - // response.locals.requestSanitation = { - // ...response.locals.requestSanitation, - // profile: { - // name: node.name.value, - // // @ts-ignore - // alias: node.alias.value, - // // path: - // }, - // }; - // } - if (node.kind === 'SelectionSet') { - // console.log(node); - return { - ...node, - selections: [ - ...node.selections, - { - kind: 'Field', - name: { - kind: 'Name', - value: '__typename' - } - } - ] - }; - } - // @ts-ignore - // if (node.alias) { - // // @ts-ignore - // console.log(node?.alias?.value, node?.name?.value); - // // console.log({ key, path, parent, acestors }); - // console.log([...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { - // return entry?.alias?.value || entry?.name?.value || 'unknown'; - // })); - // } - }, - // leave(node) { - // console.log('leave'); - // // console.log('leave', node); - // // console.log(node.kind); - // // @ts-ignore - // console.log(node.name); - // // @ts-ignore - // console.log(node.value); - // } - }); - // console.log(print(edited)); - request.body.query = print(edited); + // const edited = visit(parse(request.body.query), { + // enter(node, key, parent, path, acestors) { + // console.log(node.kind); + // // @ts-ignore + // console.log(node.name); + // // console.log('enter'); + // // console.log('enter', node); + // // console.log(node.kind); + // // @ts-ignore + // // console.log(node.name); + // // @ts-ignore + // // console.log(node.value);' + // /** + // * getUser: { abc: { aliased: email }} + // * + // */ + // /** + // * requestSanitation: { + // * 'getUser.abc.aliased': 'email' + // * } + // * + // * INSIDE RESPONSE: + // * Fields to delete per type: { + // * 'Profile': [{ + // * field: 'email', + // * condition: (value) => value !== userAddress + // * }] + // * } + // * + // * ['getUser', 'abc', 'aliased'] + // * const aliasOrRealName = requestSanitation[['getUser', 'abc', 'aliased'].join('.)] ?? jsonKey; + // * + // * + // * rules[Profile] - do they have aliasOrRealName inside? If they do, delete that field + // */ + // // @ts-ignore + // if (node.alias) { + // // if (node.kind === 'Field') { + // // let key = ''; + // // if (node?.name?.value === 'email') { + // // key = 'email'; + // // } + // // if (node?.name?.value === 'profile') { + // // key = 'profile'; + // // } + // // response.locals.requestSanitation = { + // // ...response.locals.requestSanitation, + // // [key]: { + // // name: node.name.value, + // // // @ts-ignore + // // alias: node.alias.value, + // // path: [...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { + // // return entry?.alias?.value || entry?.name?.value || 'unknown'; + // // }), + // // }, + // // }; + // // } + // const path = [...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { + // return entry?.alias?.value || entry?.name?.value || 'unknown'; + // }); + // response.locals.requestSanitation = { + // ...response.locals.requestSanitation, + // // @ts-ignore + // [node.name.value]: [...path, node.alias.value].join('.'), + // }; + + // // @ts-ignore + // const aliasPath = [...path, node.alias.value ?? node.name.value].join('.'); + // response.locals.aliases = { + // ...response.locals.aliases, + // // @ts-ignore + // [aliasPath]: node.name.value, + // } + // } + // // if (node.kind === 'Field' && node?.name?.value === 'email') { + // // response.locals.requestSanitation = { + // // ...response.locals.requestSanitation, + // // email: { + // // name: node.name.value, + // // // @ts-ignore + // // alias: node.alias.value, + // // // path: + // // }, + // // }; + // // } + // // if (node.kind === 'Field' && node.name.value === 'profile') { + // // response.locals.requestSanitation = { + // // ...response.locals.requestSanitation, + // // profile: { + // // name: node.name.value, + // // // @ts-ignore + // // alias: node.alias.value, + // // // path: + // // }, + // // }; + // // } + // if (node.kind === 'SelectionSet') { + // // console.log(node); + // return { + // ...node, + // selections: [ + // ...node.selections, + // { + // kind: 'Field', + // name: { + // kind: 'Name', + // value: '__typename' + // } + // } + // ] + // }; + // } + // // @ts-ignore + // // if (node.alias) { + // // // @ts-ignore + // // console.log(node?.alias?.value, node?.name?.value); + // // // console.log({ key, path, parent, acestors }); + // // console.log([...acestors as Array].filter(name => name?.kind === 'Field').map((entry: any) => { + // // return entry?.alias?.value || entry?.name?.value || 'unknown'; + // // })); + // // } + // }, + // // leave(node) { + // // console.log('leave'); + // // // console.log('leave', node); + // // // console.log(node.kind); + // // // @ts-ignore + // // console.log(node.name); + // // // @ts-ignore + // // console.log(node.value); + // // } + // }); + // // console.log(print(edited)); + // request.body.query = print(edited); // console.log(JSON.stringify(request.body, null, 2)); @@ -232,12 +236,11 @@ export const graphQlProxyRouteHandler: Options = { ); /* - * Queries are all allowed, while mutations need to be handled on a case by case basis + * Queries are (mostly, some are restricted) all allowed, while mutations need to be handled on a case by case basis * Some are allowed without auth (cache refresh ones) * Others based on if the user has the appropriate address and/or role */ - const canExecute = - response.locals.canExecute || operationType === OperationTypes.Query; + const canExecute = response.locals.canExecute; logger( `${ @@ -330,165 +333,165 @@ export const graphQlProxyRouteHandler: Options = { console.log('aliases', response.locals.aliases); proxyResponse.headers[Headers.PoweredBy] = 'Colony'; - modifyResponse(response, proxyResponse, (body: Record) => { - try { - if (body) { - // Don't return the API key to the client - // so that it doesn't get exposed - // delete body.integrations['Segment.io'].apiKey; - // console.log({body: JSON.stringify(body, null, 2)}); - // delete body.getUser.abc. - const modifiedBody = { ...body }; - // console.log(modifiedBody); - const callbacks = { - processValue: (key: any, value: any, level: any, path: any, isObjectRoot: any, isArrayElement: any, cbSetValue: any) => { - // console.log({ key, value, isObjectRoot, level, path }); - // ['data', 'getUser', 'profile', 'metadata', '__typename'] - // { data: { getUser: { profile }}} - - console.log({key}) - - const config = { - 'Profile': [{ - field: 'email', - deleteIf: (value: any) => !value?.id || value?.id !== request.session.auth?.address - }] - } - - // const realFieldName = response.locals.requestSanitation[path] ?? key; - // const parentTypename = <>.__typename; - - // const matchingRule = config[parentTypename]; - // if(matchingRule && matchingRule.includes(realFieldName)) { - // delete <>[key] - // } - - // console.log({ }) - /** - * profile1: profile { - * alias1: email - * } - * - * profile2: profile { - * alias2: email - * } - */ - - try { -// @ts-ignore -const matchingConfig = config[value?.__typename]; - -if(matchingConfig) { - for(const property of Object.keys(value)) { - const realFieldName = response.locals.aliases[[...(path.filter((segment: any) => segment !== 'data')), key, property].join('.')] ?? property; - - const matchingRule = matchingConfig.find((rule: any) => rule.field === realFieldName); - - console.log({realFieldName, matchingRule, deleteIf: matchingRule?.deleteIf(value)}) - - if(matchingRule && matchingRule.deleteIf(value)) { - console.log('delete email') - delete value[property]; - } - } -} - } catch(error) { - console.error(error) - } - - - // if(value?.__typename === 'Profile') { - - // const hasAccessToPrivateData = !!value?.id && value?.id === request.session.auth?.address; - - - // for(const property of Object.keys(value)) { - // console.log('alias path: ', [...path, key, property].join('.')) - // const realFieldName = response.locals.aliases[[...(path.filter((segment: any) => segment !== 'data')), key, property].join('.')] ?? property; - // console.log({property, realFieldName}) - // const profileConfig = config['Profile']; - - - // if(profileConfig.includes(realFieldName)) { - // console.log('delete email') - // if(!hasAccessToPrivateData) { - // delete value[property]; - // } - // } - // } - - // // console.log('hello', value, key) - - // // const emailFieldName = response.locals.requestSanitation['email'] ? response.locals.requestSanitation['email'].split('.').pop() : 'email'; - - // // const emailPath = response.locals.requestSanitation['email'] ? ['data', ...response.locals.requestSanitation['email'].split('.')] : [...path, key, 'email']; - // // console.log(emailFieldName) - - - // // console.log({ hasAccessToPrivateData}) - - // // const copiedValue = { - // // ...value - // // } - - // // if (!hasAccessToPrivateData) { - // // delete copiedValue[emailFieldName] - // // } - - // // cbSetValue(copiedValue) - - // // let currentObj = modifiedBody; - // // let idx = 0; - // // while (idx < emailPath.length - 1) { - // // currentObj = currentObj[emailPath[idx]]; - // // if (!currentObj) { - // // delete currentObj[emailPath[emailPath.length - 1]]; - // // } - // // idx++; - // // } - // // if (!hasAccessToPrivateData) { - // // delete currentObj[emailPath[emailPath.length - 1]]; - // // } - - // } - - // if (key === '__typename' && value === 'Profile') { - // const emailPath = response.locals.requestSanitation['email'] ? ['data', ...response.locals.requestSanitation['email'].split('.')] : [...path, 'email']; - // console.log(emailPath) - // // const fieldToDeleteOnProfile = ['email']; - // // delete modifiedBody.data.getUser.profile.email; - // // const hasAlias = !!response.locals.requestSanitation[path.slice(1).join('.')]; - // // console.log({ hasAlias}) - // // console.log(path.join('.')); - // let currentObj = modifiedBody; - // let idx = 0; - // while (idx < emailPath.length -1 ) { - // currentObj = currentObj[emailPath[idx]]; - // if (!currentObj) { - // delete currentObj[emailPath[emailPath.length - 1]]; - // } - // idx++; - // } - // delete currentObj[emailPath[emailPath.length - 1]]; - // } - /* your logic here */ - }, - enterLevel: (level: any, path: any) => { - /* your logic here */ - }, - exitLevel: (level: any, path: any) => { - /* your logic here */ - } - }; - - jt.traverse(body, callbacks); - return modifiedBody; - } - } catch (error) { - // proxyResponse.destroy(); - } - return body; - }); +// modifyResponse(response, proxyResponse, (body: Record) => { +// try { +// if (body) { +// // Don't return the API key to the client +// // so that it doesn't get exposed +// // delete body.integrations['Segment.io'].apiKey; +// // console.log({body: JSON.stringify(body, null, 2)}); +// // delete body.getUser.abc. +// const modifiedBody = { ...body }; +// // console.log(modifiedBody); +// const callbacks = { +// processValue: (key: any, value: any, level: any, path: any, isObjectRoot: any, isArrayElement: any, cbSetValue: any) => { +// // console.log({ key, value, isObjectRoot, level, path }); +// // ['data', 'getUser', 'profile', 'metadata', '__typename'] +// // { data: { getUser: { profile }}} + +// console.log({key}) + +// const config = { +// 'Profile': [{ +// field: 'email', +// deleteIf: (value: any) => !value?.id || value?.id !== request.session.auth?.address +// }] +// } + +// // const realFieldName = response.locals.requestSanitation[path] ?? key; +// // const parentTypename = <>.__typename; + +// // const matchingRule = config[parentTypename]; +// // if(matchingRule && matchingRule.includes(realFieldName)) { +// // delete <>[key] +// // } + +// // console.log({ }) +// /** +// * profile1: profile { +// * alias1: email +// * } +// * +// * profile2: profile { +// * alias2: email +// * } +// */ + +// try { +// // @ts-ignore +// const matchingConfig = config[value?.__typename]; + +// if(matchingConfig) { +// for(const property of Object.keys(value)) { +// const realFieldName = response.locals.aliases[[...(path.filter((segment: any) => segment !== 'data')), key, property].join('.')] ?? property; + +// const matchingRule = matchingConfig.find((rule: any) => rule.field === realFieldName); + +// console.log({realFieldName, matchingRule, deleteIf: matchingRule?.deleteIf(value)}) + +// if(matchingRule && matchingRule.deleteIf(value)) { +// console.log('delete email') +// delete value[property]; +// } +// } +// } +// } catch(error) { +// console.error(error) +// } + + +// // if(value?.__typename === 'Profile') { + +// // const hasAccessToPrivateData = !!value?.id && value?.id === request.session.auth?.address; + + +// // for(const property of Object.keys(value)) { +// // console.log('alias path: ', [...path, key, property].join('.')) +// // const realFieldName = response.locals.aliases[[...(path.filter((segment: any) => segment !== 'data')), key, property].join('.')] ?? property; +// // console.log({property, realFieldName}) +// // const profileConfig = config['Profile']; + + +// // if(profileConfig.includes(realFieldName)) { +// // console.log('delete email') +// // if(!hasAccessToPrivateData) { +// // delete value[property]; +// // } +// // } +// // } + +// // // console.log('hello', value, key) + +// // // const emailFieldName = response.locals.requestSanitation['email'] ? response.locals.requestSanitation['email'].split('.').pop() : 'email'; + +// // // const emailPath = response.locals.requestSanitation['email'] ? ['data', ...response.locals.requestSanitation['email'].split('.')] : [...path, key, 'email']; +// // // console.log(emailFieldName) + + +// // // console.log({ hasAccessToPrivateData}) + +// // // const copiedValue = { +// // // ...value +// // // } + +// // // if (!hasAccessToPrivateData) { +// // // delete copiedValue[emailFieldName] +// // // } + +// // // cbSetValue(copiedValue) + +// // // let currentObj = modifiedBody; +// // // let idx = 0; +// // // while (idx < emailPath.length - 1) { +// // // currentObj = currentObj[emailPath[idx]]; +// // // if (!currentObj) { +// // // delete currentObj[emailPath[emailPath.length - 1]]; +// // // } +// // // idx++; +// // // } +// // // if (!hasAccessToPrivateData) { +// // // delete currentObj[emailPath[emailPath.length - 1]]; +// // // } + +// // } + +// // if (key === '__typename' && value === 'Profile') { +// // const emailPath = response.locals.requestSanitation['email'] ? ['data', ...response.locals.requestSanitation['email'].split('.')] : [...path, 'email']; +// // console.log(emailPath) +// // // const fieldToDeleteOnProfile = ['email']; +// // // delete modifiedBody.data.getUser.profile.email; +// // // const hasAlias = !!response.locals.requestSanitation[path.slice(1).join('.')]; +// // // console.log({ hasAlias}) +// // // console.log(path.join('.')); +// // let currentObj = modifiedBody; +// // let idx = 0; +// // while (idx < emailPath.length -1 ) { +// // currentObj = currentObj[emailPath[idx]]; +// // if (!currentObj) { +// // delete currentObj[emailPath[emailPath.length - 1]]; +// // } +// // idx++; +// // } +// // delete currentObj[emailPath[emailPath.length - 1]]; +// // } +// /* your logic here */ +// }, +// enterLevel: (level: any, path: any) => { +// /* your logic here */ +// }, +// exitLevel: (level: any, path: any) => { +// /* your logic here */ +// } +// }; + +// jt.traverse(body, callbacks); +// return modifiedBody; +// } +// } catch (error) { +// // proxyResponse.destroy(); +// } +// return body; +// }); }, logProvider: () => ({ log: logger, diff --git a/src/types.ts b/src/types.ts index f0962be..2725108 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,6 +64,10 @@ export enum MutationOperations { BridgeUpdateBankAccount = 'bridgeUpdateBankAccount', } +export enum QueryOperations { + GetUserByAddress = 'getUserByAddress', +} + export enum HttpStatuses { OK = 200, BAD_REQUEST = 400, From ff2571200aa36f784c9b83677536c6fb286153df Mon Sep 17 00:00:00 2001 From: Raul Glogovetan Date: Thu, 31 Oct 2024 16:31:21 +0200 Subject: [PATCH 3/3] Refactor: request / server methods types --- src/helpers.ts | 4 +-- src/routes/graphql/graphql.ts | 4 +-- src/routes/graphql/queries.ts | 60 +++++++++++++++++++++++++++++++++++ src/routes/index.ts | 14 ++++---- src/types.ts | 26 +++++++++++++-- 5 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 src/routes/graphql/queries.ts diff --git a/src/helpers.ts b/src/helpers.ts index 3e7de80..3205ec7 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -11,7 +11,7 @@ import { Response, Headers, ContentTypes, - ServerMethods, + RequestMethods, } from '~types'; dotenv.config(); @@ -120,7 +120,7 @@ export const graphqlRequest = async ( variables?: Record ) => { const options = { - method: ServerMethods.Post.toUpperCase(), + method: RequestMethods.Post, headers: { [Headers.ApiKey]: process.env.APPSYNC_API_KEY || '', [Headers.ContentType]: ContentTypes.Json, diff --git a/src/routes/graphql/graphql.ts b/src/routes/graphql/graphql.ts index 3d4d7fc..ee46548 100644 --- a/src/routes/graphql/graphql.ts +++ b/src/routes/graphql/graphql.ts @@ -23,7 +23,7 @@ import { Headers, OperationTypes, Urls, - ServerMethods, + RequestMethods, } from '~types'; import addressCanExecuteMutation from './mutations'; @@ -39,7 +39,7 @@ export const operationExecutionHandler: RequestHandler = async ( // short circut early if ( request.path !== Urls.GraphQL || - request.method !== ServerMethods.Post.toUpperCase() + request.method !== RequestMethods.Post ) { return nextFn(); } diff --git a/src/routes/graphql/queries.ts b/src/routes/graphql/queries.ts new file mode 100644 index 0000000..f12bdcd --- /dev/null +++ b/src/routes/graphql/queries.ts @@ -0,0 +1,60 @@ +import { Request } from 'express-serve-static-core'; + +import { logger, detectOperation } from '~helpers'; +import { QueryOperations } from '~types'; + +const hasQueriesPermissions = async ( + operationName: string, + request: Request, +): Promise => { + const userAddress = request.session.auth?.address; + const { variables = '{}' } = detectOperation(request.body); + + try { + switch (operationName) { + case QueryOperations.GetUserByAddress: { + return true; + } + // By default all queries are allowed + default: { + return true; + } + } + } catch (error) { + logger( + `Error when attempting to check if user ${userAddress} can execute query ${operationName} with variables ${variables}`, + error, + ); + /* + * If anything fails just prevent the query from executing + */ + return false; + } +}; + +const addressCanExecuteQuery = async ( + request: Request, +): Promise => { + try { + const { operations } = detectOperation(request.body); + + if (!operations.length) { + return false; + } + + const canExecuteAllOperations = await Promise.all( + operations.map( + async (operationName) => + await hasQueriesPermissions(operationName, request), + ), + ); + return canExecuteAllOperations.every((canExecute) => canExecute); + } catch (error) { + /* + * If anything fails just prevent the query from executing + */ + return false; + } +}; + +export default addressCanExecuteQuery; diff --git a/src/routes/index.ts b/src/routes/index.ts index f981368..4f425c8 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,6 +1,6 @@ import { createProxyMiddleware } from "http-proxy-middleware"; -import { RouteHandler, ServerMethods, Urls } from '~types'; +import { RouteHandler, ExpressServerMethods, Urls } from '~types'; import { handleHealthRoute } from './health'; import { @@ -18,7 +18,7 @@ const routes: RouteHandler[] = [ * Server Health */ { - method: ServerMethods.Get, + method: ExpressServerMethods.Get, url: Urls.Health, handler: handleHealthRoute, }, @@ -26,22 +26,22 @@ const routes: RouteHandler[] = [ * Auth */ { - method: ServerMethods.Get, + method: ExpressServerMethods.Get, url: Urls.Nonce, handler: handleNonceRoute, }, { - method: ServerMethods.Post, + method: ExpressServerMethods.Post, url: Urls.Auth, handler: handleAuthRoute, }, { - method: ServerMethods.Post, + method: ExpressServerMethods.Post, url: Urls.DeAuth, handler: handleDeauthRoute, }, { - method: ServerMethods.Get, + method: ExpressServerMethods.Get, url: Urls.Check, handler: handleCheck, }, @@ -49,7 +49,7 @@ const routes: RouteHandler[] = [ * GraphQL */ { - method: ServerMethods.Use, + method: ExpressServerMethods.Use, url: Urls.GraphQL, handler: createProxyMiddleware(graphQlProxyRouteHandler), }, diff --git a/src/types.ts b/src/types.ts index 2725108..01ccfef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -126,14 +126,36 @@ export type StaticOriginCallback = ( origin?: StaticOrigin | undefined, ) => void; -export enum ServerMethods { +export enum ExpressServerMethods { Post = 'post', Get = 'get', Use = 'use', } +export enum RequestMethods { + Post = 'POST', + Get = 'GET', +} + +// These should really be defined by the "stream" module +// But I couldn't for the life of me find either the exported types +// or where they are defined +export enum StreamEvent { + Close = 'close', + Data = 'data', + End = 'end', + Error = 'error', + Pause = 'pause', + Readable = 'readable', + Resume = 'resume', +} + +export enum Encoding { + Utf8 = 'utf8', +} + export interface RouteHandler { - method: ServerMethods; + method: ExpressServerMethods; url: Urls; handler: RequestHandler; }