From 531992f9b38589408ac31aeb905475d105ef02fc Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Fri, 10 Jul 2020 18:10:56 -0400 Subject: [PATCH 01/18] Get started on CLI --- package-lock.json | 1220 +++++++++++++++++++---------------- package.json | 93 +-- src/cli/create/drivers.ts | 13 + src/cli/create/index.ts | 66 ++ src/cli/create/questions.ts | 50 ++ src/cli/index.ts | 18 + src/cli/start.ts | 14 + src/mail/index.ts | 12 - src/utils/has-fields.ts | 4 +- tsconfig.json | 7 +- 10 files changed, 897 insertions(+), 600 deletions(-) create mode 100644 src/cli/create/drivers.ts create mode 100644 src/cli/create/index.ts create mode 100644 src/cli/create/questions.ts create mode 100644 src/cli/index.ts create mode 100644 src/cli/start.ts diff --git a/package-lock.json b/package-lock.json index 239a0f1604..7ca6338854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "api-node", - "version": "0.0.1", + "version": "9.0.0-alpha.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -65,12 +65,49 @@ "@babel/helper-validator-identifier": "^7.10.1", "chalk": "^2.0.0", "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@google-cloud/common": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.2.0.tgz", - "integrity": "sha512-rNszQZBkzK9ZON9s8OFEVbD6zuTyGQ9ZLL5P1pakzE5qSCyZqkdNl3S2NBsUzlMUpIwJK97wCXcXgjtIFrfvMw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.3.2.tgz", + "integrity": "sha512-W7JRLBEJWYtZQQuGQX06U6GBOSLrSrlvZxv6kGNwJtFrusu6AVgZltQ9Pajuz9Dh9aSXy9aTnBcyxn2/O0EGUw==", "requires": { "@google-cloud/projectify": "^2.0.0", "@google-cloud/promisify": "^2.0.0", @@ -107,18 +144,18 @@ } }, "@google-cloud/paginator": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.1.tgz", - "integrity": "sha512-ykqRmHRg6rcIZTE+JjUMNBKOQ8uvmbVrhY//lTxZgf5QBPbZW3PoN7VK+D43yCaRJJjRmmWsaG5YdxLR6h0n0A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.2.tgz", + "integrity": "sha512-kXK+Dbz4pNvv8bKU80Aw5HsIdgOe0WuMTd8/fI6tkANUxzvJOVJQQRsWVqcHSWK2RXHPTA9WBniUCwY6gAJDXw==", "requires": { "arrify": "^2.0.0", "extend": "^3.0.2" } }, "@google-cloud/projectify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.0.tgz", - "integrity": "sha512-7wZ+m4N3Imtb5afOPfqNFyj9cKrlfVQ+t5YRxLS7tUpn8Pn/i7QuVubZRTXllaWjO4T5t/gm/r2x7oy5ajjvFQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==" }, "@google-cloud/promisify": { "version": "2.0.1", @@ -126,9 +163,9 @@ "integrity": "sha512-82EQzwrNauw1fkbUSr3f+50Bcq7g4h0XvLOk8C5e9ABkXYHei7ZPi9tiMMD7Vh3SfcdH97d1ibJ3KBWp2o1J+w==" }, "@google-cloud/storage": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.1.1.tgz", - "integrity": "sha512-w/64V+eJl+vpYUXT15sBcO8pX0KTmb9Ni2ZNuQQ8HmyhAbEA3//G8JFaLPCXGBWO2/b0OQZytUT6q2wII9a9aQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.1.2.tgz", + "integrity": "sha512-j2blsBVv6Tt5Z7ff6kOSIg5zVQPdlcTQh/4zMb9h7xMj4ekwndQA60le8c1KEa+Y6SR3EM6ER2AvKYK53P7vdQ==", "requires": { "@google-cloud/common": "^3.0.0", "@google-cloud/paginator": "^3.0.0", @@ -150,7 +187,7 @@ "readable-stream": "^3.4.0", "snakeize": "^0.1.0", "stream-events": "^1.0.1", - "through2": "^3.0.0", + "through2": "^4.0.0", "xdg-basedir": "^4.0.0" }, "dependencies": { @@ -178,12 +215,11 @@ } }, "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "readable-stream": "3" } } } @@ -240,26 +276,26 @@ } }, "@slynova/flydrive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@slynova/flydrive/-/flydrive-1.0.1.tgz", - "integrity": "sha512-+//qP4qkJl0ssTOVfSlosWZQ09MXsIgg3RjWs5Jma8wOaR0XW61vGJ4fkVk4XDRVajGCA8FkcQHw9D1wJGtARA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@slynova/flydrive/-/flydrive-1.0.2.tgz", + "integrity": "sha512-bQ2H6L9f8vYOw3mPZH/GHP7s1vvWfYtk3qVbm15coodo077oE+4wb2M143Kai+jH31sAPxYWHI3J2O7f0b1jng==", "requires": { "fs-extra": "^9.0.0", "node-exceptions": "^4.0.1" } }, "@slynova/flydrive-gcs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@slynova/flydrive-gcs/-/flydrive-gcs-1.0.1.tgz", - "integrity": "sha512-rbvUxpq3aKrdD+uyy8CVXuaezlIBkm/AJ8u80yEegkl0VPSEsLGL1jnpebLAW37Ts5zAafJGodXLFvmhCTGzFw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@slynova/flydrive-gcs/-/flydrive-gcs-1.0.2.tgz", + "integrity": "sha512-bCwzg0Vn988vke3tiVt51i95ZOaEDIhVHtqTlOCSCHajf1qIb915OvfOGDxIOA3NwDjnTy5qEP16apNm9Y5GPw==", "requires": { "@google-cloud/storage": "^5.0.0" } }, "@slynova/flydrive-s3": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@slynova/flydrive-s3/-/flydrive-s3-1.0.1.tgz", - "integrity": "sha512-sD1QGY0IH7Y3mIhn+KrBRz6kkeHflouHoYnfRcpnOB4/WO/TAF73r10sLBSrfu4xtoRrNKSizXAsPEW6q68BHA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@slynova/flydrive-s3/-/flydrive-s3-1.0.2.tgz", + "integrity": "sha512-jviTnixVvCf7Mh8rLwFAHLpUxNpiItCFqv8EuEbtHIYQXztYWvYfcO2eOm3PbQzMzjZquwbUMSePkfmsLxwjmw==", "requires": { "aws-sdk": "^2.680.0" } @@ -294,11 +330,16 @@ "@types/node": "*" } }, + "@types/clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-b2VgNzu3BxxM2nGhZSMzkiaTaCClevKkBCtgpV2Al0eGaEeQgTk5qHedzNxi/SgsvmDbqxWKDAIrxZNVKyDCXw==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/connect": { "version": "3.4.33", @@ -319,9 +360,9 @@ } }, "@types/express": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", - "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", "dev": true, "requires": { "@types/body-parser": "*", @@ -340,9 +381,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", - "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", + "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", "dev": true, "requires": { "@types/node": "*", @@ -360,12 +401,31 @@ "@types/node": "*" } }, + "@types/fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/hapi__joi": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-17.1.2.tgz", - "integrity": "sha512-2S6+hBISRQ5Ca6/9zfQi7zPueWMGyZxox6xicqJuW1/aC/6ambLyh+gDqY5fi8JBuHmGKMHldSfEpIXJtTmGKQ==", + "version": "17.1.4", + "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-17.1.4.tgz", + "integrity": "sha512-gqY3TeTyZvnyNhM02HgyCIoGIWsTFMnuzMfnD8evTsr1KIfueGJaz+QC77j+dFvhZ5cJArUNjDRHUjPxNohzGA==", "dev": true }, + "@types/inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, "@types/jsonwebtoken": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", @@ -376,9 +436,9 @@ } }, "@types/lodash": { - "version": "4.14.156", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.156.tgz", - "integrity": "sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ==", + "version": "4.14.157", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.157.tgz", + "integrity": "sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==", "dev": true }, "@types/mime": { @@ -494,12 +554,23 @@ "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=" + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true }, "@types/strip-json-comments": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==" + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/tunnel": { "version": "0.0.0", @@ -584,9 +655,9 @@ } }, "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", "requires": { "debug": "4" } @@ -599,6 +670,14 @@ "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } } }, "ajv": { @@ -613,39 +692,60 @@ } }, "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, "requires": { "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + } } }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { - "color-convert": "^1.9.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } } }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -668,7 +768,8 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true }, "argon2": { "version": "0.26.2", @@ -744,7 +845,8 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true }, "array-flatten": { "version": "1.1.1", @@ -797,9 +899,9 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "async": { @@ -828,9 +930,9 @@ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, "aws-sdk": { - "version": "2.705.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.705.0.tgz", - "integrity": "sha512-eD3YD3UgCdFIYlkAVYW1rbriKSNHm7nsZbYsBuo91pbyq4XYT56w2oTyNECorYPoq//Y4LQTTx8cbOuEjsNi3w==", + "version": "2.713.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.713.0.tgz", + "integrity": "sha512-axR1eOVn134KXJc1IT+Au2TXcK6oswY+4nvGe5GfU3pXeehhe0xNeP9Bw9yF36TRBxuvu4IJ2hRHDKma05smgA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -961,9 +1063,10 @@ "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true }, "bl": { "version": "3.0.0", @@ -985,6 +1088,15 @@ } } }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, "bn.js": { "version": "4.11.9", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", @@ -1036,6 +1148,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -1121,6 +1234,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" @@ -1129,7 +1243,8 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true } } }, @@ -1139,20 +1254,24 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, "chokidar": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -1202,11 +1321,15 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==" + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "requires": { "restore-cursor": "^3.1.0" } @@ -1219,8 +1342,32 @@ "requires": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" + }, + "dependencies": { + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } } }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1291,10 +1438,9 @@ } }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" }, "compare-versions": { "version": "3.6.0", @@ -1454,6 +1600,17 @@ "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "crypto-random-string": { @@ -1465,6 +1622,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, "requires": { "array-find-index": "^1.0.1" } @@ -1491,6 +1649,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, "requires": { "get-stdin": "^4.0.1", "meow": "^3.3.0" @@ -1507,7 +1666,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decode-uri-component": { "version": "0.2.0", @@ -1617,7 +1777,8 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true }, "doctrine": { "version": "3.0.0", @@ -1634,13 +1795,6 @@ "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "requires": { "is-obj": "^2.0.0" - }, - "dependencies": { - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - } } }, "dotenv": { @@ -1663,6 +1817,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "dev": true, "requires": { "xtend": "^4.0.0" } @@ -1707,8 +1862,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -1724,12 +1878,12 @@ } }, "enquirer": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", - "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { - "ansi-colors": "^3.2.1" + "ansi-colors": "^4.1.1" } }, "ent": { @@ -1741,6 +1895,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -1753,13 +1908,12 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.3.1.tgz", - "integrity": "sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.4.0.tgz", + "integrity": "sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1800,47 +1954,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -1852,15 +1965,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -1974,9 +2078,9 @@ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "execa": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", - "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -2187,6 +2291,16 @@ } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -2313,7 +2427,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } @@ -2331,6 +2444,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -2643,8 +2757,32 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, "optional": true }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -2700,9 +2838,9 @@ } }, "gaxios": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", - "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.4.tgz", + "integrity": "sha512-97NmFuMETFQh6gqPUxkqjxRMjmY8aRKRMphIkgO/b90AbCt5wAVuXsp8oWjIXlLN2pIK/fsXD8edcM7ULkFMLg==", "requires": { "abort-controller": "^3.0.0", "extend": "^3.0.2", @@ -2721,9 +2859,9 @@ } }, "gcs-resumable-upload": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.0.tgz", - "integrity": "sha512-gB8xH6EjYCv9lfBEL4FK5+AMgTY0feYoNHAYOV5nCuOrDPhy5MOiyJE8WosgxhbKBPS361H7fkwv6CTufEh9bg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", + "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", "requires": { "abort-controller": "^3.0.0", "configstore": "^5.0.0", @@ -2754,7 +2892,8 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true }, "get-stream": { "version": "5.1.0", @@ -2805,6 +2944,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -2848,20 +2988,12 @@ "dev": true, "requires": { "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } } }, "google-auth-library": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.2.tgz", - "integrity": "sha512-o/F/GiOPzDc49v5/6vfrEz3gRXvES49qGP84rrl3SO0efJA/M52hFwv2ozd1EC1TPrLj75Moj3iPgKGuGs6smA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.4.tgz", + "integrity": "sha512-O1uT4F1mMu5OYnvH0ppg6mqP5pGQrqzg2nrzd7ay3DA+VD0mp9VM+Julf/bov2OuxgFs2DD+gWnp2nZPB4EflA==", "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -2896,9 +3028,9 @@ } }, "google-p12-pem": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", - "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.2.tgz", + "integrity": "sha512-tbjzndQvSIHGBLzHnhDs3cL4RBjLbLXc2pYvGH+imGVu5b4RMAttUTdnmW2UH0t11QeBTXZ7wlXPS7hrypO/tg==", "requires": { "node-forge": "^0.9.0" } @@ -2951,12 +3083,13 @@ "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true }, "gtoken": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", - "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.2.tgz", + "integrity": "sha512-lull70rHCTvRTmAt+R/6W5bTtx4MjHku7AwJwK5fGqhOmygcZud0nrZcX+QUNfBJwCzqy7S5i1Bc4NYnr5PMMA==", "requires": { "gaxios": "^3.0.0", "google-p12-pem": "^3.0.0", @@ -3005,10 +3138,9 @@ } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-unicode": { "version": "2.0.1", @@ -3102,7 +3234,8 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true }, "http-errors": { "version": "1.7.2", @@ -3276,10 +3409,13 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } }, "inflight": { "version": "1.0.6", @@ -3300,6 +3436,26 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "inquirer": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.1.tgz", + "integrity": "sha512-/+vOpHQHhoh90Znev8BXiuw1TDQ7IDxWsQnFafUEoK5+4uN5Eoz1p+3GqOj/NtzEi9VzWKQcV9Bm+i8moxedsA==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.16", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -3340,12 +3496,14 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -3403,13 +3561,13 @@ "is-finite": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.1", @@ -3422,13 +3580,13 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-plain-object": { "version": "2.0.4", @@ -3473,7 +3631,8 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true }, "is-windows": { "version": "1.0.2", @@ -3483,7 +3642,8 @@ "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true }, "isarray": { "version": "1.0.0", @@ -3724,9 +3884,9 @@ "dev": true }, "lint-staged": { - "version": "10.2.10", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.10.tgz", - "integrity": "sha512-dgelFaNH6puUGAcU+OVMgbfpKSerNYsPSn6+nlbRDjovL0KigpsVpCu0PFZG6BJxX8gnHJqaZlR9krZamQsb0w==", + "version": "10.2.11", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.11.tgz", + "integrity": "sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -3744,75 +3904,17 @@ "please-upgrade-node": "^3.2.0", "string-argv": "0.3.1", "stringify-object": "^3.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "liquidjs": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-9.12.0.tgz", - "integrity": "sha512-Z1fERRy5TG76kNQfDuCl9+YmK6RMBsGhi3fl/57rBb5BbuVAS0YA7R3kh0/V0TwezpeywUf5xasR+Np86nD2TA==" + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-9.14.1.tgz", + "integrity": "sha512-5WYoIggCjGhOld1fZSIbA21AwxgQGcHv5UbzbiyJ9/EY8fhkaW8jQHVpfJ7gFHo4CtgZC4bRRd2QKTPjfT5cRg==" }, "listr2": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.1.7.tgz", - "integrity": "sha512-XCC1sWLkBFFIMIRwG/LedgHUzN2XLEo02ZqXn6fwuP0GlXGE5BCuL6EAbQFb4vZB+++YEonzEXDPWQe+jCoF6Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.2.0.tgz", + "integrity": "sha512-Q8qbd7rgmEwDo1nSyHaWQeztfGsdL6rb4uh7BA+Q80AZiDET5rVntiU1+13mu2ZTDVaBVbvAD1Db11rnu3l9sg==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -3825,55 +3927,11 @@ "through": "^2.3.8" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { + "indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -3881,6 +3939,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -3893,6 +3952,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, "requires": { "error-ex": "^1.2.0" } @@ -3909,9 +3969,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash.includes": { "version": "4.3.0", @@ -3955,58 +4015,6 @@ "dev": true, "requires": { "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "log-update": { @@ -4021,29 +4029,10 @@ "wrap-ansi": "^6.2.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "slice-ansi": { @@ -4063,6 +4052,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -4094,7 +4084,8 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "make-iterator": { "version": "1.0.1", @@ -4112,7 +4103,8 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true }, "map-visit": { "version": "1.0.0", @@ -4131,6 +4123,7 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, "requires": { "camelcase-keys": "^2.0.0", "decamelize": "^1.1.2", @@ -4287,6 +4280,11 @@ "tedious": "^6.6.2" } }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, "mysql": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", @@ -4298,11 +4296,6 @@ "sqlstring": "2.3.1" } }, - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" - }, "nanoid": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.10.tgz", @@ -4395,26 +4388,74 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "optional": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "optional": true + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + } + } + }, "node-notifier": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "dev": true, "requires": { "growly": "^1.3.0", "is-wsl": "^1.1.0", "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } } }, "node-pre-gyp": { @@ -4503,6 +4544,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -4513,7 +4555,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "npm-bundled": { "version": "1.1.1", @@ -4687,9 +4730,9 @@ } }, "oracledb": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-4.2.0.tgz", - "integrity": "sha512-07ZylNcUB9wknsiRa7dNqDWgGK3loP8eNWuoCjsiCOZ19PA1g8QLu+0gah7ty82VXl/MOQYFCMl5OpjD9Aqjcw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.0.0.tgz", + "integrity": "sha512-NLE3t6KiAkpBHA1/zgjNiKaa9Z4Nnp4PuB3d0b3Kz4C8klrIrMKfHIGUySlwqgDW588m7/2hnPxU7PH6wjBd6g==" }, "os-homedir": { "version": "1.0.2", @@ -4797,9 +4840,9 @@ } }, "parse-ms": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", - "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", "dev": true }, "parse-passwd": { @@ -4869,15 +4912,15 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.2.1.tgz", - "integrity": "sha512-DKzffhpkWRr9jx7vKxA+ur79KG+SKw+PdjMb1IRhMiKI9zqYUGczwFprqy+5Veh/DCcFs1Y6V8lRLN5I1DlleQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.3.0.tgz", + "integrity": "sha512-jQPKWHWxbI09s/Z9aUvoTbvGgoj98AU7FDCcQ7kdejupn/TcNpx56v2gaOTzXkzOajmOEJEdi9eTh9cA2RVAjQ==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", - "pg-connection-string": "^2.2.3", + "pg-connection-string": "^2.3.0", "pg-pool": "^3.2.1", - "pg-protocol": "^1.2.4", + "pg-protocol": "^1.2.5", "pg-types": "^2.1.0", "pgpass": "1.x", "semver": "4.3.2" @@ -4891,9 +4934,9 @@ } }, "pg-connection-string": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.3.tgz", - "integrity": "sha512-I/KCSQGmOrZx6sMHXkOs2MjddrYcqpza3Dtsy0AjIgBr/bZiPJRK9WhABXN1Uy1UDazRbi9gZEzO2sAhL5EqiQ==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.3.0.tgz", + "integrity": "sha512-ukMTJXLI7/hZIwTW7hGMZJ0Lj0S2XQBCJ4Shv4y1zgQ/vqVea+FLhzywvPj0ujSuofu+yA4MYHGZPTsgjBgJ+w==" }, "pg-int8": { "version": "1.0.1", @@ -4906,9 +4949,9 @@ "integrity": "sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA==" }, "pg-protocol": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.4.tgz", - "integrity": "sha512-/8L/G+vW/VhWjTGXpGh8XVkXOFx1ZDY+Yuz//Ab8CfjInzFkreI+fDG3WjCeSra7fIZwAFxzbGptNbm8xSXenw==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.5.tgz", + "integrity": "sha512-1uYCckkuTfzz/FCefvavRywkowa6M5FohNMF5OjKrqo9PSR8gYc8poVmwwYQaBxhmQdBjhtP514eXy9/Us2xKg==" }, "pg-types": { "version": "2.2.0", @@ -4933,22 +4976,26 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -4967,18 +5014,30 @@ } }, "pino-colada": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/pino-colada/-/pino-colada-1.6.1.tgz", - "integrity": "sha512-9jRyBrv60v03DRuF/MTUKYoEkT3Btk8NPKdX8IA45Tb1sR0PMZ0y7tPSLgYaXu2+Zqqc6I/RJx1Nh0zlt+M/ng==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pino-colada/-/pino-colada-2.0.1.tgz", + "integrity": "sha512-88CrKGs1graJaFqZStm8sdh/L9/b8pUcmg+Ylf8w4oQl6i2dLMDvlk76PhfSEm3yMicFB+RY+T8Dl5IZ039S5w==", "dev": true, "requires": { - "chalk": "^2.0.1", + "chalk": "^3.0.0", "fast-json-parse": "^1.0.2", "pad-left": "^2.1.0", "pad-right": "^0.2.2", "prettier-bytes": "^1.0.3", - "pretty-ms": "^2.1.0", - "split2": "^2.1.1" + "pretty-ms": "^5.0.0", + "split2": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "pino-http": { @@ -5014,12 +5073,6 @@ "semver-compare": "^1.0.0" } }, - "plur": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", - "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", - "dev": true - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -5110,14 +5163,12 @@ } }, "pretty-ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", - "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-5.1.0.tgz", + "integrity": "sha512-4gaK1skD2gwscCfkswYQRmddUb2GJZtzDGRjHWadVHtK/DIKFufa12MvES6/xu1tVbUYeia5bmLcwJtZJQUqnw==", "dev": true, "requires": { - "is-finite": "^1.0.1", - "parse-ms": "^1.0.0", - "plur": "^1.0.0" + "parse-ms": "^2.1.0" } }, "process-nextick-args": { @@ -5243,6 +5294,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -5253,6 +5305,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -5265,6 +5318,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -5274,6 +5328,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -5283,6 +5338,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -5307,6 +5363,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -5323,19 +5380,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" - }, - "dependencies": { - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "^2.0.0" - } - } } }, "regex-not": { @@ -5367,6 +5415,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, "requires": { "is-finite": "^1.0.0" } @@ -5467,6 +5516,21 @@ "path-parse": "^1.0.6" } }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + } + } + }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -5491,7 +5555,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "requires": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -5531,11 +5594,15 @@ "glob": "^7.1.3" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "dev": true, + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", "requires": { "tslib": "^1.9.0" } @@ -5758,7 +5825,8 @@ "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true }, "signal-exit": { "version": "3.0.3", @@ -5817,39 +5885,29 @@ "dev": true }, "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "color-convert": "^1.9.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true } } @@ -5986,7 +6044,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-resolve": { "version": "0.5.3", @@ -6004,6 +6063,7 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -6018,6 +6078,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -6026,12 +6087,14 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -6040,7 +6103,8 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true }, "split": { "version": "1.0.1", @@ -6059,12 +6123,25 @@ } }, "split2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", - "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz", + "integrity": "sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==", "dev": true, "requires": { - "through2": "^2.0.2" + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "sprintf-js": { @@ -6074,12 +6151,20 @@ "dev": true }, "sqlite3": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", - "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.0.tgz", + "integrity": "sha512-rjvqHFUaSGnzxDy2AHCwhHy6Zp6MNJzCPGYju4kD8yi6bze4d1/zMTg6C7JI49b7/EM7jKMTvyfN/4ylBKdwfw==", "requires": { - "nan": "^2.12.1", + "node-addon-api": "2.0.0", + "node-gyp": "3.x", "node-pre-gyp": "^0.11.0" + }, + "dependencies": { + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + } } }, "sqlstring": { @@ -6155,7 +6240,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6179,13 +6263,20 @@ "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + } } }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -6194,6 +6285,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, "requires": { "is-utf8": "^0.2.0" } @@ -6208,6 +6300,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, "requires": { "get-stdin": "^4.0.1" } @@ -6223,12 +6316,11 @@ "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "table": { @@ -6249,12 +6341,6 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -6267,17 +6353,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -6463,6 +6538,14 @@ "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==" }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -6496,6 +6579,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } @@ -6517,17 +6601,20 @@ "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true }, "ts-node": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, "requires": { "arg": "^4.1.0", "diff": "^4.0.1", @@ -6537,9 +6624,10 @@ } }, "ts-node-dev": { - "version": "1.0.0-pre.49", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.0.0-pre.49.tgz", - "integrity": "sha512-iJd4QPPOaCAByl/WuEdmDX8xDR2GmoWYu6aKvGudGUcfP1sJRjpaHb7oFDuvBspQF1xhxUdbjfHuvEtZPwKZFQ==", + "version": "1.0.0-pre.51", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.0.0-pre.51.tgz", + "integrity": "sha512-b+OMsGtxYDsPPN2CMKcIYSwffTSDFB4pzna+10mTwIvl6DLw04ge9VfyTycfKEQ2ZSWt+ecWCylNfZJIyUWs/Q==", + "dev": true, "requires": { "chokidar": "^3.4.0", "dateformat": "~1.0.4-1.2.3", @@ -6558,12 +6646,14 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -6574,6 +6664,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, "requires": { "@types/strip-bom": "^3.0.0", "@types/strip-json-comments": "0.0.30", @@ -6584,7 +6675,8 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, @@ -6612,6 +6704,49 @@ "semver": "^5.3.0", "tslib": "^1.10.0", "tsutils": "^2.29.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "tsutils": { @@ -6651,9 +6786,9 @@ } }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, "type-is": { @@ -6679,9 +6814,9 @@ } }, "typescript": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", - "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", + "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==", "dev": true }, "uid-safe": { @@ -6839,6 +6974,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -6860,10 +6996,9 @@ } }, "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } @@ -7076,7 +7211,8 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index eac8852cf9..fe35b6c97a 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,16 @@ { "name": "api-node", - "version": "0.0.1", + "version": "9.0.0-alpha.3", "description": "The Directus API in Node.js", "main": "dist/server.js", + "bin": { + "directus": "dist/cli/index.js" + }, "scripts": { "start": "NODE_ENV=production node dist/server.js", "build": "rimraf dist && tsc -b && copyfiles \"src/**/*.*\" -e \"src/**/*.ts\" -u 1 dist", - "dev": "LOG_LEVEL=trace ts-node-dev src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada" + "dev": "LOG_LEVEL=trace ts-node-dev src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada", + "cli": "ts-node --script-mode --transpile-only src/cli/index.ts" }, "repository": { "type": "git", @@ -27,14 +31,60 @@ "url": "https://github.com/directus/api-node/issues" }, "homepage": "https://github.com/directus/api-node#readme", + "dependencies": { + "@hapi/joi": "^17.1.1", + "@slynova/flydrive": "^1.0.2", + "@slynova/flydrive-gcs": "^1.0.2", + "@slynova/flydrive-s3": "^1.0.2", + "argon2": "^0.26.2", + "atob": "^2.1.2", + "body-parser": "^1.19.0", + "busboy": "^0.3.1", + "camelcase": "^6.0.0", + "chalk": "^4.1.0", + "clear": "^0.1.0", + "commander": "^5.1.0", + "cookie-parser": "^1.4.5", + "dotenv": "^8.2.0", + "exif-reader": "^1.0.3", + "express": "^4.17.1", + "express-async-handler": "^1.1.4", + "express-pino-logger": "^5.0.0", + "express-session": "^1.17.1", + "fs-extra": "^9.0.1", + "get-port": "^5.1.1", + "grant": "^5.2.0", + "icc": "^2.0.0", + "inquirer": "^7.3.1", + "jsonwebtoken": "^8.5.1", + "knex": "^0.21.1", + "liquidjs": "^9.14.1", + "lodash": "^4.17.19", + "ms": "^2.1.2", + "mssql": "^6.2.0", + "mysql": "^2.18.1", + "nanoid": "^3.1.10", + "nodemailer": "^6.4.10", + "oracledb": "^5.0.0", + "pg": "^8.3.0", + "pino": "^6.3.2", + "resolve-cwd": "^3.0.0", + "sharp": "^0.25.4", + "sqlite3": "^5.0.0", + "uuid": "^8.2.0", + "uuid-validate": "0.0.3" + }, "devDependencies": { "@types/atob": "^2.1.2", "@types/busboy": "^0.2.3", + "@types/clear": "^0.1.0", "@types/cookie-parser": "^1.4.2", "@types/express": "^4.17.7", "@types/express-pino-logger": "^4.0.2", "@types/express-session": "^1.17.0", + "@types/fs-extra": "^9.0.1", "@types/hapi__joi": "^17.1.3", + "@types/inquirer": "^6.5.0", "@types/jsonwebtoken": "^8.5.0", "@types/lodash": "^4.14.157", "@types/ms": "^0.7.31", @@ -52,6 +102,7 @@ "prettier": "^2.0.5", "rimraf": "^3.0.2", "ts-node": "^8.10.2", + "ts-node-dev": "^1.0.0-pre.51", "tslint": "^6.1.2", "typescript": "^3.9.6" }, @@ -64,43 +115,5 @@ "*.{js,ts}": [ "prettier --write" ] - }, - "dependencies": { - "@hapi/joi": "^17.1.1", - "@slynova/flydrive": "^1.0.2", - "@slynova/flydrive-gcs": "^1.0.2", - "@slynova/flydrive-s3": "^1.0.2", - "argon2": "^0.26.2", - "atob": "^2.1.2", - "body-parser": "^1.19.0", - "busboy": "^0.3.1", - "camelcase": "^6.0.0", - "cookie-parser": "^1.4.5", - "dotenv": "^8.2.0", - "exif-reader": "^1.0.3", - "express": "^4.17.1", - "express-async-handler": "^1.1.4", - "express-pino-logger": "^5.0.0", - "express-session": "^1.17.1", - "get-port": "^5.1.1", - "grant": "^5.2.0", - "icc": "^2.0.0", - "jsonwebtoken": "^8.5.1", - "knex": "^0.21.1", - "liquidjs": "^9.14.1", - "lodash": "^4.17.19", - "ms": "^2.1.2", - "mssql": "^6.2.0", - "mysql": "^2.18.1", - "nanoid": "^3.1.10", - "nodemailer": "^6.4.10", - "oracledb": "^5.0.0", - "pg": "^8.3.0", - "pino": "^6.3.2", - "sharp": "^0.25.4", - "sqlite3": "^5.0.0", - "ts-node-dev": "^1.0.0-pre.51", - "uuid": "^8.2.0", - "uuid-validate": "0.0.3" } } diff --git a/src/cli/create/drivers.ts b/src/cli/create/drivers.ts new file mode 100644 index 0000000000..9ffed8cf2c --- /dev/null +++ b/src/cli/create/drivers.ts @@ -0,0 +1,13 @@ +export const drivers = { + sqlite3: 'SQLite', + mysql: 'MySQL (/ MariaDB / Aurora)', + pg: 'PostgreSQL (/ Amazon Redshift)', + oracledb: 'Oracle Database', + mssql: 'Microsoft SQL Server', +}; + +export function getDriverForClient(client: string) { + for (const [key, value] of Object.entries(drivers)) { + if (value === client) return key; + } +} diff --git a/src/cli/create/index.ts b/src/cli/create/index.ts new file mode 100644 index 0000000000..0f4433d3d7 --- /dev/null +++ b/src/cli/create/index.ts @@ -0,0 +1,66 @@ +import fse from 'fs-extra'; +import chalk from 'chalk'; +import inquirer from 'inquirer'; +import { resolve } from 'path'; +import { databaseQuestions } from './questions'; +import { drivers, getDriverForClient } from './drivers'; + +export default async function create(directory: string, options: Record) { + const path = resolve(directory); + checkRequirements(); + + if (await fse.pathExists(path)) { + const stat = await fse.stat(path); + + if (stat.isDirectory() === false) { + console.error( + `Destination '${chalk.red(directory)}' already exists and is not a directory.` + ); + process.exit(1); + } + + const files = await fse.readdir(path); + + if (files.length > 0) { + console.error( + `Destination '${chalk.red( + directory + )}' already exists and is not an empty directory.` + ); + process.exit(1); + } + } + + let { client } = await inquirer.prompt([ + { + type: 'list', + name: 'client', + message: 'Choose your database client', + choices: Object.values(drivers), + }, + ]); + + client = getDriverForClient(client); + + const responses = await inquirer.prompt( + databaseQuestions[client].map((question) => question({ client })) + ); + + /** @todo + * - See if you can connect to DB + * - Install Directus system stuff into DB + * - Start the Node API + */ +} + +function checkRequirements() { + const nodeVersion = process.versions.node; + const major = +nodeVersion.split('.')[0]; + + if (major < 12) { + console.error(`You are running Node ${nodeVersion}.`); + console.error('Directus requires Node 12 and up.'); + console.error('Please update your Node version and try again.'); + process.exit(1); + } +} diff --git a/src/cli/create/questions.ts b/src/cli/create/questions.ts new file mode 100644 index 0000000000..1403d0afaa --- /dev/null +++ b/src/cli/create/questions.ts @@ -0,0 +1,50 @@ +const filename = () => ({ + type: 'input', + name: 'filename', + message: 'Filename:', + default: './data.db', +}); + +const host = () => ({ + type: 'input', + name: 'host', + message: 'Host:', + default: '127.0.0.1', +}); + +const port = ({ client }) => ({ + type: 'input', + name: 'port', + message: 'Port:', + default() { + const ports = { + pg: 5432, + mysql: 3306, + oracledb: 1521, + mssql: 1433, + }; + + return ports[client]; + }, +}); + +const username = () => ({ + type: 'input', + name: 'username', + message: 'Username:', +}); + +const password = () => ({ + type: 'password', + name: 'password', + message: 'Password:', + mask: '*', +}); + +export const databaseQuestions = { + sqlite3: [filename], + mysql: [host, port, username, password], + pg: [host, port, username, password], + oracledb: [host, port, username, password], + mssql: [host, port, username, password], +}; diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000000..3ea0f0cfee --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +import program from 'commander'; + +const pkg = require('../../package.json'); + +import start from './start'; +import create from './create'; + +program.version(pkg.version, '-v, --version'); + +program.name('directus').usage('[command] [options]'); + +program.command('create ').description('Create a new Directus Project').action(create); + +program.command('start').description('Start the Directus API').action(start); + +program.parseAsync(process.argv); diff --git a/src/cli/start.ts b/src/cli/start.ts new file mode 100644 index 0000000000..6d77db86c7 --- /dev/null +++ b/src/cli/start.ts @@ -0,0 +1,14 @@ +import app from '../app'; +import logger from '../logger'; +import getPort from 'get-port'; +import clear from 'clear'; + +export default async function start() { + clear(); + + const port = process.env.PORT || (await getPort({ port: 3000 })); + + app.listen(port, () => { + logger.info(`Server started at port ${port}`); + }); +} diff --git a/src/mail/index.ts b/src/mail/index.ts index bdf194b9f5..fb3385a585 100644 --- a/src/mail/index.ts +++ b/src/mail/index.ts @@ -39,18 +39,6 @@ if (emailTransport === 'sendmail') { pass: process.env.EMAIL_SMTP_PASSWORD, }, } as any); - - logger.trace('[Email] Verifying SMTP connection.'); - - transporter - .verify() - .then(() => { - logger.info('[Email] SMTP connected. Ready to send emails.'); - }) - .catch((err) => { - logger.error(`[Email] Couldn't connect to SMTP server:`); - logger.error(err); - }); } export type EmailOptions = { diff --git a/src/utils/has-fields.ts b/src/utils/has-fields.ts index b1e47435f3..cbcaf2c387 100644 --- a/src/utils/has-fields.ts +++ b/src/utils/has-fields.ts @@ -1,5 +1,4 @@ import database, { schemaInspector } from '../database'; -import { FIELD_SPECIAL_ALIAS_TYPES } from '../constants'; import { uniq } from 'lodash'; export default async function hasFields(fields: { collection: string; field: string }[]) { @@ -28,8 +27,7 @@ export async function collectionHasFields(collection: string, fieldKeys: string[ .select('field') .from('directus_fields') .where({ collection }) - .whereIn('field', fieldKeys) - .whereIn('special', FIELD_SPECIAL_ALIAS_TYPES), + .whereIn('field', fieldKeys), ]); const existingFields = uniq([ diff --git a/tsconfig.json b/tsconfig.json index 771dcbe038..29c1c7ba4e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,11 @@ "esModuleInterop": true, "target": "es6", "moduleResolution": "node", - "sourceMap": true, - "outDir": "dist" + "sourceMap": false, + "outDir": "dist", + "rootDir": "src" }, "lib": [ "es2015" - ] + ], } From b0a198ed54754a7264c3f4383affeffbbbf24e61 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Fri, 10 Jul 2020 18:11:27 -0400 Subject: [PATCH 02/18] Update knex --- src/knex-schema-inspector | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knex-schema-inspector b/src/knex-schema-inspector index a86af966cd..db80aeec75 160000 --- a/src/knex-schema-inspector +++ b/src/knex-schema-inspector @@ -1 +1 @@ -Subproject commit a86af966cd2c6882f70003a07722182a0ad3ac87 +Subproject commit db80aeec753db76b7d854f2ccc1ccc3b967b4601 From 72db3f19414c7d295d9336eb6a599575dd1028b4 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 12:33:28 -0400 Subject: [PATCH 03/18] Fix single optional-ness in express request extension --- package-lock.json | 480 ++++++++++++++++++++++++++++++++--------- src/types/express.d.ts | 2 +- 2 files changed, 384 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ca6338854..7fc627b317 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,15 +67,6 @@ "js-tokens": "^4.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -197,9 +188,9 @@ "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" }, "p-limit": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.1.tgz", - "integrity": "sha512-mw/p92EyOzl2MhauKodw54Rx5ZK4624rNfgNaBguFZkHzyUG9WsDzFF5/yQVEJinbJDdP4jEfMN+uBquiGnaLg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", "requires": { "p-try": "^2.0.0" } @@ -670,14 +661,6 @@ "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - } } }, "ajv": { @@ -718,27 +701,12 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "dependencies": { - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } + "color-convert": "^1.9.0" } }, "anymatch": { @@ -1260,6 +1228,30 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } } }, "chardet": { @@ -1344,12 +1336,37 @@ "string-width": "^4.2.0" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -1600,17 +1617,6 @@ "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "crypto-random-string": { @@ -1795,6 +1801,13 @@ "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "requires": { "is-obj": "^2.0.0" + }, + "dependencies": { + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + } } }, "dotenv": { @@ -1954,6 +1967,47 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -1961,10 +2015,19 @@ "dev": true }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -3409,13 +3472,10 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, "inflight": { "version": "1.0.6", @@ -3584,9 +3644,10 @@ "dev": true }, "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true }, "is-plain-object": { "version": "2.0.4", @@ -3904,6 +3965,64 @@ "please-upgrade-node": "^3.2.0", "string-argv": "0.3.1", "stringify-object": "^3.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "liquidjs": { @@ -3927,11 +4046,55 @@ "through": "^2.3.8" }, "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -4015,6 +4178,58 @@ "dev": true, "requires": { "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "log-update": { @@ -4029,12 +4244,37 @@ "wrap-ansi": "^6.2.0" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -4442,6 +4682,15 @@ "fstream": "^1.0.12", "inherits": "2" } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -4456,6 +4705,17 @@ "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "node-pre-gyp": { @@ -5028,6 +5288,16 @@ "split2": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -5037,6 +5307,36 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -5384,6 +5684,17 @@ "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" + }, + "dependencies": { + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + } } }, "regex-not": { @@ -5895,15 +6206,6 @@ "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -6263,14 +6565,6 @@ "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" - }, - "dependencies": { - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - } } }, "strip-ansi": { @@ -6706,15 +7000,6 @@ "tsutils": "^2.29.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -6996,9 +7281,10 @@ } }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } diff --git a/src/types/express.d.ts b/src/types/express.d.ts index cd00f44ffb..1adbe5498a 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -12,7 +12,7 @@ declare global { role?: string; collection?: string; sanitizedQuery?: Record; - single: boolean; + single?: boolean; } } } From 300951e0786c37ec40fbbaf520f68dcf891e0cf8 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 14:55:07 -0400 Subject: [PATCH 04/18] Support logical operations in filters --- package.json | 2 +- src/database/index.ts | 5 -- src/database/run-ast.ts | 103 ++++++++++++++++++------------- src/middleware/example.json | 16 +++++ src/middleware/sanitize-query.ts | 23 ++++--- src/services/items.ts | 12 ++-- src/types/query.ts | 6 +- 7 files changed, 100 insertions(+), 67 deletions(-) create mode 100644 src/middleware/example.json diff --git a/package.json b/package.json index fe35b6c97a..d68ade057c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "start": "NODE_ENV=production node dist/server.js", "build": "rimraf dist && tsc -b && copyfiles \"src/**/*.*\" -e \"src/**/*.ts\" -u 1 dist", - "dev": "LOG_LEVEL=trace ts-node-dev src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada", + "dev": "LOG_LEVEL=trace ts-node-dev --files src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada", "cli": "ts-node --script-mode --transpile-only src/cli/index.ts" }, "repository": { diff --git a/src/database/index.ts b/src/database/index.ts index cc41e511ef..e8994942e3 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -1,13 +1,10 @@ import knex from 'knex'; -import logger from '../logger'; import dotenv from 'dotenv'; import SchemaInspector from '../knex-schema-inspector/lib/index'; dotenv.config(); -const log = logger.child({ module: 'sql' }); - const database = knex({ client: process.env.DB_CLIENT, connection: { @@ -27,8 +24,6 @@ const database = knex({ }, }); -database.on('query', (data) => log.trace(data.sql)); - export const schemaInspector = SchemaInspector(database); export default database; diff --git a/src/database/run-ast.ts b/src/database/run-ast.ts index e45359f901..cc6cdf8d13 100644 --- a/src/database/run-ast.ts +++ b/src/database/run-ast.ts @@ -1,7 +1,8 @@ import { AST, NestedCollectionAST } from '../types/ast'; import { uniq } from 'lodash'; import database, { schemaInspector } from './index'; -import { Query } from '../types/query'; +import { Filter, Query } from '../types'; +import { QueryBuilder } from 'knex'; export default async function runAST(ast: AST, query = ast.query) { const toplevelFields: string[] = []; @@ -23,33 +24,10 @@ export default async function runAST(ast: AST, query = ast.query) { nestedCollections.push(child); } - const dbQuery = database.select(toplevelFields).from(ast.name); + let dbQuery = database.select(toplevelFields).from(ast.name); if (query.filter) { - query.filter.forEach((filter) => { - if (filter.operator === 'in') { - let value = filter.value; - if (typeof value === 'string') value = value.split(','); - - dbQuery.whereIn(filter.column, value as string[]); - } - - if (filter.operator === 'eq') { - dbQuery.where({ [filter.column]: filter.value }); - } - - if (filter.operator === 'neq') { - dbQuery.whereNot({ [filter.column]: filter.value }); - } - - if (filter.operator === 'null') { - dbQuery.whereNull(filter.column); - } - - if (filter.operator === 'nnull') { - dbQuery.whereNotNull(filter.column); - } - }); + applyFilter(dbQuery, query.filter); } if (query.sort) { @@ -100,30 +78,24 @@ export default async function runAST(ast: AST, query = ast.query) { if (m2o) { batchQuery = { ...batch.query, - filter: [ - ...(batch.query.filter || []), - { - column: 'id', - operator: 'in', - // filter removes null / undefined - value: uniq(results.map((res) => res[batch.relation.field_many])).filter( + filter: { + ...(batch.query.filter || {}), + [batch.relation.primary_one]: { + _in: uniq(results.map((res) => res[batch.relation.field_many])).filter( (id) => id ), }, - ], + }, }; } else { batchQuery = { ...batch.query, - filter: [ - ...(batch.query.filter || []), - { - column: batch.relation.field_many, - operator: 'in', - // filter removes null / undefined - value: uniq(results.map((res) => res[batch.parentKey])).filter((id) => id), + filter: { + ...(batch.query.filter || {}), + [batch.relation.field_many]: { + _in: uniq(results.map((res) => res[batch.parentKey])).filter((id) => id), }, - ], + }, }; } @@ -166,3 +138,50 @@ function isM2O(child: NestedCollectionAST) { child.relation.collection_one === child.name && child.relation.field_many === child.fieldKey ); } + +function applyFilter(dbQuery: QueryBuilder, filter: Filter) { + for (const [key, value] of Object.entries(filter)) { + if (key.startsWith('_') === false) { + let operator = Object.keys(value)[0]; + operator = operator.slice(1); + operator = operator.toLowerCase(); + + const compareValue = Object.values(value)[0]; + + if (operator === 'eq') { + dbQuery.where({ [key]: compareValue }); + } + + if (operator === 'neq') { + dbQuery.whereNot({ [key]: compareValue }); + } + + if (operator === 'in') { + let value = compareValue; + if (typeof value === 'string') value = value.split(','); + + dbQuery.whereIn(key, value as string[]); + } + + if (operator === 'null') { + dbQuery.whereNull(key); + } + + if (operator === 'nnull') { + dbQuery.whereNotNull(key); + } + } + + if (key === '_or') { + value.forEach((subFilter: Record) => { + dbQuery.orWhere((subQuery) => applyFilter(subQuery, subFilter)); + }); + } + + if (key === '_and') { + value.forEach((subFilter: Record) => { + dbQuery.andWhere((subQuery) => applyFilter(subQuery, subFilter)); + }); + } + } +} diff --git a/src/middleware/example.json b/src/middleware/example.json new file mode 100644 index 0000000000..b709d68972 --- /dev/null +++ b/src/middleware/example.json @@ -0,0 +1,16 @@ +{ + "_or": [ + { + "_and": [ + { "email": { "_eq": "rijk@rngr.org" }}, + { "first_name": { "_eq": "Rijk" }} + ] + }, + { + "_and": [ + { "email": { "_eq": "ben@rngr.org" } }, + { "first_name": { "_eq": "Ben" } } + ] + } + ] +} diff --git a/src/middleware/sanitize-query.ts b/src/middleware/sanitize-query.ts index eae5a7dc4a..e49c57f20b 100644 --- a/src/middleware/sanitize-query.ts +++ b/src/middleware/sanitize-query.ts @@ -4,8 +4,9 @@ */ import { RequestHandler } from 'express'; -import { Query, Sort, Filter, FilterOperator } from '../types/query'; +import { Query, Sort, Filter } from '../types/query'; import { Meta } from '../types/meta'; +import logger from '../logger'; const sanitizeQuery: RequestHandler = (req, res, next) => { if (!req.query) return; @@ -84,14 +85,20 @@ function sanitizeSort(rawSort: any) { } function sanitizeFilter(rawFilter: any) { - const filters: Filter[] = []; + let filters: Filter = rawFilter; - Object.keys(rawFilter).forEach((column) => { - Object.keys(rawFilter[column]).forEach((operator: FilterOperator) => { - const value = rawFilter[column][operator]; - filters.push({ column, operator, value }); - }); - }); + if (typeof rawFilter === 'string') { + try { + filters = JSON.parse(rawFilter); + } catch { + logger.warn('Invalid value passed for filter query parameter.'); + } + } + + /** + * @todo + * validate filter syntax? + */ return filters; } diff --git a/src/services/items.ts b/src/services/items.ts index 74f6ea7f70..7ab26ba46c 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -98,14 +98,12 @@ export const readItem = async ( query = { ...query, - filter: [ - ...(query.filter || []), - { - column: primaryKeyField, - operator: 'eq', - value: pk, + filter: { + ...(query.filter || {}), + [primaryKeyField]: { + _eq: pk, }, - ], + }, }; const ast = await getAST(collection, query); diff --git a/src/types/query.ts b/src/types/query.ts index 992acdd140..079046e557 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -3,7 +3,7 @@ import { Meta } from './meta'; export type Query = { fields?: string[]; sort?: Sort[]; - filter?: Filter[]; + filter?: Filter; limit?: number; offset?: number; page?: number; @@ -18,9 +18,7 @@ export type Sort = { }; export type Filter = { - column: string; - operator: FilterOperator; - value: null | string | number | (string | number)[]; + [keyOrOperator: string]: Filter | any; }; export type FilterOperator = 'eq' | 'neq' | 'in' | 'nin' | 'null' | 'nnull'; From 8ca92cb48d9dd9c4e1b9fc7cfc6b65c000bf5bb3 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 16:39:20 -0400 Subject: [PATCH 05/18] Add forbidden exception --- src/exceptions/forbidden.ts | 7 +++++++ src/exceptions/index.ts | 1 + 2 files changed, 8 insertions(+) create mode 100644 src/exceptions/forbidden.ts diff --git a/src/exceptions/forbidden.ts b/src/exceptions/forbidden.ts new file mode 100644 index 0000000000..4b0e6b88fa --- /dev/null +++ b/src/exceptions/forbidden.ts @@ -0,0 +1,7 @@ +import { BaseException } from './base'; + +export class ForbiddenException extends BaseException { + constructor(message = `You don't have permission to access this.`) { + super(message, 403, 'NO_PERMISSION_TO_ACCESS'); + } +} diff --git a/src/exceptions/index.ts b/src/exceptions/index.ts index 25619832ff..db7444a24d 100644 --- a/src/exceptions/index.ts +++ b/src/exceptions/index.ts @@ -1,6 +1,7 @@ export * from './base'; export * from './collection-not-found'; export * from './field-not-found'; +export * from './forbidden'; export * from './invalid-credentials'; export * from './invalid-payload'; export * from './invalid-query'; From ff961de4ce3d7f3213ad2d7fe8292f1676af96b3 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 16:39:23 -0400 Subject: [PATCH 06/18] Add powered by header --- src/app.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app.ts b/src/app.ts index e341e84d17..649f7c40bf 100644 --- a/src/app.ts +++ b/src/app.ts @@ -37,6 +37,10 @@ const app = express() .use(logger()) .use(bodyParser.json()) .use(extractToken) + .use((req, res, next) => { + res.setHeader('X-Powered-By', 'Directus'); + next(); + }) // the auth endpoints allow you to login/logout etc. It should ignore the authentication check .use('/auth', authRouter) From 7b0d14e79baf9bb0d1926251273e1d83236550dc Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 16:39:42 -0400 Subject: [PATCH 07/18] Add permissions types --- src/types/permissions.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/types/permissions.ts diff --git a/src/types/permissions.ts b/src/types/permissions.ts new file mode 100644 index 0000000000..c896245e69 --- /dev/null +++ b/src/types/permissions.ts @@ -0,0 +1,19 @@ +export type Operation = + | 'create' + | 'read' + | 'update' + | 'validate' + | 'delete' + | 'comment' + | 'explain'; + +export type Permission = { + id: number; + role: string | null; + collection: string; + operation: Operation; + permissions: Record; + limit: number | null; + presets: Record | null; + fields: string | null; +}; From 6a3f9210f0d8f6991c2db14a38227de5a422a1e8 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 16:39:59 -0400 Subject: [PATCH 08/18] Add permissions to express request --- src/types/express.d.ts | 3 +++ src/types/index.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/src/types/express.d.ts b/src/types/express.d.ts index 1adbe5498a..e5407f4bab 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -2,6 +2,8 @@ * Custom properties on the req object in express */ +import { Permission } from './permissions'; + export {}; declare global { @@ -13,6 +15,7 @@ declare global { collection?: string; sanitizedQuery?: Record; single?: boolean; + permissions?: Permission; } } } diff --git a/src/types/index.ts b/src/types/index.ts index 9435b54884..796d30f5b1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,6 +5,7 @@ export * from './collection'; export * from './field'; export * from './files'; export * from './meta'; +export * from './permissions'; export * from './query'; export * from './relation'; export * from './sessions'; From 8887d3216ecace07f365b5e5fa2f2845b3bb0f00 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Mon, 13 Jul 2020 16:40:29 -0400 Subject: [PATCH 09/18] Add find-permissions method --- src/services/permissions.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/services/permissions.ts b/src/services/permissions.ts index 877c74275b..f7f3f85f6a 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -1,4 +1,4 @@ -import { Accountability, Query } from '../types'; +import { Accountability, Permission, Operation, Query } from '../types'; import * as ItemsService from './items'; export const createPermission = async ( @@ -32,3 +32,27 @@ export const updatePermission = async ( export const deletePermission = async (pk: number, accountability: Accountability) => { await ItemsService.deleteItem('directus_permissions', pk, accountability); }; + +export const authorize = async (operation: Operation, collection: string, role?: string) => { + const query: Query = { + filter: { + collection: { + _eq: collection, + }, + operation: { + _eq: operation, + }, + }, + limit: 1, + }; + + if (role) { + query.filter.role = { + _eq: role, + }; + } + + const records = await ItemsService.readItems('directus_permissions', query); + + return records[0]; +}; From 0deaa77f0be97a30af9e9867da7f4c07ede00ea0 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 14 Jul 2020 13:11:05 -0400 Subject: [PATCH 10/18] [WIP] Add processAST to PermissionsService, rely on AST instead of Query in services --- src/exceptions/forbidden.ts | 2 +- src/middleware/authenticate.ts | 13 +- ...ate-collection.ts => collection-exists.ts} | 9 +- src/middleware/example.json | 16 -- src/middleware/sanitize-query.ts | 26 +-- src/middleware/validate-query.ts | 67 -------- src/routes/activity.ts | 20 +-- src/routes/collections.ts | 14 +- src/routes/fields.ts | 5 +- src/routes/files.ts | 16 +- src/routes/folders.ts | 15 +- src/routes/items.ts | 34 ++-- src/routes/permissions.ts | 5 +- src/routes/presets.ts | 8 +- src/routes/relations.ts | 9 +- src/routes/revisions.ts | 7 +- src/routes/roles.ts | 9 +- src/routes/settings.ts | 9 +- src/routes/users.ts | 11 +- src/routes/webhooks.ts | 7 +- src/services/activity.ts | 2 +- src/services/collections.ts | 30 ++-- src/services/files.ts | 2 +- src/services/folders.ts | 2 +- src/services/items.ts | 20 +-- src/services/permissions.ts | 149 +++++++++++++++++- src/services/presets.ts | 2 +- src/services/relations.ts | 2 +- src/services/revisions.ts | 2 +- src/services/roles.ts | 2 +- src/services/settings.ts | 2 +- src/services/users.ts | 2 +- src/services/webhooks.ts | 2 +- src/types/express.d.ts | 7 +- .../{get-ast.ts => get-ast-from-query.ts} | 57 ++++--- 35 files changed, 316 insertions(+), 269 deletions(-) rename src/middleware/{validate-collection.ts => collection-exists.ts} (70%) delete mode 100644 src/middleware/example.json delete mode 100644 src/middleware/validate-query.ts rename src/utils/{get-ast.ts => get-ast-from-query.ts} (64%) diff --git a/src/exceptions/forbidden.ts b/src/exceptions/forbidden.ts index 4b0e6b88fa..195b0ab6c3 100644 --- a/src/exceptions/forbidden.ts +++ b/src/exceptions/forbidden.ts @@ -2,6 +2,6 @@ import { BaseException } from './base'; export class ForbiddenException extends BaseException { constructor(message = `You don't have permission to access this.`) { - super(message, 403, 'NO_PERMISSION_TO_ACCESS'); + super(message, 403, 'NO_PERMISSION'); } } diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index 4ef611c685..98feb551c4 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -9,7 +9,10 @@ import { InvalidCredentialsException } from '../exceptions'; * Verify the passed JWT and assign the user ID and role to `req` */ const authenticate: RequestHandler = asyncHandler(async (req, res, next) => { - /** @todo base this on a validation middleware on permissions */ + req.user = null; + req.role = null; + req.admin = false; + if (!req.token) return next(); if (isJWT(req.token)) { @@ -26,14 +29,17 @@ const authenticate: RequestHandler = asyncHandler(async (req, res, next) => { } const user = await database - .select('role') + .select('role', 'directus_roles.admin') .from('directus_users') - .where({ id: payload.id }) + .leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id') + .where({ 'directus_users.id': payload.id }) .first(); /** @TODO verify user status */ + req.user = payload.id; req.role = user.role; + req.admin = user.admin; return next(); } @@ -41,6 +47,7 @@ const authenticate: RequestHandler = asyncHandler(async (req, res, next) => { * @TODO * Implement static tokens * + * @NOTE * We'll silently ignore wrong tokens. This makes sure we prevent brute-forcing static tokens */ return next(); diff --git a/src/middleware/validate-collection.ts b/src/middleware/collection-exists.ts similarity index 70% rename from src/middleware/validate-collection.ts rename to src/middleware/collection-exists.ts index bff8b6794e..6320102f2a 100644 --- a/src/middleware/validate-collection.ts +++ b/src/middleware/collection-exists.ts @@ -5,15 +5,15 @@ import { RequestHandler } from 'express'; import asyncHandler from 'express-async-handler'; import database from '../database'; -import { CollectionNotFoundException } from '../exceptions'; +import { ForbiddenException } from '../exceptions'; -const validateCollection: RequestHandler = asyncHandler(async (req, res, next) => { +const collectionExists: RequestHandler = asyncHandler(async (req, res, next) => { if (!req.params.collection) return next(); const exists = await database.schema.hasTable(req.params.collection); if (exists === false) { - throw new CollectionNotFoundException(req.params.collection); + throw new ForbiddenException(); } req.collection = req.params.collection; @@ -23,9 +23,10 @@ const validateCollection: RequestHandler = asyncHandler(async (req, res, next) = .from('directus_collections') .where({ collection: req.collection }) .first(); + req.single = single; return next(); }); -export default validateCollection; +export default collectionExists; diff --git a/src/middleware/example.json b/src/middleware/example.json deleted file mode 100644 index b709d68972..0000000000 --- a/src/middleware/example.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "_or": [ - { - "_and": [ - { "email": { "_eq": "rijk@rngr.org" }}, - { "first_name": { "_eq": "Rijk" }} - ] - }, - { - "_and": [ - { "email": { "_eq": "ben@rngr.org" } }, - { "first_name": { "_eq": "Ben" } } - ] - } - ] -} diff --git a/src/middleware/sanitize-query.ts b/src/middleware/sanitize-query.ts index e49c57f20b..fd1408dd46 100644 --- a/src/middleware/sanitize-query.ts +++ b/src/middleware/sanitize-query.ts @@ -11,11 +11,10 @@ import logger from '../logger'; const sanitizeQuery: RequestHandler = (req, res, next) => { if (!req.query) return; - const query: Query = {}; - - if (req.query.fields) { - query.fields = sanitizeFields(req.query.fields); - } + const query: Query = { + fields: sanitizeFields(req.query.fields) || ['*'], + limit: sanitizeLimit(req.query.limit) || 100, + }; if (req.query.sort) { query.sort = sanitizeSort(req.query.sort); @@ -25,13 +24,6 @@ const sanitizeQuery: RequestHandler = (req, res, next) => { query.filter = sanitizeFilter(req.query.filter); } - if (req.query.limit) { - query.limit = sanitizeLimit(req.query.limit); - } else { - /** @todo is this the right place to set these defaults? */ - query.limit = 100; - } - if (req.query.limit == '-1') { delete query.limit; } @@ -56,6 +48,13 @@ const sanitizeQuery: RequestHandler = (req, res, next) => { query.search = req.query.search; } + if (req.permissions) { + query.filter = { + ...(query.filter || {}), + ...(req.permissions.permissions || {}), + }; + } + req.sanitizedQuery = query; return next(); }; @@ -63,6 +62,8 @@ const sanitizeQuery: RequestHandler = (req, res, next) => { export default sanitizeQuery; function sanitizeFields(rawFields: any) { + if (!rawFields) return; + let fields: string[] = []; if (typeof rawFields === 'string') fields = rawFields.split(','); @@ -104,6 +105,7 @@ function sanitizeFilter(rawFilter: any) { } function sanitizeLimit(rawLimit: any) { + if (!rawLimit) return null; return Number(rawLimit); } diff --git a/src/middleware/validate-query.ts b/src/middleware/validate-query.ts deleted file mode 100644 index d0d6d3c5bf..0000000000 --- a/src/middleware/validate-query.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Validates query parameters. - * We'll check if all fields you're trying to access exist - * - * This has to be run after sanitizeQuery - */ - -import { RequestHandler } from 'express'; -import { Query } from '../types/query'; -import asyncHandler from 'express-async-handler'; -import { InvalidQueryException } from '../exceptions'; -import hasFields from '../utils/has-fields'; - -const validateQuery: RequestHandler = asyncHandler(async (req, res, next) => { - if (!req.collection) return next(); - if (!req.sanitizedQuery) return next(); - - const query: Query = req.sanitizedQuery; - - await Promise.all([ - validateParams(req.collection, query), - validateFields(req.collection, query), - validateMeta(query), - ]); - - return next(); -}); - -async function validateParams(collection: string, query: Query) { - if (query.offset && query.page) { - throw new InvalidQueryException( - `You can't have both the offset and page query parameters enabled.` - ); - } -} - -async function validateFields(collection: string, query: Query) { - /** - * @todo combine this with permissions (?) - */ - /** - * @todo use /utils/has-fields - */ - // const fieldsToCheck = new Set(); - // if (query.fields) { - // /** @todo support relationships in here */ - // query.fields.forEach((field) => fieldsToCheck.add(field)); - // } - // if (query.sort) { - // query.sort.forEach((sort) => fieldsToCheck.add(sort.column)); - // } - // /** @todo swap with more efficient schemaInspector version */ - // const fieldsExist = await hasFields(collection, Array.from(fieldsToCheck)); - // Array.from(fieldsToCheck).forEach((field, index) => { - // const exists = fieldsExist[index]; - // if (exists === false) - // throw new InvalidQueryException(`Field ${field} doesn't exist in ${collection}.`); - // }); -} - -async function validateMeta(query: Query) { - if (!query.meta) return; - - return query.meta.every((metaField) => []); -} - -export default validateQuery; diff --git a/src/routes/activity.ts b/src/routes/activity.ts index 43b846ffcd..751520d6a7 100644 --- a/src/routes/activity.ts +++ b/src/routes/activity.ts @@ -1,30 +1,27 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as ActivityService from '../services/activity'; import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_activity')); + router.get( '/', - useCollection('directus_activity'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await ActivityService.readActivities(req.sanitizedQuery); - return res.json({ - data: records || null, - }); + // const records = await ActivityService.readActivities(req.sanitizedQuery); + // return res.json({ + // data: records || null, + // }); }) ); router.get( '/:pk', - useCollection('directus_activity'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await ActivityService.readActivity(req.params.pk, req.sanitizedQuery); @@ -36,9 +33,7 @@ router.get( router.post( '/comment', - useCollection('directus_activity'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await ActivityService.createActivity({ ...req.body, @@ -58,9 +53,7 @@ router.post( router.patch( '/comment/:pk', - useCollection('directus_activity'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await ActivityService.updateActivity(req.params.pk, req.body, { user: req.user, @@ -78,7 +71,6 @@ router.patch( router.delete( '/comment/:pk', - useCollection('directus_activity'), asyncHandler(async (req, res) => { await ActivityService.deleteActivity(req.params.pk, { user: req.user, diff --git a/src/routes/collections.ts b/src/routes/collections.ts index bc64717d1f..38d823e1fb 100644 --- a/src/routes/collections.ts +++ b/src/routes/collections.ts @@ -1,11 +1,11 @@ import { Router } from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as CollectionsService from '../services/collections'; -import database, { schemaInspector } from '../database'; +import { schemaInspector } from '../database'; import { InvalidPayloadException, CollectionNotFoundException } from '../exceptions'; import Joi from '@hapi/joi'; +import useCollection from '../middleware/use-collection'; const router = Router(); @@ -25,6 +25,7 @@ const collectionSchema = Joi.object({ router.post( '/', + useCollection('directus_collections'), asyncHandler(async (req, res) => { const { error } = collectionSchema.validate(req.body); if (error) throw new InvalidPayloadException(error.message); @@ -41,18 +42,18 @@ router.post( router.get( '/', + useCollection('directus_collections'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const collections = await CollectionsService.readAll(req.sanitizedQuery); - res.json({ data: collections || null }); + // const collections = await CollectionsService.readAll(req.sanitizedQuery); + // res.json({ data: collections || null }); }) ); router.get( '/:collection', + useCollection('directus_collections'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const exists = await schemaInspector.hasTable(req.params.collection); @@ -68,6 +69,7 @@ router.get( router.delete( '/:collection', + useCollection('directus_collections'), asyncHandler(async (req, res) => { if ((await schemaInspector.hasTable(req.params.collection)) === false) { throw new CollectionNotFoundException(req.params.collection); diff --git a/src/routes/fields.ts b/src/routes/fields.ts index a8e5530e88..bdcd2be6e3 100644 --- a/src/routes/fields.ts +++ b/src/routes/fields.ts @@ -1,14 +1,17 @@ import { Router } from 'express'; import asyncHandler from 'express-async-handler'; import * as FieldsService from '../services/fields'; -import validateCollection from '../middleware/validate-collection'; +import validateCollection from '../middleware/collection-exists'; import { schemaInspector } from '../database'; import { FieldNotFoundException, InvalidPayloadException } from '../exceptions'; import Joi from '@hapi/joi'; import { Field } from '../types/field'; +import useCollection from '../middleware/use-collection'; const router = Router(); +router.use(useCollection('directus_fields')); + router.get( '/', asyncHandler(async (req, res) => { diff --git a/src/routes/files.ts b/src/routes/files.ts index cfefde28ff..15436da535 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -2,7 +2,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import Busboy from 'busboy'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as FilesService from '../services/files'; import logger from '../logger'; import { InvalidPayloadException } from '../exceptions'; @@ -10,6 +9,8 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_files')); + const multipartHandler = (operation: 'create' | 'update') => asyncHandler(async (req, res, next) => { const busboy = new Busboy({ headers: req.headers }); @@ -93,24 +94,20 @@ const multipartHandler = (operation: 'create' | 'update') => return req.pipe(busboy); }); -router.post('/', useCollection('directus_files'), multipartHandler('create')); +router.post('/', sanitizeQuery, multipartHandler('create')); router.get( '/', - useCollection('directus_files'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await FilesService.readFiles(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await FilesService.readFiles(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_files'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await FilesService.readFile(req.params.pk, req.sanitizedQuery); return res.json({ data: record || null }); @@ -119,7 +116,7 @@ router.get( router.patch( '/:pk', - useCollection('directus_files'), + sanitizeQuery, asyncHandler(async (req, res, next) => { let file: Record; @@ -140,7 +137,6 @@ router.patch( router.delete( '/:pk', - useCollection('directus_files'), asyncHandler(async (req, res) => { await FilesService.deleteFile(req.params.pk, { ip: req.ip, diff --git a/src/routes/folders.ts b/src/routes/folders.ts index b83f4072b9..5d60b7dd5f 100644 --- a/src/routes/folders.ts +++ b/src/routes/folders.ts @@ -1,15 +1,15 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import useCollection from '../middleware/use-collection'; import * as FoldersService from '../services/folders'; const router = express.Router(); +router.use(useCollection('directus_folders')); + router.post( '/', - useCollection('directus_folders'), asyncHandler(async (req, res) => { const primaryKey = await FoldersService.createFolder(req.body, { ip: req.ip, @@ -24,20 +24,16 @@ router.post( router.get( '/', - useCollection('directus_folders'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await FoldersService.readFolders(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await FoldersService.readFolders(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_folders'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await FoldersService.readFolder(req.params.pk, req.sanitizedQuery); return res.json({ data: record || null }); @@ -46,7 +42,7 @@ router.get( router.patch( '/:pk', - useCollection('directus_folders'), + sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await FoldersService.updateFolder(req.params.pk, req.body, { ip: req.ip, @@ -62,7 +58,6 @@ router.patch( router.delete( '/:pk', - useCollection('directus_folders'), asyncHandler(async (req, res) => { await FoldersService.deleteFolder(req.params.pk, { ip: req.ip, diff --git a/src/routes/items.ts b/src/routes/items.ts index ca8c1ebff7..3196845f3f 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -2,18 +2,18 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import * as ItemsService from '../services/items'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateCollection from '../middleware/validate-collection'; -import validateQuery from '../middleware/validate-query'; +import collectionExists from '../middleware/collection-exists'; import * as MetaService from '../services/meta'; +import * as PermissionsService from '../services/permissions'; import { RouteNotFoundException } from '../exceptions'; +import getASTFromQuery from '../utils/get-ast-from-query'; const router = express.Router(); router.post( '/:collection', - validateCollection, + collectionExists, sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { if (req.single) { throw new RouteNotFoundException(req.path); @@ -33,14 +33,17 @@ router.post( router.get( '/:collection', - validateCollection, + collectionExists, sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { + let ast = await getASTFromQuery(req.role, req.collection, req.sanitizedQuery); + + ast = await PermissionsService.processAST(req.role, ast); + const [records, meta] = await Promise.all([ req.single - ? ItemsService.readSingleton(req.collection, req.sanitizedQuery) - : ItemsService.readItems(req.collection, req.sanitizedQuery), + ? ItemsService.readSingleton(req.collection, ast) + : ItemsService.readItems(req.collection, ast), MetaService.getMetaForQuery(req.collection, req.sanitizedQuery), ]); @@ -53,9 +56,8 @@ router.get( router.get( '/:collection/:pk', - validateCollection, + collectionExists, sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { if (req.single) { throw new RouteNotFoundException(req.path); @@ -75,9 +77,8 @@ router.get( router.patch( '/:collection', - validateCollection, + collectionExists, sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { if (req.single === false) { throw new RouteNotFoundException(req.path); @@ -89,17 +90,16 @@ router.patch( user: req.user, }); - const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery); + // const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery); - return res.json({ data: item || null }); + // return res.json({ data: item || null }); }) ); router.patch( '/:collection/:pk', - validateCollection, + collectionExists, sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { if (req.single) { throw new RouteNotFoundException(req.path); @@ -119,7 +119,7 @@ router.patch( router.delete( '/:collection/:pk', - validateCollection, + collectionExists, asyncHandler(async (req, res) => { await ItemsService.deleteItem(req.collection, req.params.pk, { ip: req.ip, diff --git a/src/routes/permissions.ts b/src/routes/permissions.ts index 51f192ddfb..1e5a2ec34d 100644 --- a/src/routes/permissions.ts +++ b/src/routes/permissions.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as PermissionsService from '../services/permissions'; import useCollection from '../middleware/use-collection'; @@ -27,10 +26,9 @@ router.get( '/', useCollection('directus_permissions'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const item = await PermissionsService.readPermissions(req.sanitizedQuery); - return res.json({ data: item || null }); + // return res.json({ data: item || null }); }) ); @@ -38,7 +36,6 @@ router.get( '/:pk', useCollection('directus_permissions'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await PermissionsService.readPermission( Number(req.params.pk), diff --git a/src/routes/presets.ts b/src/routes/presets.ts index 3b5fe2e82e..0a319aa99d 100644 --- a/src/routes/presets.ts +++ b/src/routes/presets.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as CollectionPresetsService from '../services/presets'; import useCollection from '../middleware/use-collection'; @@ -29,10 +28,9 @@ router.get( '/', useCollection('directus_presets'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await CollectionPresetsService.readCollectionPresets(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await CollectionPresetsService.readCollectionPresets(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); @@ -40,7 +38,6 @@ router.get( '/:pk', useCollection('directus_presets'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await CollectionPresetsService.readCollectionPreset( req.params.pk, @@ -54,7 +51,6 @@ router.patch( '/:pk', useCollection('directus_presets'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await CollectionPresetsService.updateCollectionPreset( req.params.pk, diff --git a/src/routes/relations.ts b/src/routes/relations.ts index 4f4c62be14..fe91168eef 100644 --- a/src/routes/relations.ts +++ b/src/routes/relations.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as RelationsService from '../services/relations'; import useCollection from '../middleware/use-collection'; @@ -11,7 +10,6 @@ router.post( '/', useCollection('directus_relations'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await RelationsService.createRelation(req.body, { ip: req.ip, @@ -27,10 +25,9 @@ router.get( '/', useCollection('directus_relations'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await RelationsService.readRelations(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await RelationsService.readRelations(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); @@ -38,7 +35,6 @@ router.get( '/:pk', useCollection('directus_relations'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await RelationsService.readRelation(req.params.pk, req.sanitizedQuery); return res.json({ data: record || null }); @@ -49,7 +45,6 @@ router.patch( '/:pk', useCollection('directus_relations'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await RelationsService.updateRelation(req.params.pk, req.body, { ip: req.ip, diff --git a/src/routes/revisions.ts b/src/routes/revisions.ts index e604e7f413..ca72f46883 100644 --- a/src/routes/revisions.ts +++ b/src/routes/revisions.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as RevisionsService from '../services/revisions'; import useCollection from '../middleware/use-collection'; @@ -11,10 +10,9 @@ router.get( '/', useCollection('directus_revisions'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await RevisionsService.readRevisions(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await RevisionsService.readRevisions(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); @@ -22,7 +20,6 @@ router.get( '/:pk', useCollection('directus_revisions'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await RevisionsService.readRevision(req.params.pk, req.sanitizedQuery); return res.json({ data: record || null }); diff --git a/src/routes/roles.ts b/src/routes/roles.ts index 4e8cf6d090..272b8d41d7 100644 --- a/src/routes/roles.ts +++ b/src/routes/roles.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as RolesService from '../services/roles'; import useCollection from '../middleware/use-collection'; @@ -11,7 +10,6 @@ router.post( '/', useCollection('directus_roles'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await RolesService.createRole(req.body, { ip: req.ip, @@ -27,10 +25,9 @@ router.get( '/', useCollection('directus_roles'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await RolesService.readRoles(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await RolesService.readRoles(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); @@ -38,7 +35,6 @@ router.get( '/:pk', useCollection('directus_roles'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await RolesService.readRole(req.params.pk, req.sanitizedQuery); return res.json({ data: record || null }); @@ -49,7 +45,6 @@ router.patch( '/:pk', useCollection('directus_roles'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await RolesService.updateRole(req.params.pk, req.body, { ip: req.ip, diff --git a/src/routes/settings.ts b/src/routes/settings.ts index bfab4d2afa..f5b84ff546 100644 --- a/src/routes/settings.ts +++ b/src/routes/settings.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as SettingsService from '../services/settings'; import useCollection from '../middleware/use-collection'; @@ -11,10 +10,9 @@ router.get( '/', useCollection('directus_settings'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const records = await SettingsService.readSettings(req.sanitizedQuery); - return res.json({ data: records || null }); + // return res.json({ data: records || null }); }) ); @@ -22,7 +20,6 @@ router.patch( '/', useCollection('directus_settings'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { await SettingsService.updateSettings(req.body, { ip: req.ip, @@ -30,9 +27,9 @@ router.patch( user: req.user, }); - const record = await SettingsService.readSettings(req.sanitizedQuery); + // const record = await SettingsService.readSettings(req.sanitizedQuery); - return res.json({ data: record || null }); + // return res.json({ data: record || null }); }) ); diff --git a/src/routes/users.ts b/src/routes/users.ts index 32130e8e5e..1839235072 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as UsersService from '../services/users'; import Joi from '@hapi/joi'; import { InvalidPayloadException, InvalidCredentialsException } from '../exceptions'; @@ -13,7 +12,6 @@ router.post( '/', useCollection('directus_users'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await UsersService.createUser(req.body, { ip: req.ip, @@ -29,10 +27,9 @@ router.get( '/', useCollection('directus_users'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const item = await UsersService.readUsers(req.sanitizedQuery); - return res.json({ data: item || null }); + // const item = await UsersService.readUsers(req.sanitizedQuery); + // return res.json({ data: item || null }); }) ); @@ -40,7 +37,6 @@ router.get( '/me', useCollection('directus_users'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { if (!req.user) { throw new InvalidCredentialsException(); @@ -55,7 +51,6 @@ router.get( '/:pk', useCollection('directus_users'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const items = await UsersService.readUser(req.params.pk, req.sanitizedQuery); return res.json({ data: items || null }); @@ -66,7 +61,6 @@ router.patch( '/me', useCollection('directus_users'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { if (!req.user) { throw new InvalidCredentialsException(); @@ -87,7 +81,6 @@ router.patch( '/:pk', useCollection('directus_users'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const primaryKey = await UsersService.updateUser(req.params.pk, req.body, { ip: req.ip, diff --git a/src/routes/webhooks.ts b/src/routes/webhooks.ts index 0d04dccac5..058ab9cd8e 100644 --- a/src/routes/webhooks.ts +++ b/src/routes/webhooks.ts @@ -1,7 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; -import validateQuery from '../middleware/validate-query'; import * as WebhooksService from '../services/webhooks'; import useCollection from '../middleware/use-collection'; @@ -27,10 +26,9 @@ router.get( '/', useCollection('directus_webhooks'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { - const records = await WebhooksService.readWebhooks(req.sanitizedQuery); - return res.json({ data: records || null }); + // const records = await WebhooksService.readWebhooks(req.sanitizedQuery); + // return res.json({ data: records || null }); }) ); @@ -38,7 +36,6 @@ router.get( '/:pk', useCollection('directus_webhooks'), sanitizeQuery, - validateQuery, asyncHandler(async (req, res) => { const record = await WebhooksService.readWebhook(req.params.pk, req.sanitizedQuery); return res.json({ data: record || null }); diff --git a/src/services/activity.ts b/src/services/activity.ts index d6f854afdc..f601468b26 100644 --- a/src/services/activity.ts +++ b/src/services/activity.ts @@ -17,7 +17,7 @@ export const createActivity = async (data: Record) => { }; export const readActivities = async (query?: Query) => { - return await ItemsService.readItems('directus_activity', query); + // return await ItemsService.readItems('directus_activity', query); }; export const readActivity = async (pk: string | number, query?: Query) => { diff --git a/src/services/collections.ts b/src/services/collections.ts index 3bba16f8bb..30ebf4410b 100644 --- a/src/services/collections.ts +++ b/src/services/collections.ts @@ -73,25 +73,25 @@ export const create = async (payload: any, accountability: Accountability) => { export const readAll = async (query?: Query) => { const [tables, collections] = await Promise.all([ schemaInspector.tableInfo(), - ItemsService.readItems('directus_collections', query), + // ItemsService.readItems('directus_collections', query), ]); - const data = tables.map((table) => { - const collectionInfo = collections.find((collection) => { - return collection.collection === table.name; - }); + // const data = tables.map((table) => { + // const collectionInfo = collections.find((collection) => { + // return collection.collection === table.name; + // }); - return { - collection: table.name, - note: table.comment, - hidden: collectionInfo?.hidden || false, - single: collectionInfo?.single || false, - icon: collectionInfo?.icon || null, - translation: collectionInfo?.translation || null, - }; - }); + // return { + // collection: table.name, + // note: table.comment, + // hidden: collectionInfo?.hidden || false, + // single: collectionInfo?.single || false, + // icon: collectionInfo?.icon || null, + // translation: collectionInfo?.translation || null, + // }; + // }); - return data; + // return data; }; export const readOne = async (collection: string, query?: Query) => { diff --git a/src/services/files.ts b/src/services/files.ts index bcf7035c6f..e65c1f209e 100644 --- a/src/services/files.ts +++ b/src/services/files.ts @@ -57,7 +57,7 @@ export const createFile = async ( }; export const readFiles = async (query: Query) => { - return await ItemsService.readItems('directus_files', query); + // return await ItemsService.readItems('directus_files', query); }; export const readFile = async (pk: string | number, query: Query) => { diff --git a/src/services/folders.ts b/src/services/folders.ts index fd471b7682..b3f9a9bd6a 100644 --- a/src/services/folders.ts +++ b/src/services/folders.ts @@ -9,7 +9,7 @@ export const createFolder = async ( }; export const readFolders = async (query: Query) => { - return await ItemsService.readItems('directus_folders', query); + // return await ItemsService.readItems('directus_folders', query); }; export const readFolder = async (pk: string, query: Query) => { diff --git a/src/services/items.ts b/src/services/items.ts index 7ab26ba46c..9ee1f6f82c 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -1,9 +1,9 @@ import database, { schemaInspector } from '../database'; import { Query } from '../types/query'; import runAST from '../database/run-ast'; -import getAST from '../utils/get-ast'; +import getAST from '../utils/get-ast-from-query'; import * as PayloadService from './payload'; -import { Accountability } from '../types/accountability'; +import { Accountability, AST } from '../types'; import * as ActivityService from './activity'; import * as RevisionsService from './revisions'; @@ -82,9 +82,8 @@ export const createItem = async ( export const readItems = async >( collection: string, - query: Query = {} + ast: AST ): Promise => { - const ast = await getAST(collection, query); const records = await runAST(ast); return await PayloadService.processValues('read', collection, records); }; @@ -106,9 +105,10 @@ export const readItem = async ( }, }; - const ast = await getAST(collection, query); - const records = await runAST(ast); - return await PayloadService.processValues('read', collection, records[0]); + // const ast = await getAST(collection, query); + // const records = await runAST(ast); + // return await PayloadService.processValues('read', collection, records[0]); + return; }; export const updateItem = async ( @@ -172,8 +172,10 @@ export const deleteItem = async ( .where({ [primaryKeyField]: pk }); }; -export const readSingleton = async (collection: string, query: Query = {}) => { - const records = await readItems(collection, { ...query, limit: 1 }); +export const readSingleton = async (collection: string, ast: AST) => { + ast.query.limit = 1; + + const records = await readItems(collection, ast); const record = records[0]; if (!record) { diff --git a/src/services/permissions.ts b/src/services/permissions.ts index f7f3f85f6a..ca27516fac 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -1,5 +1,15 @@ -import { Accountability, Permission, Operation, Query } from '../types'; +import { + Accountability, + AST, + NestedCollectionAST, + FieldAST, + Operation, + Query, + Permission, +} from '../types'; import * as ItemsService from './items'; +import database from '../database'; +import { ForbiddenException } from '../exceptions'; export const createPermission = async ( data: Record, @@ -9,7 +19,7 @@ export const createPermission = async ( }; export const readPermissions = async (query: Query) => { - return await ItemsService.readItems('directus_permissions', query); + // return await ItemsService.readItems('directus_permissions', query); }; export const readPermission = async (pk: number, query: Query) => { @@ -52,7 +62,138 @@ export const authorize = async (operation: Operation, collection: string, role?: }; } - const records = await ItemsService.readItems('directus_permissions', query); + // const records = await ItemsService.readItems('directus_permissions', query); - return records[0]; + // return records[0]; +}; + +export const processAST = async (role: string | null, ast: AST): Promise => { + const collectionsRequested = getCollectionsFromAST(ast); + const permissionsForCollections = await database + .select('*') + .from('directus_permissions') + .whereIn( + 'collection', + collectionsRequested.map(({ collection }) => collection) + ) + .andWhere('role', role); + + validateCollections(); + + // Convert the requested `*` fields to the actual allowed fields, so we don't attempt to fetch data you're not supposed to see + ast = convertWildcards(ast); + + validateFields(ast); + + return ast; + + /** + * Traverses the AST and returns an array of all collections that are being fetched + */ + function getCollectionsFromAST(ast: AST | NestedCollectionAST) { + const collections = []; + + if (ast.type === 'collection') { + collections.push({ + collection: ast.name, + field: (ast as NestedCollectionAST).fieldKey + ? (ast as NestedCollectionAST).fieldKey + : null, + }); + } + + for (const subAST of ast.children) { + if (subAST.type === 'collection') { + collections.push(...getCollectionsFromAST(subAST)); + } + } + + return collections; + } + + function validateCollections() { + // If the permissions don't match the collections, you don't have permission to read all of them + if (collectionsRequested.length !== permissionsForCollections.length) { + // Find the first collection that doesn't have permissions configured + const { collection, field } = collectionsRequested.find( + ({ collection }) => + permissionsForCollections.find( + (permission) => permission.collection === collection + ) === undefined + ); + + if (field) { + throw new ForbiddenException( + `You don't have permission to access the "${field}" field.` + ); + } else { + throw new ForbiddenException( + `You don't have permission to access the "${collection}" collection.` + ); + } + } + } + + /** + * Replace all requested wildcard `*` fields with the fields you're allowed to read + */ + function convertWildcards(ast: AST | NestedCollectionAST) { + if (ast.type === 'collection') { + const permission = permissionsForCollections.find( + (permission) => permission.collection === ast.name + ); + + const wildcardIndex = ast.children.findIndex((nestedAST) => { + return nestedAST.type === 'field' && nestedAST.name === '*'; + }); + + // Replace wildcard with array of fields you're allowed to read + if (wildcardIndex !== -1) { + const allowedFields = permission?.fields; + + if (allowedFields !== '*') { + const fields: FieldAST[] = allowedFields + .split(',') + .map((fieldKey) => ({ type: 'field', name: fieldKey })); + ast.children.splice(wildcardIndex, 1, ...fields); + } + } + + ast.children = ast.children + .map((childAST) => { + if (childAST.type === 'collection') { + return convertWildcards(childAST) as NestedCollectionAST | FieldAST; + } + + return childAST; + }) + .filter((c) => c); + } + + return ast; + } + + function validateFields(ast: AST | NestedCollectionAST) { + if (ast.type === 'collection') { + const collection = ast.name; + const permissions = permissionsForCollections.find( + (permission) => permission.collection === collection + ); + const allowedFields = permissions.fields.split(','); + + for (const childAST of ast.children) { + if (childAST.type === 'collection') { + validateFields(childAST); + continue; + } + + const fieldKey = childAST.name; + if (allowedFields.includes(fieldKey) === false) { + throw new ForbiddenException( + `You don't have permission to access the "${fieldKey}" field.` + ); + } + } + } + } }; diff --git a/src/services/presets.ts b/src/services/presets.ts index f953a4d737..8012bec5c8 100644 --- a/src/services/presets.ts +++ b/src/services/presets.ts @@ -11,7 +11,7 @@ export const createCollectionPreset = async ( }; export const readCollectionPresets = async (query: Query) => { - return await ItemsService.readItems('directus_presets', query); + // return await ItemsService.readItems('directus_presets', query); }; export const readCollectionPreset = async (pk: string | number, query: Query) => { diff --git a/src/services/relations.ts b/src/services/relations.ts index 9c359f2d24..8ff4acb432 100644 --- a/src/services/relations.ts +++ b/src/services/relations.ts @@ -6,7 +6,7 @@ export const createRelation = async (data: Record, accountability: }; export const readRelations = async (query: Query) => { - return await ItemsService.readItems('directus_relations', query); + // return await ItemsService.readItems('directus_relations', query); }; export const readRelation = async (pk: string | number, query: Query) => { diff --git a/src/services/revisions.ts b/src/services/revisions.ts index 29041cee0e..9c425ae231 100644 --- a/src/services/revisions.ts +++ b/src/services/revisions.ts @@ -6,7 +6,7 @@ export const createRevision = async (data: Record) => { }; export const readRevisions = async (query: Query) => { - return await ItemsService.readItems('directus_revisions', query); + // return await ItemsService.readItems('directus_revisions', query); }; export const readRevision = async (pk: string | number, query: Query) => { diff --git a/src/services/roles.ts b/src/services/roles.ts index 30b18f5f86..cc357c8dd2 100644 --- a/src/services/roles.ts +++ b/src/services/roles.ts @@ -6,7 +6,7 @@ export const createRole = async (data: Record, accountability: Acco }; export const readRoles = async (query: Query) => { - return await ItemsService.readItems('directus_roles', query); + // return await ItemsService.readItems('directus_roles', query); }; export const readRole = async (pk: string | number, query: Query) => { diff --git a/src/services/settings.ts b/src/services/settings.ts index 6d7fafd7ca..06198767cf 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -3,7 +3,7 @@ import * as ItemsService from './items'; import { Accountability } from '../types'; export const readSettings = async (query: Query) => { - return await ItemsService.readSingleton('directus_settings', query); + // return await ItemsService.readSingleton('directus_settings', query); }; export const updateSettings = async (data: Record, accountability: Accountability) => { diff --git a/src/services/users.ts b/src/services/users.ts index 50afd35af9..ddce886f5d 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -11,7 +11,7 @@ export const createUser = async (data: Record, accountability: Acco }; export const readUsers = async (query?: Query) => { - return await ItemsService.readItems('directus_users', query); + // return await ItemsService.readItems('directus_users', query); }; export const readUser = async (pk: string | number, query?: Query) => { diff --git a/src/services/webhooks.ts b/src/services/webhooks.ts index 5de126e65f..c7e9d80b29 100644 --- a/src/services/webhooks.ts +++ b/src/services/webhooks.ts @@ -6,7 +6,7 @@ export const createWebhook = async (data: Record, accountability: A }; export const readWebhooks = async (query: Query) => { - return await ItemsService.readItems('directus_webhooks', query); + // return await ItemsService.readItems('directus_webhooks', query); }; export const readWebhook = async (pk: string | number, query: Query) => { diff --git a/src/types/express.d.ts b/src/types/express.d.ts index e5407f4bab..33db09d58f 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -9,9 +9,10 @@ export {}; declare global { namespace Express { export interface Request { - token?: string; - user?: string; - role?: string; + token: string; + user: string; + role: string | null; + admin: boolean; collection?: string; sanitizedQuery?: Record; single?: boolean; diff --git a/src/utils/get-ast.ts b/src/utils/get-ast-from-query.ts similarity index 64% rename from src/utils/get-ast.ts rename to src/utils/get-ast-from-query.ts index eb8e52230d..e095ed4f7f 100644 --- a/src/utils/get-ast.ts +++ b/src/utils/get-ast-from-query.ts @@ -8,7 +8,11 @@ import { AST, NestedCollectionAST, FieldAST } from '../types/ast'; import database from '../database'; import * as FieldsService from '../services/fields'; -export default async function getAST(collection: string, query: Query): Promise { +export default async function getASTFromQuery( + role: string | null, + collection: string, + query: Query +): Promise { const ast: AST = { type: 'collection', name: collection, @@ -16,13 +20,14 @@ export default async function getAST(collection: string, query: Query): Promise< children: [], }; - if (!query.fields) query.fields = ['*']; + const fields = query.fields || ['*']; - /** @todo support wildcard */ - const fields = query.fields; + // Prevent fields from showing up in the query object + delete query.fields; // If no relational fields are requested, we can stop early - const hasRelations = query.fields.some((field) => field.includes('.')); + const hasRelations = fields.some((field) => field.includes('.')); + if (hasRelations === false) { fields.forEach((field) => { ast.children.push({ @@ -34,12 +39,18 @@ export default async function getAST(collection: string, query: Query): Promise< return ast; } - // Even though we might not need all records from relations, it'll be faster to load all records - // into memory once and search through it in JS than it would be to do individual queries to fetch - // this data field by field + // Even though we probably don't need all relations in this request, it's faster to fetch all of them up front + // and search through the relations in memory than to attempt to read each relation as a single SQL query + // @TODO look into using graphql/dataloader for this purpose const relations = await database.select('*').from('directus_relations'); - ast.children = await parseFields(collection, query.fields); + // All collections the current user is allowed to see. This is used to transform the wildcard requests into fields the + // user is actually allowed to read + const allowedCollections = ( + await database.select('collection').from('directus_permissions').where({ role }) + ).map(({ collection }) => collection); + + ast.children = await parseFields(collection, fields); return ast; @@ -48,9 +59,9 @@ export default async function getAST(collection: string, query: Query): Promise< const relationalStructure: Record = {}; - // Swap *.* case for *,.* - for (let i = 0; i < fields.length; i++) { - const fieldKey = fields[i]; + // Swap *.* case for *,.*,.* + for (let index = 0; index < fields.length; index++) { + const fieldKey = fields[index]; if (fieldKey.includes('.') === false) continue; @@ -58,13 +69,23 @@ export default async function getAST(collection: string, query: Query): Promise< if (parts[0] === '*') { const availableFields = await FieldsService.fieldsInCollection(parentCollection); - fields.splice( - i, - 1, - ...availableFields - .filter((field) => !!getRelation(parentCollection, field)) - .map((field) => `${field}.${parts.slice(1).join('.')}`) + + const relationalFields = availableFields.filter((field) => { + const relation = getRelation(parentCollection, field); + if (!relation) return false; + + return ( + allowedCollections.includes(relation.collection_one) && + allowedCollections.includes(relation.collection_many) + ); + }); + + const nestedFieldKeys = relationalFields.map( + (relationalField) => `${relationalField}.${parts.slice(1).join('.')}` ); + + fields.splice(index, 1, ...nestedFieldKeys); + fields.push('*'); } } From 54a2b3b74d3b839bdb00ddc7e30364238ffc7fb3 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 14 Jul 2020 16:53:32 -0400 Subject: [PATCH 11/18] Add fields permissions --- src/database/run-ast.ts | 107 ++++++++++++++------ src/routes/items.ts | 2 + src/services/permissions.ts | 170 ++++++++++++++++++++------------ src/utils/get-ast-from-query.ts | 107 ++++++++++---------- tsconfig.json | 2 +- 5 files changed, 238 insertions(+), 150 deletions(-) diff --git a/src/database/run-ast.ts b/src/database/run-ast.ts index cc6cdf8d13..7724b10f5c 100644 --- a/src/database/run-ast.ts +++ b/src/database/run-ast.ts @@ -1,16 +1,24 @@ import { AST, NestedCollectionAST } from '../types/ast'; -import { uniq } from 'lodash'; +import { uniq, pick } from 'lodash'; import database, { schemaInspector } from './index'; import { Filter, Query } from '../types'; import { QueryBuilder } from 'knex'; export default async function runAST(ast: AST, query = ast.query) { const toplevelFields: string[] = []; + const tempFields: string[] = []; const nestedCollections: NestedCollectionAST[] = []; + const primaryKeyField = await schemaInspector.primary(ast.name); + const columnsInCollection = (await schemaInspector.columns(ast.name)).map( + ({ column }) => column + ); for (const child of ast.children) { if (child.type === 'field') { - toplevelFields.push(child.name); + if (columnsInCollection.includes(child.name)) { + toplevelFields.push(child.name); + } + continue; } @@ -24,7 +32,12 @@ export default async function runAST(ast: AST, query = ast.query) { nestedCollections.push(child); } - let dbQuery = database.select(toplevelFields).from(ast.name); + /** Always fetch primary key in case there's a nested relation that needs it */ + if (toplevelFields.includes(primaryKeyField) === false) { + tempFields.push(primaryKeyField); + } + + let dbQuery = database.select([...toplevelFields, ...tempFields]).from(ast.name); if (query.filter) { applyFilter(dbQuery, query.filter); @@ -74,8 +87,18 @@ export default async function runAST(ast: AST, query = ast.query) { const m2o = isM2O(batch); let batchQuery: Query = {}; + let tempField: string = null; if (m2o) { + // Make sure we always fetch the nested items primary key field to ensure we have the key to match the item by + const toplevelFields = batch.children + .filter(({ type }) => type === 'field') + .map(({ name }) => name); + if (toplevelFields.includes(batch.relation.primary_one) === false) { + tempField = batch.relation.primary_one; + batch.children.push({ type: 'field', name: batch.relation.primary_one }); + } + batchQuery = { ...batch.query, filter: { @@ -88,6 +111,17 @@ export default async function runAST(ast: AST, query = ast.query) { }, }; } else { + // o2m + // Make sure we always fetch the related m2o field to ensure we have the foreign key to + // match the items by + const toplevelFields = batch.children + .filter(({ type }) => type === 'field') + .map(({ name }) => name); + if (toplevelFields.includes(batch.relation.field_many) === false) { + tempField = batch.relation.field_many; + batch.children.push({ type: 'field', name: batch.relation.field_many }); + } + batchQuery = { ...batch.query, filter: { @@ -103,34 +137,53 @@ export default async function runAST(ast: AST, query = ast.query) { results = results.map((record) => { if (m2o) { + const nestedResult = + nestedResults.find((nestedRecord) => { + return nestedRecord[batch.relation.primary_one] === record[batch.fieldKey]; + }) || null; + + if (tempField && nestedResult) { + delete nestedResult[tempField]; + } + return { ...record, - [batch.fieldKey]: - nestedResults.find((nestedRecord) => { - return ( - nestedRecord[batch.relation.primary_one] === record[batch.fieldKey] - ); - }) || null, + [batch.fieldKey]: nestedResult, }; } - return { + // o2m + const newRecord = { ...record, - [batch.fieldKey]: nestedResults.filter((nestedRecord) => { - /** - * @todo - * pull the name ID from somewhere real - */ - return ( - nestedRecord[batch.relation.field_many] === record.id || - nestedRecord[batch.relation.field_many]?.id === record.id - ); - }), + [batch.fieldKey]: nestedResults + .filter((nestedRecord) => { + /** + * @todo + * pull the name ID from somewhere real + */ + return ( + nestedRecord[batch.relation.field_many] === record.id || + nestedRecord[batch.relation.field_many]?.id === record.id + ); + }) + .map((nestedRecord) => { + if (tempField) { + delete nestedRecord[tempField]; + } + + return nestedRecord; + }), }; + + return newRecord; }); } - return results; + const nestedCollectionKeys = nestedCollections.map(({ fieldKey }) => fieldKey); + + return results.map((result) => + pick(result, uniq([...nestedCollectionKeys, ...toplevelFields])) + ); } function isM2O(child: NestedCollectionAST) { @@ -143,31 +196,29 @@ function applyFilter(dbQuery: QueryBuilder, filter: Filter) { for (const [key, value] of Object.entries(filter)) { if (key.startsWith('_') === false) { let operator = Object.keys(value)[0]; - operator = operator.slice(1); - operator = operator.toLowerCase(); const compareValue = Object.values(value)[0]; - if (operator === 'eq') { + if (operator === '_eq') { dbQuery.where({ [key]: compareValue }); } - if (operator === 'neq') { + if (operator === '_neq') { dbQuery.whereNot({ [key]: compareValue }); } - if (operator === 'in') { + if (operator === '_in') { let value = compareValue; if (typeof value === 'string') value = value.split(','); dbQuery.whereIn(key, value as string[]); } - if (operator === 'null') { + if (operator === '_null') { dbQuery.whereNull(key); } - if (operator === 'nnull') { + if (operator === '_nnull') { dbQuery.whereNotNull(key); } } diff --git a/src/routes/items.ts b/src/routes/items.ts index 3196845f3f..acb2fd8f15 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -38,6 +38,8 @@ router.get( asyncHandler(async (req, res) => { let ast = await getASTFromQuery(req.role, req.collection, req.sanitizedQuery); + console.log(JSON.stringify(ast, null, 2)); + ast = await PermissionsService.processAST(req.role, ast); const [records, meta] = await Promise.all([ diff --git a/src/services/permissions.ts b/src/services/permissions.ts index ca27516fac..1b6c348a55 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -6,10 +6,12 @@ import { Operation, Query, Permission, + Relation, } from '../types'; import * as ItemsService from './items'; import database from '../database'; import { ForbiddenException } from '../exceptions'; +import { uniq } from 'lodash'; export const createPermission = async ( data: Record, @@ -69,6 +71,7 @@ export const authorize = async (operation: Operation, collection: string, role?: export const processAST = async (role: string | null, ast: AST): Promise => { const collectionsRequested = getCollectionsFromAST(ast); + const permissionsForCollections = await database .select('*') .from('directus_permissions') @@ -78,10 +81,30 @@ export const processAST = async (role: string | null, ast: AST): Promise => ) .andWhere('role', role); - validateCollections(); + // If the permissions don't match the collections, you don't have permission to read all of them + const uniqueCollectionsRequestedCount = uniq( + collectionsRequested.map(({ collection }) => collection) + ).length; - // Convert the requested `*` fields to the actual allowed fields, so we don't attempt to fetch data you're not supposed to see - ast = convertWildcards(ast); + if (uniqueCollectionsRequestedCount !== permissionsForCollections.length) { + // Find the first collection that doesn't have permissions configured + const { collection, field } = collectionsRequested.find( + ({ collection }) => + permissionsForCollections.find( + (permission) => permission.collection === collection + ) === undefined + ); + + if (field) { + throw new ForbiddenException( + `You don't have permission to access the "${field}" field.` + ); + } else { + throw new ForbiddenException( + `You don't have permission to access the "${collection}" collection.` + ); + } + } validateFields(ast); @@ -111,68 +134,6 @@ export const processAST = async (role: string | null, ast: AST): Promise => return collections; } - function validateCollections() { - // If the permissions don't match the collections, you don't have permission to read all of them - if (collectionsRequested.length !== permissionsForCollections.length) { - // Find the first collection that doesn't have permissions configured - const { collection, field } = collectionsRequested.find( - ({ collection }) => - permissionsForCollections.find( - (permission) => permission.collection === collection - ) === undefined - ); - - if (field) { - throw new ForbiddenException( - `You don't have permission to access the "${field}" field.` - ); - } else { - throw new ForbiddenException( - `You don't have permission to access the "${collection}" collection.` - ); - } - } - } - - /** - * Replace all requested wildcard `*` fields with the fields you're allowed to read - */ - function convertWildcards(ast: AST | NestedCollectionAST) { - if (ast.type === 'collection') { - const permission = permissionsForCollections.find( - (permission) => permission.collection === ast.name - ); - - const wildcardIndex = ast.children.findIndex((nestedAST) => { - return nestedAST.type === 'field' && nestedAST.name === '*'; - }); - - // Replace wildcard with array of fields you're allowed to read - if (wildcardIndex !== -1) { - const allowedFields = permission?.fields; - - if (allowedFields !== '*') { - const fields: FieldAST[] = allowedFields - .split(',') - .map((fieldKey) => ({ type: 'field', name: fieldKey })); - ast.children.splice(wildcardIndex, 1, ...fields); - } - } - - ast.children = ast.children - .map((childAST) => { - if (childAST.type === 'collection') { - return convertWildcards(childAST) as NestedCollectionAST | FieldAST; - } - - return childAST; - }) - .filter((c) => c); - } - - return ast; - } - function validateFields(ast: AST | NestedCollectionAST) { if (ast.type === 'collection') { const collection = ast.name; @@ -197,3 +158,82 @@ export const processAST = async (role: string | null, ast: AST): Promise => } } }; + +/* +// Swap *.* case for *,.*,.* +for (let index = 0; index < fields.length; index++) { + const fieldKey = fields[index]; + + if (fieldKey.includes('.') === false) continue; + + const parts = fieldKey.split('.'); + + if (parts[0] === '*') { + const availableFields = await FieldsService.fieldsInCollection(parentCollection); + const allowedCollections = permissions.map(({ collection }) => collection); + + const relationalFields = availableFields.filter((field) => { + const relation = getRelation(parentCollection, field); + if (!relation) return false; + + return ( + allowedCollections.includes(relation.collection_one) && + allowedCollections.includes(relation.collection_many) + ); + }); + + const nestedFieldKeys = relationalFields.map( + (relationalField) => `${relationalField}.${parts.slice(1).join('.')}` + ); + + fields.splice(index, 1, ...nestedFieldKeys); + + fields.push('*'); + } +} + + +function convertWildcards(ast: AST | NestedCollectionAST) { + if (ast.type === 'collection') { + const permission = permissionsForCollections.find( + (permission) => permission.collection === ast.name + ); + + const wildcardIndex = ast.children.findIndex((nestedAST) => { + return nestedAST.type === 'field' && nestedAST.name === '*'; + }); + + // Replace wildcard with array of fields you're allowed to read + if (wildcardIndex !== -1) { + const allowedFields = permission?.fields; + + if (allowedFields !== '*') { + const currentFieldKeys = ast.children.map((field) => field.type === 'field' ? field.name : field.fieldKey); + console.log(currentFieldKeys); + const fields: FieldAST[] = allowedFields + .split(',') + // Make sure we don't include nested collections as columns + .filter((fieldKey) => { + console.log(currentFieldKeys, fieldKey, currentFieldKeys.includes(fieldKey)); + return currentFieldKeys.includes(fieldKey) === false; + }) + .map((fieldKey) => ({ type: 'field', name: fieldKey })); + + ast.children.splice(wildcardIndex, 1, ...fields); + } + } + + ast.children = ast.children + .map((childAST) => { + if (childAST.type === 'collection') { + return convertWildcards(childAST) as NestedCollectionAST | FieldAST; + } + + return childAST; + }) + .filter((c) => c); + } + + return ast; +} +*/ diff --git a/src/utils/get-ast-from-query.ts b/src/utils/get-ast-from-query.ts index e095ed4f7f..4f2a856f4e 100644 --- a/src/utils/get-ast-from-query.ts +++ b/src/utils/get-ast-from-query.ts @@ -2,10 +2,9 @@ * Generate an AST based on a given collection and query */ -import { Query } from '../types/query'; import { Relation } from '../types/relation'; -import { AST, NestedCollectionAST, FieldAST } from '../types/ast'; -import database from '../database'; +import { AST, NestedCollectionAST, FieldAST, Query } from '../types'; +import database, { schemaInspector } from '../database'; import * as FieldsService from '../services/fields'; export default async function getASTFromQuery( @@ -13,6 +12,16 @@ export default async function getASTFromQuery( collection: string, query: Query ): Promise { + /** + * we might not need al this info at all times, but it's easier to fetch it all once, than trying to fetch it for every + * requested field. @todo look into utilizing graphql/dataloader for this purpose + */ + const permissions = await database + .select<{ collection: string; fields: string }[]>('collection', 'fields') + .from('directus_permissions') + .where({ role, operation: 'read' }); + const relations = await database.select('*').from('directus_relations'); + const ast: AST = { type: 'collection', name: collection, @@ -20,76 +29,62 @@ export default async function getASTFromQuery( children: [], }; - const fields = query.fields || ['*']; + const fields = convertWildcards(collection, query.fields || ['*']); // Prevent fields from showing up in the query object delete query.fields; - // If no relational fields are requested, we can stop early - const hasRelations = fields.some((field) => field.includes('.')); - - if (hasRelations === false) { - fields.forEach((field) => { - ast.children.push({ - type: 'field', - name: field, - }); - }); - - return ast; - } - - // Even though we probably don't need all relations in this request, it's faster to fetch all of them up front - // and search through the relations in memory than to attempt to read each relation as a single SQL query - // @TODO look into using graphql/dataloader for this purpose - const relations = await database.select('*').from('directus_relations'); - - // All collections the current user is allowed to see. This is used to transform the wildcard requests into fields the - // user is actually allowed to read - const allowedCollections = ( - await database.select('collection').from('directus_permissions').where({ role }) - ).map(({ collection }) => collection); - - ast.children = await parseFields(collection, fields); + ast.children = parseFields(collection, fields); return ast; - async function parseFields(parentCollection: string, fields: string[]) { - const children: (NestedCollectionAST | FieldAST)[] = []; + function convertWildcards(parentCollection: string, fields: string[]) { + const allowedFields = permissions + .find((permission) => parentCollection === permission.collection) + ?.fields?.split(','); - const relationalStructure: Record = {}; - - // Swap *.* case for *,.*,.* for (let index = 0; index < fields.length; index++) { const fieldKey = fields[index]; - if (fieldKey.includes('.') === false) continue; + if (fieldKey.includes('*') === false) continue; - const parts = fieldKey.split('.'); + if (fieldKey === '*') { + fields.splice(index, 1, ...allowedFields); + } - if (parts[0] === '*') { - const availableFields = await FieldsService.fieldsInCollection(parentCollection); - - const relationalFields = availableFields.filter((field) => { - const relation = getRelation(parentCollection, field); - if (!relation) return false; - - return ( - allowedCollections.includes(relation.collection_one) && - allowedCollections.includes(relation.collection_many) - ); - }); - - const nestedFieldKeys = relationalFields.map( - (relationalField) => `${relationalField}.${parts.slice(1).join('.')}` + // Swap *.* case for *,.*,.* + if (fieldKey.includes('.') && fieldKey.split('.')[0] === '*') { + const parts = fieldKey.split('.'); + const relationalFields = allowedFields.filter( + (fieldKey) => !!getRelation(parentCollection, fieldKey) + ); + const nonRelationalFields = allowedFields.filter( + (fieldKey) => relationalFields.includes(fieldKey) === false ); - fields.splice(index, 1, ...nestedFieldKeys); - - fields.push('*'); + fields.splice( + index, + 1, + ...[ + ...relationalFields.map((relationalField) => { + return `${relationalField}.${parts.slice(1).join('.')}`; + }), + ...nonRelationalFields, + ] + ); } } + return fields; + } + + function parseFields(parentCollection: string, fields: string[]) { + fields = convertWildcards(parentCollection, fields); + + const children: (NestedCollectionAST | FieldAST)[] = []; + + const relationalStructure: Record = {}; + for (const field of fields) { if (field.includes('.') === false) { children.push({ type: 'field', name: field }); @@ -115,7 +110,7 @@ export default async function getASTFromQuery( parentKey: 'id' /** @todo this needs to come from somewhere real */, relation: getRelation(parentCollection, relationalField), query: {} /** @todo inject nested query here */, - children: await parseFields(relatedCollection, nestedFields), + children: parseFields(relatedCollection, nestedFields), }; children.push(child); diff --git a/tsconfig.json b/tsconfig.json index 29c1c7ba4e..9ab7799317 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,6 @@ "rootDir": "src" }, "lib": [ - "es2015" + "es2019" ], } From dd6c7043ac58411b8ca55198b40bd8c1a57792eb Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 14 Jul 2020 17:13:37 -0400 Subject: [PATCH 12/18] Fix couple hiccups, support * --- src/database/run-ast.ts | 6 ++- src/services/permissions.ts | 94 ++++++++------------------------- src/utils/get-ast-from-query.ts | 2 +- 3 files changed, 28 insertions(+), 74 deletions(-) diff --git a/src/database/run-ast.ts b/src/database/run-ast.ts index 7724b10f5c..957e78e6f0 100644 --- a/src/database/run-ast.ts +++ b/src/database/run-ast.ts @@ -15,7 +15,7 @@ export default async function runAST(ast: AST, query = ast.query) { for (const child of ast.children) { if (child.type === 'field') { - if (columnsInCollection.includes(child.name)) { + if (columnsInCollection.includes(child.name) || child.name === '*') { toplevelFields.push(child.name); } @@ -181,6 +181,10 @@ export default async function runAST(ast: AST, query = ast.query) { const nestedCollectionKeys = nestedCollections.map(({ fieldKey }) => fieldKey); + if (toplevelFields.includes('*')) { + return results; + } + return results.map((result) => pick(result, uniq([...nestedCollectionKeys, ...toplevelFields])) ); diff --git a/src/services/permissions.ts b/src/services/permissions.ts index 1b6c348a55..1982fd6b2f 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -108,6 +108,8 @@ export const processAST = async (role: string | null, ast: AST): Promise => validateFields(ast); + applyFilters(ast); + return ast; /** @@ -148,7 +150,10 @@ export const processAST = async (role: string | null, ast: AST): Promise => continue; } + if (allowedFields.includes('*')) continue; + const fieldKey = childAST.name; + if (allowedFields.includes(fieldKey) === false) { throw new ForbiddenException( `You don't have permission to access the "${fieldKey}" field.` @@ -157,83 +162,28 @@ export const processAST = async (role: string | null, ast: AST): Promise => } } } -}; -/* -// Swap *.* case for *,.*,.* -for (let index = 0; index < fields.length; index++) { - const fieldKey = fields[index]; + function applyFilters( + ast: AST | NestedCollectionAST | FieldAST + ): AST | NestedCollectionAST | FieldAST { + if (ast.type === 'collection') { + const collection = ast.name; - if (fieldKey.includes('.') === false) continue; - - const parts = fieldKey.split('.'); - - if (parts[0] === '*') { - const availableFields = await FieldsService.fieldsInCollection(parentCollection); - const allowedCollections = permissions.map(({ collection }) => collection); - - const relationalFields = availableFields.filter((field) => { - const relation = getRelation(parentCollection, field); - if (!relation) return false; - - return ( - allowedCollections.includes(relation.collection_one) && - allowedCollections.includes(relation.collection_many) + const permissions = permissionsForCollections.find( + (permission) => permission.collection === collection ); - }); - const nestedFieldKeys = relationalFields.map( - (relationalField) => `${relationalField}.${parts.slice(1).join('.')}` - ); + ast.query = { + ...ast.query, + filter: { + ...(ast.query.filter || {}), + ...permissions.permissions, + }, + }; - fields.splice(index, 1, ...nestedFieldKeys); - - fields.push('*'); - } -} - - -function convertWildcards(ast: AST | NestedCollectionAST) { - if (ast.type === 'collection') { - const permission = permissionsForCollections.find( - (permission) => permission.collection === ast.name - ); - - const wildcardIndex = ast.children.findIndex((nestedAST) => { - return nestedAST.type === 'field' && nestedAST.name === '*'; - }); - - // Replace wildcard with array of fields you're allowed to read - if (wildcardIndex !== -1) { - const allowedFields = permission?.fields; - - if (allowedFields !== '*') { - const currentFieldKeys = ast.children.map((field) => field.type === 'field' ? field.name : field.fieldKey); - console.log(currentFieldKeys); - const fields: FieldAST[] = allowedFields - .split(',') - // Make sure we don't include nested collections as columns - .filter((fieldKey) => { - console.log(currentFieldKeys, fieldKey, currentFieldKeys.includes(fieldKey)); - return currentFieldKeys.includes(fieldKey) === false; - }) - .map((fieldKey) => ({ type: 'field', name: fieldKey })); - - ast.children.splice(wildcardIndex, 1, ...fields); - } + ast.children = ast.children.map(applyFilters) as (NestedCollectionAST | FieldAST)[]; } - ast.children = ast.children - .map((childAST) => { - if (childAST.type === 'collection') { - return convertWildcards(childAST) as NestedCollectionAST | FieldAST; - } - - return childAST; - }) - .filter((c) => c); + return ast; } - - return ast; -} -*/ +}; diff --git a/src/utils/get-ast-from-query.ts b/src/utils/get-ast-from-query.ts index 4f2a856f4e..8ed61a4604 100644 --- a/src/utils/get-ast-from-query.ts +++ b/src/utils/get-ast-from-query.ts @@ -5,7 +5,6 @@ import { Relation } from '../types/relation'; import { AST, NestedCollectionAST, FieldAST, Query } from '../types'; import database, { schemaInspector } from '../database'; -import * as FieldsService from '../services/fields'; export default async function getASTFromQuery( role: string | null, @@ -49,6 +48,7 @@ export default async function getASTFromQuery( if (fieldKey.includes('*') === false) continue; if (fieldKey === '*') { + if (allowedFields.includes('*')) continue; fields.splice(index, 1, ...allowedFields); } From a5620b8b37b39fae09e241c9f73143b3feb9fbb0 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 14 Jul 2020 18:48:21 -0400 Subject: [PATCH 13/18] It works, pfew --- src/database/run-ast.ts | 12 ++++++++---- src/utils/get-ast-from-query.ts | 34 +++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/database/run-ast.ts b/src/database/run-ast.ts index 957e78e6f0..b43ee7a1c0 100644 --- a/src/database/run-ast.ts +++ b/src/database/run-ast.ts @@ -1,5 +1,5 @@ import { AST, NestedCollectionAST } from '../types/ast'; -import { uniq, pick } from 'lodash'; +import { clone, uniq, pick } from 'lodash'; import database, { schemaInspector } from './index'; import { Filter, Query } from '../types'; import { QueryBuilder } from 'knex'; @@ -138,9 +138,13 @@ export default async function runAST(ast: AST, query = ast.query) { results = results.map((record) => { if (m2o) { const nestedResult = - nestedResults.find((nestedRecord) => { - return nestedRecord[batch.relation.primary_one] === record[batch.fieldKey]; - }) || null; + clone( + nestedResults.find((nestedRecord) => { + return ( + nestedRecord[batch.relation.primary_one] === record[batch.fieldKey] + ); + }) + ) || null; if (tempField && nestedResult) { delete nestedResult[tempField]; diff --git a/src/utils/get-ast-from-query.ts b/src/utils/get-ast-from-query.ts index 8ed61a4604..eec6fcb6b1 100644 --- a/src/utils/get-ast-from-query.ts +++ b/src/utils/get-ast-from-query.ts @@ -28,12 +28,12 @@ export default async function getASTFromQuery( children: [], }; - const fields = convertWildcards(collection, query.fields || ['*']); + const fields = query.fields || ['*']; // Prevent fields from showing up in the query object delete query.fields; - ast.children = parseFields(collection, fields); + ast.children = parseFields(collection, fields).filter(filterEmptyChildCollections); return ast; @@ -42,6 +42,8 @@ export default async function getASTFromQuery( .find((permission) => parentCollection === permission.collection) ?.fields?.split(','); + if (!allowedFields || allowedFields.length === 0) return []; + for (let index = 0; index < fields.length; index++) { const fieldKey = fields[index]; @@ -55,9 +57,20 @@ export default async function getASTFromQuery( // Swap *.* case for *,.*,.* if (fieldKey.includes('.') && fieldKey.split('.')[0] === '*') { const parts = fieldKey.split('.'); - const relationalFields = allowedFields.filter( - (fieldKey) => !!getRelation(parentCollection, fieldKey) - ); + + const relationalFields = allowedFields.includes('*') + ? relations + .filter( + (relation) => + relation.collection_many === parentCollection || + relation.collection_one === parentCollection + ) + .map((relation) => { + const isM2O = relation.collection_many === parentCollection; + return isM2O ? relation.field_many : relation.field_one; + }) + : allowedFields.filter((fieldKey) => !!getRelation(parentCollection, fieldKey)); + const nonRelationalFields = allowedFields.filter( (fieldKey) => relationalFields.includes(fieldKey) === false ); @@ -81,6 +94,8 @@ export default async function getASTFromQuery( function parseFields(parentCollection: string, fields: string[]) { fields = convertWildcards(parentCollection, fields); + if (!fields) return null; + const children: (NestedCollectionAST | FieldAST)[] = []; const relationalStructure: Record = {}; @@ -110,7 +125,9 @@ export default async function getASTFromQuery( parentKey: 'id' /** @todo this needs to come from somewhere real */, relation: getRelation(parentCollection, relationalField), query: {} /** @todo inject nested query here */, - children: parseFields(relatedCollection, nestedFields), + children: parseFields(relatedCollection, nestedFields).filter( + filterEmptyChildCollections + ), }; children.push(child); @@ -143,4 +160,9 @@ export default async function getASTFromQuery( return relation.collection_many; } } + + function filterEmptyChildCollections(childAST: NestedCollectionAST) { + if (childAST.type === 'collection' && childAST.children.length === 0) return false; + return true; + } } From 61738c5aa8b4d576efbaab6b7d3767198319b88d Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 15 Jul 2020 12:19:27 -0400 Subject: [PATCH 14/18] Use accountability on all endpoints --- src/routes/activity.ts | 32 +++++++++++++++------ src/routes/collections.ts | 11 ++++++-- src/routes/fields.ts | 11 +++++++- src/routes/files.ts | 22 +++++++++++---- src/routes/folders.ts | 19 +++++++++---- src/routes/items.ts | 35 ++++++++++++----------- src/routes/permissions.ts | 27 +++++++++++------- src/routes/presets.ts | 25 ++++++++++------- src/routes/relations.ts | 28 ++++++++++++------- src/routes/revisions.ts | 14 ++++++---- src/routes/roles.ts | 26 ++++++++++------- src/routes/settings.ts | 9 +++--- src/routes/users.ts | 38 ++++++++++++++----------- src/routes/webhooks.ts | 26 ++++++++++------- src/services/activity.ts | 12 +++++--- src/services/collections.ts | 45 ++++++++++++++++++------------ src/services/fields.ts | 5 ++++ src/services/files.ts | 12 +++++--- src/services/folders.ts | 8 +++--- src/services/items.ts | 39 ++++++++++++++++++-------- src/services/permissions.ts | 49 ++++++--------------------------- src/services/presets.ts | 12 +++++--- src/services/relations.ts | 12 +++++--- src/services/revisions.ts | 14 ++++++---- src/services/roles.ts | 12 +++++--- src/services/settings.ts | 4 +-- src/services/users.ts | 12 +++++--- src/services/webhooks.ts | 12 +++++--- src/types/accountability.ts | 3 +- src/utils/get-ast-from-query.ts | 24 ++++++++++------ 30 files changed, 364 insertions(+), 234 deletions(-) diff --git a/src/routes/activity.ts b/src/routes/activity.ts index 751520d6a7..d73fd3d126 100644 --- a/src/routes/activity.ts +++ b/src/routes/activity.ts @@ -6,24 +6,29 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); -router.use(useCollection('directus_activity')); - router.get( '/', + useCollection('directus_activity'), sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await ActivityService.readActivities(req.sanitizedQuery); - // return res.json({ - // data: records || null, - // }); + const records = await ActivityService.readActivities(req.sanitizedQuery, { + role: req.role, + }); + + return res.json({ + data: records || null, + }); }) ); router.get( '/:pk', + useCollection('directus_activity'), sanitizeQuery, asyncHandler(async (req, res) => { - const record = await ActivityService.readActivity(req.params.pk, req.sanitizedQuery); + const record = await ActivityService.readActivity(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null, @@ -33,6 +38,7 @@ router.get( router.post( '/comment', + useCollection('directus_activity'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await ActivityService.createActivity({ @@ -43,7 +49,9 @@ router.post( user_agent: req.get('user-agent'), }); - const record = await ActivityService.readActivity(primaryKey, req.sanitizedQuery); + const record = await ActivityService.readActivity(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null, @@ -53,15 +61,19 @@ router.post( router.patch( '/comment/:pk', + useCollection('directus_activity'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await ActivityService.updateActivity(req.params.pk, req.body, { + role: req.role, user: req.user, ip: req.ip, userAgent: req.get('user-agent'), }); - const record = await ActivityService.readActivity(primaryKey, req.sanitizedQuery); + const record = await ActivityService.readActivity(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null, @@ -71,8 +83,10 @@ router.patch( router.delete( '/comment/:pk', + useCollection('directus_activity'), asyncHandler(async (req, res) => { await ActivityService.deleteActivity(req.params.pk, { + role: req.role, user: req.user, ip: req.ip, userAgent: req.get('user-agent'), diff --git a/src/routes/collections.ts b/src/routes/collections.ts index 38d823e1fb..ee4d2b0cb8 100644 --- a/src/routes/collections.ts +++ b/src/routes/collections.ts @@ -31,6 +31,7 @@ router.post( if (error) throw new InvalidPayloadException(error.message); const createdCollection = await CollectionsService.create(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -45,8 +46,10 @@ router.get( useCollection('directus_collections'), sanitizeQuery, asyncHandler(async (req, res) => { - // const collections = await CollectionsService.readAll(req.sanitizedQuery); - // res.json({ data: collections || null }); + const collections = await CollectionsService.readAll(req.sanitizedQuery, { + role: req.role, + }); + res.json({ data: collections || null }); }) ); @@ -61,7 +64,8 @@ router.get( const collection = await CollectionsService.readOne( req.params.collection, - req.sanitizedQuery + req.sanitizedQuery, + { role: req.role } ); res.json({ data: collection || null }); }) @@ -76,6 +80,7 @@ router.delete( } await CollectionsService.deleteCollection(req.params.collection, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/fields.ts b/src/routes/fields.ts index bdcd2be6e3..768a97d48f 100644 --- a/src/routes/fields.ts +++ b/src/routes/fields.ts @@ -10,10 +10,15 @@ import useCollection from '../middleware/use-collection'; const router = Router(); -router.use(useCollection('directus_fields')); +/** + * @TODO + * + * Add accountability / permissions handling to fields + */ router.get( '/', + useCollection('directus_fields'), asyncHandler(async (req, res) => { const fields = await FieldsService.readAll(); return res.json({ data: fields || null }); @@ -22,6 +27,7 @@ router.get( router.get( '/:collection', + useCollection('directus_fields'), validateCollection, asyncHandler(async (req, res) => { const fields = await FieldsService.readAll(req.collection); @@ -31,6 +37,7 @@ router.get( router.get( '/:collection/:field', + useCollection('directus_fields'), validateCollection, asyncHandler(async (req, res) => { const exists = await schemaInspector.hasColumn(req.collection, req.params.field); @@ -54,6 +61,7 @@ const newFieldSchema = Joi.object({ router.post( '/:collection', + useCollection('directus_fields'), validateCollection, asyncHandler(async (req, res) => { const { error } = newFieldSchema.validate(req.body); @@ -65,6 +73,7 @@ router.post( const field: Partial = req.body; const createdField = await FieldsService.createField(req.collection, field, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/files.ts b/src/routes/files.ts index 15436da535..427db82d33 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -56,11 +56,14 @@ const multipartHandler = (operation: 'create' | 'update') => try { if (operation === 'create') { const pk = await FilesService.createFile(payload, fileStream, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const file = await FilesService.readFile(pk, req.sanitizedQuery); + const file = await FilesService.readFile(pk, req.sanitizedQuery, { + role: req.role, + }); savedFiles.push(file); } else { @@ -68,13 +71,16 @@ const multipartHandler = (operation: 'create' | 'update') => req.params.pk, payload, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }, fileStream ); - const file = await FilesService.readFile(pk, req.sanitizedQuery); + const file = await FilesService.readFile(pk, req.sanitizedQuery, { + role: req.role, + }); savedFiles.push(file); } @@ -100,8 +106,8 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await FilesService.readFiles(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await FilesService.readFiles(req.sanitizedQuery, { role: req.role }); + return res.json({ data: records || null }); }) ); @@ -109,7 +115,9 @@ router.get( '/:pk', sanitizeQuery, asyncHandler(async (req, res) => { - const record = await FilesService.readFile(req.params.pk, req.sanitizedQuery); + const record = await FilesService.readFile(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); @@ -124,11 +132,12 @@ router.patch( file = await multipartHandler('update')(req, res, next); } else { const pk = await FilesService.updateFile(req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - file = await FilesService.readFile(pk, req.sanitizedQuery); + file = await FilesService.readFile(pk, req.sanitizedQuery, { role: req.role }); } return res.status(200).json({ data: file || null }); @@ -139,6 +148,7 @@ router.delete( '/:pk', asyncHandler(async (req, res) => { await FilesService.deleteFile(req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/folders.ts b/src/routes/folders.ts index 5d60b7dd5f..1d2873fa5b 100644 --- a/src/routes/folders.ts +++ b/src/routes/folders.ts @@ -12,12 +12,15 @@ router.post( '/', asyncHandler(async (req, res) => { const primaryKey = await FoldersService.createFolder(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery); + const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); @@ -26,8 +29,8 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await FoldersService.readFolders(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await FoldersService.readFolders(req.sanitizedQuery, { role: req.role }); + return res.json({ data: records || null }); }) ); @@ -35,7 +38,9 @@ router.get( '/:pk', sanitizeQuery, asyncHandler(async (req, res) => { - const record = await FoldersService.readFolder(req.params.pk, req.sanitizedQuery); + const record = await FoldersService.readFolder(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); @@ -45,12 +50,15 @@ router.patch( sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await FoldersService.updateFolder(req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery); + const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) @@ -60,6 +68,7 @@ router.delete( '/:pk', asyncHandler(async (req, res) => { await FoldersService.deleteFolder(req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/items.ts b/src/routes/items.ts index acb2fd8f15..e761e7c424 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -4,9 +4,7 @@ import * as ItemsService from '../services/items'; import sanitizeQuery from '../middleware/sanitize-query'; import collectionExists from '../middleware/collection-exists'; import * as MetaService from '../services/meta'; -import * as PermissionsService from '../services/permissions'; import { RouteNotFoundException } from '../exceptions'; -import getASTFromQuery from '../utils/get-ast-from-query'; const router = express.Router(); @@ -20,12 +18,15 @@ router.post( } const primaryKey = await ItemsService.createItem(req.collection, req.body, { + user: req.user, + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), - user: req.user, }); - const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery); + const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery, { + role: req.role, + }); res.json({ data: item || null }); }) @@ -36,16 +37,10 @@ router.get( collectionExists, sanitizeQuery, asyncHandler(async (req, res) => { - let ast = await getASTFromQuery(req.role, req.collection, req.sanitizedQuery); - - console.log(JSON.stringify(ast, null, 2)); - - ast = await PermissionsService.processAST(req.role, ast); - const [records, meta] = await Promise.all([ req.single - ? ItemsService.readSingleton(req.collection, ast) - : ItemsService.readItems(req.collection, ast), + ? ItemsService.readSingleton(req.collection, req.sanitizedQuery, { role: req.role }) + : ItemsService.readItems(req.collection, req.sanitizedQuery, { role: req.role }), MetaService.getMetaForQuery(req.collection, req.sanitizedQuery), ]); @@ -68,7 +63,8 @@ router.get( const record = await ItemsService.readItem( req.collection, req.params.pk, - req.sanitizedQuery + req.sanitizedQuery, + { role: req.role } ); return res.json({ @@ -87,14 +83,17 @@ router.patch( } await ItemsService.upsertSingleton(req.collection, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - // const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery); + const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery, { + role: req.role, + }); - // return res.json({ data: item || null }); + return res.json({ data: item || null }); }) ); @@ -108,12 +107,15 @@ router.patch( } const primaryKey = await ItemsService.updateItem(req.collection, req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery); + const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) @@ -124,6 +126,7 @@ router.delete( collectionExists, asyncHandler(async (req, res) => { await ItemsService.deleteItem(req.collection, req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/permissions.ts b/src/routes/permissions.ts index 1e5a2ec34d..bc3f8a93ed 100644 --- a/src/routes/permissions.ts +++ b/src/routes/permissions.ts @@ -6,17 +6,21 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_permissions')); + router.post( '/', - useCollection('directus_permissions'), asyncHandler(async (req, res) => { const primaryKey = await PermissionsService.createPermission(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery); + const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) @@ -24,22 +28,23 @@ router.post( router.get( '/', - useCollection('directus_permissions'), sanitizeQuery, asyncHandler(async (req, res) => { - const item = await PermissionsService.readPermissions(req.sanitizedQuery); - // return res.json({ data: item || null }); + const item = await PermissionsService.readPermissions(req.sanitizedQuery, { + role: req.role, + }); + return res.json({ data: item || null }); }) ); router.get( '/:pk', - useCollection('directus_permissions'), sanitizeQuery, asyncHandler(async (req, res) => { const record = await PermissionsService.readPermission( Number(req.params.pk), - req.sanitizedQuery + req.sanitizedQuery, + { role: req.role } ); return res.json({ data: record || null }); }) @@ -47,19 +52,21 @@ router.get( router.patch( '/:pk', - useCollection('directus_permissions'), asyncHandler(async (req, res) => { const primaryKey = await PermissionsService.updatePermission( Number(req.params.pk), req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, } ); - const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery); + const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) @@ -67,9 +74,9 @@ router.patch( router.delete( '/:pk', - useCollection('directus_permissions'), asyncHandler(async (req, res) => { await PermissionsService.deletePermission(Number(req.params.pk), { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/presets.ts b/src/routes/presets.ts index 0a319aa99d..4051353d72 100644 --- a/src/routes/presets.ts +++ b/src/routes/presets.ts @@ -6,11 +6,13 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_presets')); + router.post( '/', - useCollection('directus_presets'), asyncHandler(async (req, res) => { const primaryKey = await CollectionPresetsService.createCollectionPreset(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -18,7 +20,8 @@ router.post( const record = await CollectionPresetsService.readCollectionPreset( primaryKey, - req.sanitizedQuery + req.sanitizedQuery, + { role: req.role } ); return res.json({ data: record || null }); }) @@ -26,22 +29,23 @@ router.post( router.get( '/', - useCollection('directus_presets'), sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await CollectionPresetsService.readCollectionPresets(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await CollectionPresetsService.readCollectionPresets(req.sanitizedQuery, { + role: req.role, + }); + return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_presets'), sanitizeQuery, asyncHandler(async (req, res) => { const record = await CollectionPresetsService.readCollectionPreset( req.params.pk, - req.sanitizedQuery + req.sanitizedQuery, + { role: req.role } ); return res.json({ data: record || null }); }) @@ -49,13 +53,13 @@ router.get( router.patch( '/:pk', - useCollection('directus_presets'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await CollectionPresetsService.updateCollectionPreset( req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -64,7 +68,8 @@ router.patch( const record = await CollectionPresetsService.readCollectionPreset( primaryKey, - req.sanitizedQuery + req.sanitizedQuery, + { role: req.role } ); return res.json({ data: record || null }); }) @@ -72,9 +77,9 @@ router.patch( router.delete( '/:pk', - useCollection('directus_presets'), asyncHandler(async (req, res) => { await CollectionPresetsService.deleteCollectionPreset(req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/relations.ts b/src/routes/relations.ts index fe91168eef..aef2541b25 100644 --- a/src/routes/relations.ts +++ b/src/routes/relations.ts @@ -6,61 +6,69 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_relations')); + router.post( '/', - useCollection('directus_relations'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await RelationsService.createRelation(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery); + const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.get( '/', - useCollection('directus_relations'), sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await RelationsService.readRelations(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await RelationsService.readRelations(req.sanitizedQuery, { + role: req.role, + }); + return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_relations'), sanitizeQuery, asyncHandler(async (req, res) => { - const record = await RelationsService.readRelation(req.params.pk, req.sanitizedQuery); + const record = await RelationsService.readRelation(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); router.patch( '/:pk', - useCollection('directus_relations'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await RelationsService.updateRelation(req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery); + const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.delete( '/:pk', - useCollection('directus_relations'), asyncHandler(async (req, res) => { await RelationsService.deleteRelation(Number(req.params.pk), { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/revisions.ts b/src/routes/revisions.ts index ca72f46883..9e878e29b8 100644 --- a/src/routes/revisions.ts +++ b/src/routes/revisions.ts @@ -6,22 +6,26 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_revisions')); + router.get( '/', - useCollection('directus_revisions'), sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await RevisionsService.readRevisions(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await RevisionsService.readRevisions(req.sanitizedQuery, { + role: req.role, + }); + return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_revisions'), sanitizeQuery, asyncHandler(async (req, res) => { - const record = await RevisionsService.readRevision(req.params.pk, req.sanitizedQuery); + const record = await RevisionsService.readRevision(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); diff --git a/src/routes/roles.ts b/src/routes/roles.ts index 272b8d41d7..585189c0f8 100644 --- a/src/routes/roles.ts +++ b/src/routes/roles.ts @@ -6,61 +6,67 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_roles')); + router.post( '/', - useCollection('directus_roles'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await RolesService.createRole(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await RolesService.readRole(primaryKey, req.sanitizedQuery); + const item = await RolesService.readRole(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.get( '/', - useCollection('directus_roles'), sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await RolesService.readRoles(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await RolesService.readRoles(req.sanitizedQuery, { role: req.role }); + return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_roles'), sanitizeQuery, asyncHandler(async (req, res) => { - const record = await RolesService.readRole(req.params.pk, req.sanitizedQuery); + const record = await RolesService.readRole(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); router.patch( '/:pk', - useCollection('directus_roles'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await RolesService.updateRole(req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await RolesService.readRole(primaryKey, req.sanitizedQuery); + const item = await RolesService.readRole(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.delete( '/:pk', - useCollection('directus_roles'), asyncHandler(async (req, res) => { await RolesService.deleteRole(req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/settings.ts b/src/routes/settings.ts index f5b84ff546..308475edb7 100644 --- a/src/routes/settings.ts +++ b/src/routes/settings.ts @@ -11,8 +11,8 @@ router.get( useCollection('directus_settings'), sanitizeQuery, asyncHandler(async (req, res) => { - const records = await SettingsService.readSettings(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await SettingsService.readSettings(req.sanitizedQuery, { role: req.role }); + return res.json({ data: records || null }); }) ); @@ -22,14 +22,15 @@ router.patch( sanitizeQuery, asyncHandler(async (req, res) => { await SettingsService.updateSettings(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - // const record = await SettingsService.readSettings(req.sanitizedQuery); + const record = await SettingsService.readSettings(req.sanitizedQuery, { role: req.role }); - // return res.json({ data: record || null }); + return res.json({ data: record || null }); }) ); diff --git a/src/routes/users.ts b/src/routes/users.ts index 1839235072..7a4da26ff8 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -8,58 +8,60 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_users')); + router.post( '/', - useCollection('directus_users'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await UsersService.createUser(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await UsersService.readUser(primaryKey, req.sanitizedQuery); + const item = await UsersService.readUser(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.get( '/', - useCollection('directus_users'), sanitizeQuery, asyncHandler(async (req, res) => { - // const item = await UsersService.readUsers(req.sanitizedQuery); - // return res.json({ data: item || null }); + const item = await UsersService.readUsers(req.sanitizedQuery, { role: req.role }); + return res.json({ data: item || null }); }) ); router.get( '/me', - useCollection('directus_users'), sanitizeQuery, asyncHandler(async (req, res) => { if (!req.user) { throw new InvalidCredentialsException(); } - const item = await UsersService.readUser(req.user, req.sanitizedQuery); + const item = await UsersService.readUser(req.user, req.sanitizedQuery, { role: req.role }); return res.json({ data: item || null }); }) ); router.get( '/:pk', - useCollection('directus_users'), sanitizeQuery, asyncHandler(async (req, res) => { - const items = await UsersService.readUser(req.params.pk, req.sanitizedQuery); + const items = await UsersService.readUser(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: items || null }); }) ); router.patch( '/me', - useCollection('directus_users'), sanitizeQuery, asyncHandler(async (req, res) => { if (!req.user) { @@ -67,36 +69,41 @@ router.patch( } const primaryKey = await UsersService.updateUser(req.user, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await UsersService.readUser(primaryKey, req.sanitizedQuery); + const item = await UsersService.readUser(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.patch( '/:pk', - useCollection('directus_users'), sanitizeQuery, asyncHandler(async (req, res) => { const primaryKey = await UsersService.updateUser(req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await UsersService.readUser(primaryKey, req.sanitizedQuery); + const item = await UsersService.readUser(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.delete( '/:pk', - useCollection('directus_users'), asyncHandler(async (req, res) => { await UsersService.deleteUser(req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -113,11 +120,11 @@ const inviteSchema = Joi.object({ router.post( '/invite', - useCollection('directus_users'), asyncHandler(async (req, res) => { const { error } = inviteSchema.validate(req.body); if (error) throw new InvalidPayloadException(error.message); await UsersService.inviteUser(req.body.email, req.body.role, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -133,7 +140,6 @@ const acceptInviteSchema = Joi.object({ router.post( '/invite/accept', - useCollection('directus_users'), asyncHandler(async (req, res) => { const { error } = acceptInviteSchema.validate(req.body); if (error) throw new InvalidPayloadException(error.message); diff --git a/src/routes/webhooks.ts b/src/routes/webhooks.ts index 058ab9cd8e..c6e86520a9 100644 --- a/src/routes/webhooks.ts +++ b/src/routes/webhooks.ts @@ -6,17 +6,21 @@ import useCollection from '../middleware/use-collection'; const router = express.Router(); +router.use(useCollection('directus_webhooks')); + router.post( '/', - useCollection('directus_webhooks'), asyncHandler(async (req, res) => { const primaryKey = await WebhooksService.createWebhook(req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery); + const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) @@ -24,43 +28,45 @@ router.post( router.get( '/', - useCollection('directus_webhooks'), sanitizeQuery, asyncHandler(async (req, res) => { - // const records = await WebhooksService.readWebhooks(req.sanitizedQuery); - // return res.json({ data: records || null }); + const records = await WebhooksService.readWebhooks(req.sanitizedQuery, { role: req.role }); + return res.json({ data: records || null }); }) ); router.get( '/:pk', - useCollection('directus_webhooks'), sanitizeQuery, asyncHandler(async (req, res) => { - const record = await WebhooksService.readWebhook(req.params.pk, req.sanitizedQuery); + const record = await WebhooksService.readWebhook(req.params.pk, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: record || null }); }) ); router.patch( '/:pk', - useCollection('directus_webhooks'), asyncHandler(async (req, res) => { const primaryKey = await WebhooksService.updateWebhook(req.params.pk, req.body, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery); + const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery, { + role: req.role, + }); return res.json({ data: item || null }); }) ); router.delete( '/:pk', - useCollection('directus_webhooks'), asyncHandler(async (req, res) => { await WebhooksService.deleteWebhook(req.params.pk, { + role: req.role, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/services/activity.ts b/src/services/activity.ts index f601468b26..28b875066d 100644 --- a/src/services/activity.ts +++ b/src/services/activity.ts @@ -16,12 +16,16 @@ export const createActivity = async (data: Record) => { return await ItemsService.createItem('directus_activity', data); }; -export const readActivities = async (query?: Query) => { - // return await ItemsService.readItems('directus_activity', query); +export const readActivities = async (query?: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_activity', query, accountability); }; -export const readActivity = async (pk: string | number, query?: Query) => { - return await ItemsService.readItem('directus_activity', pk, query); +export const readActivity = async ( + pk: string | number, + query?: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_activity', pk, query, accountability); }; export const updateActivity = async ( diff --git a/src/services/collections.ts b/src/services/collections.ts index 30ebf4410b..19a255305d 100644 --- a/src/services/collections.ts +++ b/src/services/collections.ts @@ -70,34 +70,43 @@ export const create = async (payload: any, accountability: Accountability) => { return await ItemsService.readItem('directus_collections', primaryKey); }; -export const readAll = async (query?: Query) => { +export const readAll = async (query?: Query, accountability?: Accountability) => { const [tables, collections] = await Promise.all([ schemaInspector.tableInfo(), - // ItemsService.readItems('directus_collections', query), + ItemsService.readItems('directus_collections', query, accountability), ]); - // const data = tables.map((table) => { - // const collectionInfo = collections.find((collection) => { - // return collection.collection === table.name; - // }); + const data = tables.map((table) => { + const collectionInfo = collections.find((collection) => { + return collection.collection === table.name; + }); - // return { - // collection: table.name, - // note: table.comment, - // hidden: collectionInfo?.hidden || false, - // single: collectionInfo?.single || false, - // icon: collectionInfo?.icon || null, - // translation: collectionInfo?.translation || null, - // }; - // }); + return { + collection: table.name, + note: table.comment, + hidden: collectionInfo?.hidden || false, + single: collectionInfo?.single || false, + icon: collectionInfo?.icon || null, + translation: collectionInfo?.translation || null, + }; + }); - // return data; + return data; }; -export const readOne = async (collection: string, query?: Query) => { +export const readOne = async ( + collection: string, + query?: Query, + accountability?: Accountability +) => { const [table, collectionInfo] = await Promise.all([ schemaInspector.tableInfo(collection), - ItemsService.readItem('directus_collections', collection, query), + ItemsService.readItem( + 'directus_collections', + collection, + query, + accountability + ), ]); return { diff --git a/src/services/fields.ts b/src/services/fields.ts index ce0f0f784f..df39365979 100644 --- a/src/services/fields.ts +++ b/src/services/fields.ts @@ -13,6 +13,11 @@ export const fieldsInCollection = async (collection: string) => { return uniq([...fields.map(({ field }) => field), ...columns.map(({ column }) => column)]); }; +/** + * @TODO + * update read to use ItemsService instead of direct to db + */ + export const readAll = async (collection?: string) => { const fieldsQuery = database.select('*').from('directus_fields'); diff --git a/src/services/files.ts b/src/services/files.ts index e65c1f209e..9062c46dd7 100644 --- a/src/services/files.ts +++ b/src/services/files.ts @@ -56,12 +56,16 @@ export const createFile = async ( return await ItemsService.createItem('directus_files', payload, accountability); }; -export const readFiles = async (query: Query) => { - // return await ItemsService.readItems('directus_files', query); +export const readFiles = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_files', query, accountability); }; -export const readFile = async (pk: string | number, query: Query) => { - return await ItemsService.readItem('directus_files', pk, query); +export const readFile = async ( + pk: string | number, + query: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_files', pk, query, accountability); }; export const updateFile = async ( diff --git a/src/services/folders.ts b/src/services/folders.ts index b3f9a9bd6a..66887d0432 100644 --- a/src/services/folders.ts +++ b/src/services/folders.ts @@ -8,12 +8,12 @@ export const createFolder = async ( return (await ItemsService.createItem('directus_folders', data, accountability)) as string; }; -export const readFolders = async (query: Query) => { - // return await ItemsService.readItems('directus_folders', query); +export const readFolders = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_folders', query, accountability); }; -export const readFolder = async (pk: string, query: Query) => { - return await ItemsService.readItem('directus_folders', pk, query); +export const readFolder = async (pk: string, query: Query, accountability?: Accountability) => { + return await ItemsService.readItem('directus_folders', pk, query, accountability); }; export const updateFolder = async ( diff --git a/src/services/items.ts b/src/services/items.ts index 9ee1f6f82c..efc1e1e0f4 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -1,10 +1,11 @@ import database, { schemaInspector } from '../database'; import { Query } from '../types/query'; import runAST from '../database/run-ast'; -import getAST from '../utils/get-ast-from-query'; -import * as PayloadService from './payload'; +import getASTFromQuery from '../utils/get-ast-from-query'; import { Accountability, AST } from '../types'; +import * as PayloadService from './payload'; +import * as PermissionsService from './permissions'; import * as ActivityService from './activity'; import * as RevisionsService from './revisions'; @@ -82,8 +83,15 @@ export const createItem = async ( export const readItems = async >( collection: string, - ast: AST + query: Query, + accountability?: Accountability ): Promise => { + let ast = await getASTFromQuery(collection, query, accountability?.role); + + if (accountability) { + ast = await PermissionsService.processAST(ast, accountability.role); + } + const records = await runAST(ast); return await PayloadService.processValues('read', collection, records); }; @@ -91,7 +99,8 @@ export const readItems = async >( export const readItem = async ( collection: string, pk: number | string, - query: Query = {} + query: Query = {}, + accountability?: Accountability ): Promise => { const primaryKeyField = await schemaInspector.primary(collection); @@ -105,10 +114,14 @@ export const readItem = async ( }, }; - // const ast = await getAST(collection, query); - // const records = await runAST(ast); - // return await PayloadService.processValues('read', collection, records[0]); - return; + let ast = await getASTFromQuery(collection, query, accountability?.role); + + if (accountability) { + ast = await PermissionsService.processAST(ast, accountability.role); + } + + const records = await runAST(ast); + return await PayloadService.processValues('read', collection, records[0]); }; export const updateItem = async ( @@ -172,10 +185,14 @@ export const deleteItem = async ( .where({ [primaryKeyField]: pk }); }; -export const readSingleton = async (collection: string, ast: AST) => { - ast.query.limit = 1; +export const readSingleton = async ( + collection: string, + query: Query, + accountability?: Accountability +) => { + query.limit = 1; - const records = await readItems(collection, ast); + const records = await readItems(collection, query, accountability); const record = records[0]; if (!record) { diff --git a/src/services/permissions.ts b/src/services/permissions.ts index 1982fd6b2f..37f8571fd9 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -1,13 +1,4 @@ -import { - Accountability, - AST, - NestedCollectionAST, - FieldAST, - Operation, - Query, - Permission, - Relation, -} from '../types'; +import { Accountability, AST, NestedCollectionAST, FieldAST, Query, Permission } from '../types'; import * as ItemsService from './items'; import database from '../database'; import { ForbiddenException } from '../exceptions'; @@ -20,12 +11,12 @@ export const createPermission = async ( return (await ItemsService.createItem('directus_permissions', data, accountability)) as number; }; -export const readPermissions = async (query: Query) => { - // return await ItemsService.readItems('directus_permissions', query); +export const readPermissions = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_permissions', query, accountability); }; -export const readPermission = async (pk: number, query: Query) => { - return await ItemsService.readItem('directus_permissions', pk, query); +export const readPermission = async (pk: number, query: Query, accountability?: Accountability) => { + return await ItemsService.readItem('directus_permissions', pk, query, accountability); }; export const updatePermission = async ( @@ -45,41 +36,17 @@ export const deletePermission = async (pk: number, accountability: Accountabilit await ItemsService.deleteItem('directus_permissions', pk, accountability); }; -export const authorize = async (operation: Operation, collection: string, role?: string) => { - const query: Query = { - filter: { - collection: { - _eq: collection, - }, - operation: { - _eq: operation, - }, - }, - limit: 1, - }; - - if (role) { - query.filter.role = { - _eq: role, - }; - } - - // const records = await ItemsService.readItems('directus_permissions', query); - - // return records[0]; -}; - -export const processAST = async (role: string | null, ast: AST): Promise => { +export const processAST = async (ast: AST, role: string | null): Promise => { const collectionsRequested = getCollectionsFromAST(ast); const permissionsForCollections = await database .select('*') .from('directus_permissions') + .where({ operation: 'read', role }) .whereIn( 'collection', collectionsRequested.map(({ collection }) => collection) - ) - .andWhere('role', role); + ); // If the permissions don't match the collections, you don't have permission to read all of them const uniqueCollectionsRequestedCount = uniq( diff --git a/src/services/presets.ts b/src/services/presets.ts index 8012bec5c8..86a7bbff86 100644 --- a/src/services/presets.ts +++ b/src/services/presets.ts @@ -10,12 +10,16 @@ export const createCollectionPreset = async ( return await ItemsService.createItem('directus_presets', data, accountability); }; -export const readCollectionPresets = async (query: Query) => { - // return await ItemsService.readItems('directus_presets', query); +export const readCollectionPresets = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_presets', query, accountability); }; -export const readCollectionPreset = async (pk: string | number, query: Query) => { - return await ItemsService.readItem('directus_presets', pk, query); +export const readCollectionPreset = async ( + pk: string | number, + query: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_presets', pk, query, accountability); }; export const updateCollectionPreset = async ( diff --git a/src/services/relations.ts b/src/services/relations.ts index 8ff4acb432..1a9c0dbfc5 100644 --- a/src/services/relations.ts +++ b/src/services/relations.ts @@ -5,12 +5,16 @@ export const createRelation = async (data: Record, accountability: return await ItemsService.createItem('directus_relations', data, accountability); }; -export const readRelations = async (query: Query) => { - // return await ItemsService.readItems('directus_relations', query); +export const readRelations = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_relations', query, accountability); }; -export const readRelation = async (pk: string | number, query: Query) => { - return await ItemsService.readItem('directus_relations', pk, query); +export const readRelation = async ( + pk: string | number, + query: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_relations', pk, query, accountability); }; export const updateRelation = async ( diff --git a/src/services/revisions.ts b/src/services/revisions.ts index 9c425ae231..0d6a7b4538 100644 --- a/src/services/revisions.ts +++ b/src/services/revisions.ts @@ -1,14 +1,18 @@ -import { Query } from '../types/query'; import * as ItemsService from './items'; +import { Accountability, Query } from '../types'; export const createRevision = async (data: Record) => { return await ItemsService.createItem('directus_revisions', data); }; -export const readRevisions = async (query: Query) => { - // return await ItemsService.readItems('directus_revisions', query); +export const readRevisions = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_revisions', query, accountability); }; -export const readRevision = async (pk: string | number, query: Query) => { - return await ItemsService.readItem('directus_revisions', pk, query); +export const readRevision = async ( + pk: string | number, + query: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_revisions', pk, query, accountability); }; diff --git a/src/services/roles.ts b/src/services/roles.ts index cc357c8dd2..5558421424 100644 --- a/src/services/roles.ts +++ b/src/services/roles.ts @@ -5,12 +5,16 @@ export const createRole = async (data: Record, accountability: Acco return await ItemsService.createItem('directus_roles', data, accountability); }; -export const readRoles = async (query: Query) => { - // return await ItemsService.readItems('directus_roles', query); +export const readRoles = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_roles', query, accountability); }; -export const readRole = async (pk: string | number, query: Query) => { - return await ItemsService.readItem('directus_roles', pk, query); +export const readRole = async ( + pk: string | number, + query: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_roles', pk, query, accountability); }; export const updateRole = async ( diff --git a/src/services/settings.ts b/src/services/settings.ts index 06198767cf..decb16dffa 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -2,8 +2,8 @@ import { Query } from '../types/query'; import * as ItemsService from './items'; import { Accountability } from '../types'; -export const readSettings = async (query: Query) => { - // return await ItemsService.readSingleton('directus_settings', query); +export const readSettings = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readSingleton('directus_settings', query, accountability); }; export const updateSettings = async (data: Record, accountability: Accountability) => { diff --git a/src/services/users.ts b/src/services/users.ts index ddce886f5d..d504ff7c9b 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -10,12 +10,16 @@ export const createUser = async (data: Record, accountability: Acco return await ItemsService.createItem('directus_users', data, accountability); }; -export const readUsers = async (query?: Query) => { - // return await ItemsService.readItems('directus_users', query); +export const readUsers = async (query?: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_users', query, accountability); }; -export const readUser = async (pk: string | number, query?: Query) => { - return await ItemsService.readItem('directus_users', pk, query); +export const readUser = async ( + pk: string | number, + query?: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_users', pk, query, accountability); }; export const updateUser = async ( diff --git a/src/services/webhooks.ts b/src/services/webhooks.ts index c7e9d80b29..5e7476b0ed 100644 --- a/src/services/webhooks.ts +++ b/src/services/webhooks.ts @@ -5,12 +5,16 @@ export const createWebhook = async (data: Record, accountability: A return await ItemsService.createItem('directus_webhooks', data, accountability); }; -export const readWebhooks = async (query: Query) => { - // return await ItemsService.readItems('directus_webhooks', query); +export const readWebhooks = async (query: Query, accountability?: Accountability) => { + return await ItemsService.readItems('directus_webhooks', query, accountability); }; -export const readWebhook = async (pk: string | number, query: Query) => { - return await ItemsService.readItem('directus_webhooks', pk, query); +export const readWebhook = async ( + pk: string | number, + query: Query, + accountability?: Accountability +) => { + return await ItemsService.readItem('directus_webhooks', pk, query, accountability); }; export const updateWebhook = async ( diff --git a/src/types/accountability.ts b/src/types/accountability.ts index 8bdebbea82..e30a3efa6b 100644 --- a/src/types/accountability.ts +++ b/src/types/accountability.ts @@ -1,7 +1,8 @@ export type Accountability = { + role: string; + user?: string; ip?: string; userAgent?: string; - user?: string; parent?: number; }; diff --git a/src/utils/get-ast-from-query.ts b/src/utils/get-ast-from-query.ts index eec6fcb6b1..644d94f64d 100644 --- a/src/utils/get-ast-from-query.ts +++ b/src/utils/get-ast-from-query.ts @@ -4,23 +4,27 @@ import { Relation } from '../types/relation'; import { AST, NestedCollectionAST, FieldAST, Query } from '../types'; -import database, { schemaInspector } from '../database'; +import database from '../database'; export default async function getASTFromQuery( - role: string | null, collection: string, - query: Query + query: Query, + role?: string | null ): Promise { /** * we might not need al this info at all times, but it's easier to fetch it all once, than trying to fetch it for every * requested field. @todo look into utilizing graphql/dataloader for this purpose */ - const permissions = await database - .select<{ collection: string; fields: string }[]>('collection', 'fields') - .from('directus_permissions') - .where({ role, operation: 'read' }); const relations = await database.select('*').from('directus_relations'); + const permissions = + role !== undefined + ? await database + .select<{ collection: string; fields: string }[]>('collection', 'fields') + .from('directus_permissions') + .where({ role, operation: 'read' }) + : null; + const ast: AST = { type: 'collection', name: collection, @@ -39,8 +43,10 @@ export default async function getASTFromQuery( function convertWildcards(parentCollection: string, fields: string[]) { const allowedFields = permissions - .find((permission) => parentCollection === permission.collection) - ?.fields?.split(','); + ? permissions + .find((permission) => parentCollection === permission.collection) + ?.fields?.split(',') + : ['*']; if (!allowedFields || allowedFields.length === 0) return []; From 699014e715434769f78eb9cd29c0304b6ef220c1 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 15 Jul 2020 13:15:39 -0400 Subject: [PATCH 15/18] Add limit permissions --- src/database/run-ast.ts | 58 ++++++++++++++++++++++---------- src/middleware/sanitize-query.ts | 5 ++- src/services/permissions.ts | 11 ++++++ 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/database/run-ast.ts b/src/database/run-ast.ts index b43ee7a1c0..f5a55a7bed 100644 --- a/src/database/run-ast.ts +++ b/src/database/run-ast.ts @@ -39,6 +39,10 @@ export default async function runAST(ast: AST, query = ast.query) { let dbQuery = database.select([...toplevelFields, ...tempFields]).from(ast.name); + // Query defaults + query.limit = query.limit || 100; + query.sort = query.sort || [{ column: primaryKeyField, order: 'asc' }]; + if (query.filter) { applyFilter(dbQuery, query.filter); } @@ -88,6 +92,7 @@ export default async function runAST(ast: AST, query = ast.query) { let batchQuery: Query = {}; let tempField: string = null; + let tempLimit: number = null; if (m2o) { // Make sure we always fetch the nested items primary key field to ensure we have the key to match the item by @@ -131,6 +136,17 @@ export default async function runAST(ast: AST, query = ast.query) { }, }, }; + + /** + * The nested queries are done with a WHERE m2o IN (pk, pk, pk) query. We have to remove + * LIMIT from that equation to ensure we limit `n` items _per parent record_ instead of + * `n` items in total. This limit will then be re-applied in the stitching process + * down below + */ + if (batchQuery.limit) { + tempLimit = batchQuery.limit; + delete batchQuery.limit; + } } const nestedResults = await runAST(batch, batchQuery); @@ -157,26 +173,32 @@ export default async function runAST(ast: AST, query = ast.query) { } // o2m + let resultsForCurrentRecord = nestedResults + .filter((nestedRecord) => { + return ( + nestedRecord[batch.relation.field_many] === + record[batch.relation.primary_one] || + // In case of nested object: + nestedRecord[batch.relation.field_many]?.[batch.relation.primary_many] === + record[batch.relation.primary_one] + ); + }) + .map((nestedRecord) => { + if (tempField) { + delete nestedRecord[tempField]; + } + + return nestedRecord; + }); + + // Reapply LIMIT query on a per-record basis + if (tempLimit) { + resultsForCurrentRecord = resultsForCurrentRecord.slice(0, tempLimit); + } + const newRecord = { ...record, - [batch.fieldKey]: nestedResults - .filter((nestedRecord) => { - /** - * @todo - * pull the name ID from somewhere real - */ - return ( - nestedRecord[batch.relation.field_many] === record.id || - nestedRecord[batch.relation.field_many]?.id === record.id - ); - }) - .map((nestedRecord) => { - if (tempField) { - delete nestedRecord[tempField]; - } - - return nestedRecord; - }), + [batch.fieldKey]: resultsForCurrentRecord, }; return newRecord; diff --git a/src/middleware/sanitize-query.ts b/src/middleware/sanitize-query.ts index fd1408dd46..fa8023c432 100644 --- a/src/middleware/sanitize-query.ts +++ b/src/middleware/sanitize-query.ts @@ -13,9 +13,12 @@ const sanitizeQuery: RequestHandler = (req, res, next) => { const query: Query = { fields: sanitizeFields(req.query.fields) || ['*'], - limit: sanitizeLimit(req.query.limit) || 100, }; + if (req.query.limit) { + query.limit = sanitizeLimit(req.query.limit); + } + if (req.query.sort) { query.sort = sanitizeSort(req.query.sort); } diff --git a/src/services/permissions.ts b/src/services/permissions.ts index 37f8571fd9..d769c1eb01 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -148,6 +148,17 @@ export const processAST = async (ast: AST, role: string | null): Promise => }, }; + if (permissions.limit && ast.query.limit > permissions.limit) { + throw new ForbiddenException( + `You can't read more than ${permissions.limit} items at a time.` + ); + } + + // Default to the permissions limit if limit hasn't been set + if (permissions.limit && !ast.query.limit) { + ast.query.limit = permissions.limit; + } + ast.children = ast.children.map(applyFilters) as (NestedCollectionAST | FieldAST)[]; } From 55332f72d597c8bf7cab0648ffdd425dff243b48 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 15 Jul 2020 15:01:09 -0400 Subject: [PATCH 16/18] Add payload validation on create / validate --- src/services/items.ts | 28 +++++++++++++++--- src/services/permissions.ts | 59 +++++++++++++++++++++++++++++++++++-- src/utils/generate-joi.ts | 44 +++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 src/utils/generate-joi.ts diff --git a/src/services/items.ts b/src/services/items.ts index efc1e1e0f4..320457de92 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -44,15 +44,24 @@ export const createItem = async ( data: Record, accountability?: Accountability ): Promise => { - let payload = await PayloadService.processValues('create', collection, data); + let payload = data; + + if (accountability) { + payload = await PermissionsService.processValues( + 'create', + collection, + accountability?.role, + data + ); + } + + payload = await PayloadService.processValues('create', collection, payload); payload = await PayloadService.processM2O(collection, payload); const primaryKeyField = await schemaInspector.primary(collection); - // Only insert the values that actually save to an existing column. This ensures we ignore aliases etc const columns = await schemaInspector.columns(collection); - const payloadWithoutAlias = pick( payload, columns.map(({ column }) => column) @@ -130,7 +139,18 @@ export const updateItem = async ( data: Record, accountability?: Accountability ): Promise => { - let payload = await PayloadService.processValues('update', collection, data); + let payload = data; + + if (accountability) { + payload = await PermissionsService.processValues( + 'validate', + collection, + accountability?.role, + data + ); + } + + payload = await PayloadService.processValues('update', collection, data); payload = await PayloadService.processM2O(collection, payload); diff --git a/src/services/permissions.ts b/src/services/permissions.ts index d769c1eb01..b6891ef442 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -1,8 +1,18 @@ -import { Accountability, AST, NestedCollectionAST, FieldAST, Query, Permission } from '../types'; +import { + Accountability, + AST, + NestedCollectionAST, + FieldAST, + Query, + Permission, + Operation, +} from '../types'; import * as ItemsService from './items'; import database from '../database'; -import { ForbiddenException } from '../exceptions'; +import { ForbiddenException, InvalidPayloadException } from '../exceptions'; import { uniq } from 'lodash'; +import generateJoi from '../utils/generate-joi'; +import Joi from '@hapi/joi'; export const createPermission = async ( data: Record, @@ -165,3 +175,48 @@ export const processAST = async (ast: AST, role: string | null): Promise => return ast; } }; + +export const processValues = async ( + operation: Operation, + collection: string, + role: string | null, + data: Record +) => { + const permission = await database + .select('*') + .from('directus_permissions') + .where({ operation, collection, role }) + .first(); + + if (!permission) throw new ForbiddenException(); + + const allowedFields = permission.fields.split(','); + + if (allowedFields.includes('*') === false) { + const keysInData = Object.keys(data); + const invalidKeys = keysInData.filter( + (fieldKey) => allowedFields.includes(fieldKey) === false + ); + + if (invalidKeys.length > 0) { + throw new InvalidPayloadException(`Field "${invalidKeys[0]}" doesn't exist.`); + } + } + + const preset = permission.presets || {}; + + const payload = { + ...preset, + ...data, + }; + + const schema = generateJoi(permission.permissions); + + const { error } = schema.validate(payload); + + if (error) { + throw new InvalidPayloadException(error.message); + } + + return payload; +}; diff --git a/src/utils/generate-joi.ts b/src/utils/generate-joi.ts new file mode 100644 index 0000000000..f76ad3ac2b --- /dev/null +++ b/src/utils/generate-joi.ts @@ -0,0 +1,44 @@ +import { Filter } from '../types'; +import Joi, { AnySchema } from '@hapi/joi'; + +export default function generateJoi(filter: Filter) { + const schema: Record = {}; + + for (const [key, value] of Object.entries(filter)) { + const isField = key.startsWith('_') === false; + + if (isField) { + const operator = Object.keys(value)[0]; + + /** @TODO + * - Extend with all operators + */ + + if (operator === '_eq') { + schema[key] = Joi.any().equal(Object.values(value)[0]); + } + + if (operator === '_neq') { + schema[key] = Joi.any().not(Object.values(value)[0]); + } + + if (operator === '_in') { + schema[key] = Joi.any().equal(...(Object.values(value)[0] as (string | number)[])); + } + + if (operator === '_nin') { + schema[key] = Joi.any().not(...(Object.values(value)[0] as (string | number)[])); + } + + if (operator === '_gt') { + schema[key] = Joi.number().greater(Number(Object.values(value)[0])); + } + + if (operator === '_lt') { + schema[key] = Joi.number().less(Number(Object.values(value)[0])); + } + } + } + + return Joi.object(schema).unknown(); +} From 046345c1e82922dc045fb06b795ab4756ea56e47 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 15 Jul 2020 15:44:25 -0400 Subject: [PATCH 17/18] Add dynamic permissions check for update / delete --- src/services/items.ts | 42 ++++++++++++++++++++------------- src/services/permissions.ts | 28 +++++++++++++++++++--- src/utils/get-ast-from-query.ts | 6 ++--- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/services/items.ts b/src/services/items.ts index 320457de92..5ff3080035 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -2,7 +2,7 @@ import database, { schemaInspector } from '../database'; import { Query } from '../types/query'; import runAST from '../database/run-ast'; import getASTFromQuery from '../utils/get-ast-from-query'; -import { Accountability, AST } from '../types'; +import { Accountability, Operation } from '../types'; import * as PayloadService from './payload'; import * as PermissionsService from './permissions'; @@ -28,15 +28,17 @@ async function saveActivityAndRevision( action_by: accountability.user, }); - await RevisionsService.createRevision({ - activity: activityID, - collection, - item, - delta: payload, - /** @todo make this configurable */ - data: await readItem(collection, item, { fields: ['*'] }), - parent: accountability.parent, - }); + if (action !== ActivityService.Action.DELETE) { + await RevisionsService.createRevision({ + activity: activityID, + collection, + item, + delta: payload, + /** @todo make this configurable */ + data: await readItem(collection, item, { fields: ['*'] }), + parent: accountability.parent, + }); + } } export const createItem = async ( @@ -109,8 +111,13 @@ export const readItem = async ( collection: string, pk: number | string, query: Query = {}, - accountability?: Accountability + accountability?: Accountability, + operation?: Operation ): Promise => { + // We allow overriding the operation, so we can use the item read logic to validate permissions + // for update and delete as well + operation = operation || 'read'; + const primaryKeyField = await schemaInspector.primary(collection); query = { @@ -123,10 +130,10 @@ export const readItem = async ( }, }; - let ast = await getASTFromQuery(collection, query, accountability?.role); + let ast = await getASTFromQuery(collection, query, accountability?.role, operation); if (accountability) { - ast = await PermissionsService.processAST(ast, accountability.role); + ast = await PermissionsService.processAST(ast, accountability.role, operation); } const records = await runAST(ast); @@ -142,16 +149,17 @@ export const updateItem = async ( let payload = data; if (accountability) { + await PermissionsService.checkAccess('update', collection, pk, accountability.role); + payload = await PermissionsService.processValues( 'validate', collection, - accountability?.role, + accountability.role, data ); } - payload = await PayloadService.processValues('update', collection, data); - + payload = await PayloadService.processValues('update', collection, payload); payload = await PayloadService.processM2O(collection, payload); const primaryKeyField = await schemaInspector.primary(collection); @@ -190,6 +198,8 @@ export const deleteItem = async ( const primaryKeyField = await schemaInspector.primary(collection); if (accountability) { + await PermissionsService.checkAccess('delete', collection, pk, accountability.role); + // Don't await this. It can run async in the background saveActivityAndRevision( ActivityService.Action.DELETE, diff --git a/src/services/permissions.ts b/src/services/permissions.ts index b6891ef442..0e96fac0e0 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -12,7 +12,6 @@ import database from '../database'; import { ForbiddenException, InvalidPayloadException } from '../exceptions'; import { uniq } from 'lodash'; import generateJoi from '../utils/generate-joi'; -import Joi from '@hapi/joi'; export const createPermission = async ( data: Record, @@ -46,13 +45,17 @@ export const deletePermission = async (pk: number, accountability: Accountabilit await ItemsService.deleteItem('directus_permissions', pk, accountability); }; -export const processAST = async (ast: AST, role: string | null): Promise => { +export const processAST = async ( + ast: AST, + role: string | null, + operation: Operation = 'read' +): Promise => { const collectionsRequested = getCollectionsFromAST(ast); const permissionsForCollections = await database .select('*') .from('directus_permissions') - .where({ operation: 'read', role }) + .where({ operation, role }) .whereIn( 'collection', collectionsRequested.map(({ collection }) => collection) @@ -220,3 +223,22 @@ export const processValues = async ( return payload; }; + +export const checkAccess = async ( + operation: Operation, + collection: string, + pk: string | number, + role: string +) => { + try { + const query: Query = { + fields: ['*'], + }; + + const result = await ItemsService.readItem(collection, pk, query, { role }, operation); + + if (!result) throw ''; + } catch { + throw new ForbiddenException(`You're not allowed to ${operation} this item.`); + } +}; diff --git a/src/utils/get-ast-from-query.ts b/src/utils/get-ast-from-query.ts index 644d94f64d..104b1fbea2 100644 --- a/src/utils/get-ast-from-query.ts +++ b/src/utils/get-ast-from-query.ts @@ -2,14 +2,14 @@ * Generate an AST based on a given collection and query */ -import { Relation } from '../types/relation'; -import { AST, NestedCollectionAST, FieldAST, Query } from '../types'; +import { AST, NestedCollectionAST, FieldAST, Query, Relation, Operation } from '../types'; import database from '../database'; export default async function getASTFromQuery( collection: string, query: Query, - role?: string | null + role?: string | null, + operation?: Operation ): Promise { /** * we might not need al this info at all times, but it's easier to fetch it all once, than trying to fetch it for every From ca1152b176d45111224dacbd836915a259e9fbcc Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 15 Jul 2020 16:56:55 -0400 Subject: [PATCH 18/18] Ignore permissions for admin users --- src/routes/activity.ts | 6 ++++++ src/routes/collections.ts | 5 ++++- src/routes/fields.ts | 1 + src/routes/files.ts | 17 +++++++++++++++-- src/routes/folders.ts | 11 ++++++++++- src/routes/items.ts | 19 ++++++++++++++++--- src/routes/permissions.ts | 8 +++++++- src/routes/presets.ts | 10 +++++++--- src/routes/relations.ts | 7 +++++++ src/routes/revisions.ts | 2 ++ src/routes/roles.ts | 11 ++++++++++- src/routes/settings.ts | 11 +++++++++-- src/routes/users.ts | 19 +++++++++++++++++-- src/routes/webhooks.ts | 11 ++++++++++- src/services/items.ts | 14 +++++++------- src/types/accountability.ts | 2 ++ src/utils/get-ast-from-query.ts | 16 ++++++++++++---- 17 files changed, 142 insertions(+), 28 deletions(-) diff --git a/src/routes/activity.ts b/src/routes/activity.ts index d73fd3d126..0c578df5a9 100644 --- a/src/routes/activity.ts +++ b/src/routes/activity.ts @@ -13,6 +13,7 @@ router.get( asyncHandler(async (req, res) => { const records = await ActivityService.readActivities(req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ @@ -28,6 +29,7 @@ router.get( asyncHandler(async (req, res) => { const record = await ActivityService.readActivity(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ @@ -51,6 +53,7 @@ router.post( const record = await ActivityService.readActivity(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ @@ -66,6 +69,7 @@ router.patch( asyncHandler(async (req, res) => { const primaryKey = await ActivityService.updateActivity(req.params.pk, req.body, { role: req.role, + admin: req.admin, user: req.user, ip: req.ip, userAgent: req.get('user-agent'), @@ -73,6 +77,7 @@ router.patch( const record = await ActivityService.readActivity(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ @@ -87,6 +92,7 @@ router.delete( asyncHandler(async (req, res) => { await ActivityService.deleteActivity(req.params.pk, { role: req.role, + admin: req.admin, user: req.user, ip: req.ip, userAgent: req.get('user-agent'), diff --git a/src/routes/collections.ts b/src/routes/collections.ts index ee4d2b0cb8..087050687a 100644 --- a/src/routes/collections.ts +++ b/src/routes/collections.ts @@ -32,6 +32,7 @@ router.post( const createdCollection = await CollectionsService.create(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -48,6 +49,7 @@ router.get( asyncHandler(async (req, res) => { const collections = await CollectionsService.readAll(req.sanitizedQuery, { role: req.role, + admin: req.admin, }); res.json({ data: collections || null }); }) @@ -65,7 +67,7 @@ router.get( const collection = await CollectionsService.readOne( req.params.collection, req.sanitizedQuery, - { role: req.role } + { role: req.role, admin: req.admin } ); res.json({ data: collection || null }); }) @@ -81,6 +83,7 @@ router.delete( await CollectionsService.deleteCollection(req.params.collection, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/fields.ts b/src/routes/fields.ts index 768a97d48f..e34578219f 100644 --- a/src/routes/fields.ts +++ b/src/routes/fields.ts @@ -74,6 +74,7 @@ router.post( const createdField = await FieldsService.createField(req.collection, field, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/files.ts b/src/routes/files.ts index 427db82d33..8e5afd2556 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -57,12 +57,14 @@ const multipartHandler = (operation: 'create' | 'update') => if (operation === 'create') { const pk = await FilesService.createFile(payload, fileStream, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const file = await FilesService.readFile(pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); savedFiles.push(file); @@ -72,6 +74,7 @@ const multipartHandler = (operation: 'create' | 'update') => payload, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -80,6 +83,7 @@ const multipartHandler = (operation: 'create' | 'update') => ); const file = await FilesService.readFile(pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); savedFiles.push(file); @@ -106,7 +110,10 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - const records = await FilesService.readFiles(req.sanitizedQuery, { role: req.role }); + const records = await FilesService.readFiles(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: records || null }); }) ); @@ -117,6 +124,7 @@ router.get( asyncHandler(async (req, res) => { const record = await FilesService.readFile(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) @@ -133,11 +141,15 @@ router.patch( } else { const pk = await FilesService.updateFile(req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - file = await FilesService.readFile(pk, req.sanitizedQuery, { role: req.role }); + file = await FilesService.readFile(pk, req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); } return res.status(200).json({ data: file || null }); @@ -149,6 +161,7 @@ router.delete( asyncHandler(async (req, res) => { await FilesService.deleteFile(req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/folders.ts b/src/routes/folders.ts index 1d2873fa5b..905a5f94f1 100644 --- a/src/routes/folders.ts +++ b/src/routes/folders.ts @@ -13,6 +13,7 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await FoldersService.createFolder(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -20,6 +21,7 @@ router.post( const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) @@ -29,7 +31,10 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - const records = await FoldersService.readFolders(req.sanitizedQuery, { role: req.role }); + const records = await FoldersService.readFolders(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: records || null }); }) ); @@ -40,6 +45,7 @@ router.get( asyncHandler(async (req, res) => { const record = await FoldersService.readFolder(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) @@ -51,6 +57,7 @@ router.patch( asyncHandler(async (req, res) => { const primaryKey = await FoldersService.updateFolder(req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -58,6 +65,7 @@ router.patch( const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); @@ -69,6 +77,7 @@ router.delete( asyncHandler(async (req, res) => { await FoldersService.deleteFolder(req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/items.ts b/src/routes/items.ts index e761e7c424..ec9ac38391 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -20,12 +20,14 @@ router.post( const primaryKey = await ItemsService.createItem(req.collection, req.body, { user: req.user, role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), }); const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); res.json({ data: item || null }); @@ -39,8 +41,14 @@ router.get( asyncHandler(async (req, res) => { const [records, meta] = await Promise.all([ req.single - ? ItemsService.readSingleton(req.collection, req.sanitizedQuery, { role: req.role }) - : ItemsService.readItems(req.collection, req.sanitizedQuery, { role: req.role }), + ? ItemsService.readSingleton(req.collection, req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }) + : ItemsService.readItems(req.collection, req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }), MetaService.getMetaForQuery(req.collection, req.sanitizedQuery), ]); @@ -64,7 +72,7 @@ router.get( req.collection, req.params.pk, req.sanitizedQuery, - { role: req.role } + { role: req.role, admin: req.admin } ); return res.json({ @@ -84,6 +92,7 @@ router.patch( await ItemsService.upsertSingleton(req.collection, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -91,6 +100,7 @@ router.patch( const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); @@ -108,6 +118,7 @@ router.patch( const primaryKey = await ItemsService.updateItem(req.collection, req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -115,6 +126,7 @@ router.patch( const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); @@ -127,6 +139,7 @@ router.delete( asyncHandler(async (req, res) => { await ItemsService.deleteItem(req.collection, req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/permissions.ts b/src/routes/permissions.ts index bc3f8a93ed..abccd3b747 100644 --- a/src/routes/permissions.ts +++ b/src/routes/permissions.ts @@ -13,6 +13,7 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await PermissionsService.createPermission(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -20,6 +21,7 @@ router.post( const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); @@ -32,6 +34,7 @@ router.get( asyncHandler(async (req, res) => { const item = await PermissionsService.readPermissions(req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -44,7 +47,7 @@ router.get( const record = await PermissionsService.readPermission( Number(req.params.pk), req.sanitizedQuery, - { role: req.role } + { role: req.role, admin: req.admin } ); return res.json({ data: record || null }); }) @@ -58,6 +61,7 @@ router.patch( req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -66,6 +70,7 @@ router.patch( const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); @@ -77,6 +82,7 @@ router.delete( asyncHandler(async (req, res) => { await PermissionsService.deletePermission(Number(req.params.pk), { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/presets.ts b/src/routes/presets.ts index 4051353d72..ac35a520d6 100644 --- a/src/routes/presets.ts +++ b/src/routes/presets.ts @@ -13,6 +13,7 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await CollectionPresetsService.createCollectionPreset(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -21,7 +22,7 @@ router.post( const record = await CollectionPresetsService.readCollectionPreset( primaryKey, req.sanitizedQuery, - { role: req.role } + { role: req.role, admin: req.admin } ); return res.json({ data: record || null }); }) @@ -33,6 +34,7 @@ router.get( asyncHandler(async (req, res) => { const records = await CollectionPresetsService.readCollectionPresets(req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: records || null }); }) @@ -45,7 +47,7 @@ router.get( const record = await CollectionPresetsService.readCollectionPreset( req.params.pk, req.sanitizedQuery, - { role: req.role } + { role: req.role, admin: req.admin } ); return res.json({ data: record || null }); }) @@ -60,6 +62,7 @@ router.patch( req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -69,7 +72,7 @@ router.patch( const record = await CollectionPresetsService.readCollectionPreset( primaryKey, req.sanitizedQuery, - { role: req.role } + { role: req.role, admin: req.admin } ); return res.json({ data: record || null }); }) @@ -80,6 +83,7 @@ router.delete( asyncHandler(async (req, res) => { await CollectionPresetsService.deleteCollectionPreset(req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/relations.ts b/src/routes/relations.ts index aef2541b25..1839ea5de1 100644 --- a/src/routes/relations.ts +++ b/src/routes/relations.ts @@ -14,12 +14,14 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await RelationsService.createRelation(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -31,6 +33,7 @@ router.get( asyncHandler(async (req, res) => { const records = await RelationsService.readRelations(req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: records || null }); }) @@ -42,6 +45,7 @@ router.get( asyncHandler(async (req, res) => { const record = await RelationsService.readRelation(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) @@ -53,12 +57,14 @@ router.patch( asyncHandler(async (req, res) => { const primaryKey = await RelationsService.updateRelation(req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -69,6 +75,7 @@ router.delete( asyncHandler(async (req, res) => { await RelationsService.deleteRelation(Number(req.params.pk), { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/revisions.ts b/src/routes/revisions.ts index 9e878e29b8..a391ba0cca 100644 --- a/src/routes/revisions.ts +++ b/src/routes/revisions.ts @@ -14,6 +14,7 @@ router.get( asyncHandler(async (req, res) => { const records = await RevisionsService.readRevisions(req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: records || null }); }) @@ -25,6 +26,7 @@ router.get( asyncHandler(async (req, res) => { const record = await RevisionsService.readRevision(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) diff --git a/src/routes/roles.ts b/src/routes/roles.ts index 585189c0f8..67ccd8eb05 100644 --- a/src/routes/roles.ts +++ b/src/routes/roles.ts @@ -14,12 +14,14 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await RolesService.createRole(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await RolesService.readRole(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -29,7 +31,10 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - const records = await RolesService.readRoles(req.sanitizedQuery, { role: req.role }); + const records = await RolesService.readRoles(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: records || null }); }) ); @@ -40,6 +45,7 @@ router.get( asyncHandler(async (req, res) => { const record = await RolesService.readRole(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) @@ -51,12 +57,14 @@ router.patch( asyncHandler(async (req, res) => { const primaryKey = await RolesService.updateRole(req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await RolesService.readRole(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -67,6 +75,7 @@ router.delete( asyncHandler(async (req, res) => { await RolesService.deleteRole(req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/settings.ts b/src/routes/settings.ts index 308475edb7..d6a7568614 100644 --- a/src/routes/settings.ts +++ b/src/routes/settings.ts @@ -11,7 +11,10 @@ router.get( useCollection('directus_settings'), sanitizeQuery, asyncHandler(async (req, res) => { - const records = await SettingsService.readSettings(req.sanitizedQuery, { role: req.role }); + const records = await SettingsService.readSettings(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: records || null }); }) ); @@ -23,12 +26,16 @@ router.patch( asyncHandler(async (req, res) => { await SettingsService.updateSettings(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); - const record = await SettingsService.readSettings(req.sanitizedQuery, { role: req.role }); + const record = await SettingsService.readSettings(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: record || null }); }) diff --git a/src/routes/users.ts b/src/routes/users.ts index 7a4da26ff8..a69d09d9a5 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -16,12 +16,14 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await UsersService.createUser(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await UsersService.readUser(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -31,7 +33,10 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - const item = await UsersService.readUsers(req.sanitizedQuery, { role: req.role }); + const item = await UsersService.readUsers(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: item || null }); }) ); @@ -44,7 +49,10 @@ router.get( throw new InvalidCredentialsException(); } - const item = await UsersService.readUser(req.user, req.sanitizedQuery, { role: req.role }); + const item = await UsersService.readUser(req.user, req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: item || null }); }) ); @@ -55,6 +63,7 @@ router.get( asyncHandler(async (req, res) => { const items = await UsersService.readUser(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: items || null }); }) @@ -70,6 +79,7 @@ router.patch( const primaryKey = await UsersService.updateUser(req.user, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -77,6 +87,7 @@ router.patch( const item = await UsersService.readUser(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -88,12 +99,14 @@ router.patch( asyncHandler(async (req, res) => { const primaryKey = await UsersService.updateUser(req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await UsersService.readUser(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -104,6 +117,7 @@ router.delete( asyncHandler(async (req, res) => { await UsersService.deleteUser(req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -125,6 +139,7 @@ router.post( if (error) throw new InvalidPayloadException(error.message); await UsersService.inviteUser(req.body.email, req.body.role, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/routes/webhooks.ts b/src/routes/webhooks.ts index c6e86520a9..9917a36c17 100644 --- a/src/routes/webhooks.ts +++ b/src/routes/webhooks.ts @@ -13,6 +13,7 @@ router.post( asyncHandler(async (req, res) => { const primaryKey = await WebhooksService.createWebhook(req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, @@ -20,6 +21,7 @@ router.post( const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); @@ -30,7 +32,10 @@ router.get( '/', sanitizeQuery, asyncHandler(async (req, res) => { - const records = await WebhooksService.readWebhooks(req.sanitizedQuery, { role: req.role }); + const records = await WebhooksService.readWebhooks(req.sanitizedQuery, { + role: req.role, + admin: req.admin, + }); return res.json({ data: records || null }); }) ); @@ -41,6 +46,7 @@ router.get( asyncHandler(async (req, res) => { const record = await WebhooksService.readWebhook(req.params.pk, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: record || null }); }) @@ -51,12 +57,14 @@ router.patch( asyncHandler(async (req, res) => { const primaryKey = await WebhooksService.updateWebhook(req.params.pk, req.body, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery, { role: req.role, + admin: req.admin, }); return res.json({ data: item || null }); }) @@ -67,6 +75,7 @@ router.delete( asyncHandler(async (req, res) => { await WebhooksService.deleteWebhook(req.params.pk, { role: req.role, + admin: req.admin, ip: req.ip, userAgent: req.get('user-agent'), user: req.user, diff --git a/src/services/items.ts b/src/services/items.ts index 5ff3080035..5d800c4fd6 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -48,7 +48,7 @@ export const createItem = async ( ): Promise => { let payload = data; - if (accountability) { + if (accountability && accountability.admin === false) { payload = await PermissionsService.processValues( 'create', collection, @@ -97,9 +97,9 @@ export const readItems = async >( query: Query, accountability?: Accountability ): Promise => { - let ast = await getASTFromQuery(collection, query, accountability?.role); + let ast = await getASTFromQuery(collection, query, accountability); - if (accountability) { + if (accountability && accountability.admin === false) { ast = await PermissionsService.processAST(ast, accountability.role); } @@ -130,9 +130,9 @@ export const readItem = async ( }, }; - let ast = await getASTFromQuery(collection, query, accountability?.role, operation); + let ast = await getASTFromQuery(collection, query, accountability, operation); - if (accountability) { + if (accountability && accountability.admin === false) { ast = await PermissionsService.processAST(ast, accountability.role, operation); } @@ -148,7 +148,7 @@ export const updateItem = async ( ): Promise => { let payload = data; - if (accountability) { + if (accountability && accountability.admin === false) { await PermissionsService.checkAccess('update', collection, pk, accountability.role); payload = await PermissionsService.processValues( @@ -197,7 +197,7 @@ export const deleteItem = async ( ) => { const primaryKeyField = await schemaInspector.primary(collection); - if (accountability) { + if (accountability && accountability.admin === false) { await PermissionsService.checkAccess('delete', collection, pk, accountability.role); // Don't await this. It can run async in the background diff --git a/src/types/accountability.ts b/src/types/accountability.ts index e30a3efa6b..2661ac5da6 100644 --- a/src/types/accountability.ts +++ b/src/types/accountability.ts @@ -1,6 +1,8 @@ export type Accountability = { role: string; user?: string; + admin?: boolean; + ip?: string; userAgent?: string; diff --git a/src/utils/get-ast-from-query.ts b/src/utils/get-ast-from-query.ts index 104b1fbea2..112b06d220 100644 --- a/src/utils/get-ast-from-query.ts +++ b/src/utils/get-ast-from-query.ts @@ -2,13 +2,21 @@ * Generate an AST based on a given collection and query */ -import { AST, NestedCollectionAST, FieldAST, Query, Relation, Operation } from '../types'; +import { + AST, + NestedCollectionAST, + FieldAST, + Query, + Relation, + Operation, + Accountability, +} from '../types'; import database from '../database'; export default async function getASTFromQuery( collection: string, query: Query, - role?: string | null, + accountability: Accountability | null, operation?: Operation ): Promise { /** @@ -18,11 +26,11 @@ export default async function getASTFromQuery( const relations = await database.select('*').from('directus_relations'); const permissions = - role !== undefined + accountability && accountability.admin !== true ? await database .select<{ collection: string; fields: string }[]>('collection', 'fields') .from('directus_permissions') - .where({ role, operation: 'read' }) + .where({ role: accountability.role, operation: operation || 'read' }) : null; const ast: AST = {