From 123c7e316c70daf055ca8d41230169deb27373c0 Mon Sep 17 00:00:00 2001 From: Kai Haase Date: Thu, 5 Dec 2024 01:02:48 +0100 Subject: [PATCH] Restriction handling optimized --- package-lock.json | 180 ++++++++---------- package.json | 10 +- spectaql.yml | 2 +- .../common/decorators/restricted.decorator.ts | 38 +++- src/core/common/helpers/db.helper.ts | 6 +- src/core/common/helpers/input.helper.ts | 20 +- .../check-response.interceptor.ts | 14 +- .../check-security.interceptor.ts | 39 +++- .../interfaces/server-options.interface.ts | 29 ++- .../common/models/core-persistence.model.ts | 26 +-- src/core/modules/user/core-user.model.ts | 5 +- src/server/modules/user/user.model.ts | 13 +- 12 files changed, 232 insertions(+), 150 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77eaacc..ed80b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "@lenne.tech/nest-server", - "version": "10.6.0", + "version": "10.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lenne.tech/nest-server", - "version": "10.6.0", + "version": "10.7.0", "license": "MIT", "dependencies": { "@apollo/gateway": "2.9.3", "@getbrevo/brevo": "1.0.1", "@lenne.tech/mongoose-gridfs": "1.4.2", "@lenne.tech/multer-gridfs-storage": "5.0.6", - "@nestjs/apollo": "12.2.1", + "@nestjs/apollo": "12.2.2", "@nestjs/common": "10.4.13", "@nestjs/core": "10.4.13", - "@nestjs/graphql": "12.2.1", + "@nestjs/graphql": "12.2.2", "@nestjs/jwt": "10.2.0", "@nestjs/mongoose": "10.1.0", "@nestjs/passport": "10.0.3", @@ -61,7 +61,7 @@ "@nestjs/schematics": "10.2.3", "@nestjs/testing": "10.4.13", "@swc/cli": "0.5.2", - "@swc/core": "1.9.3", + "@swc/core": "1.10.0", "@swc/jest": "0.2.37", "@types/compression": "1.7.5", "@types/cookie-parser": "1.4.8", @@ -90,7 +90,7 @@ "jest": "29.7.0", "npm-watch": "0.13.0", "pm2": "5.4.3", - "prettier": "3.4.1", + "prettier": "3.4.2", "pretty-quick": "4.0.0", "supertest": "7.0.0", "ts-jest": "29.2.5", @@ -4611,15 +4611,15 @@ } }, "node_modules/@nestjs/apollo": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/apollo/-/apollo-12.2.1.tgz", - "integrity": "sha512-Det66rvMZwXSxwSkMBdTd+jqVyQRDRT+GJh/CU25PR3bM4n7BpdBTzW0XR3Eoi5oyas1YB4cUxa7nR5Iy37lag==", + "version": "12.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/apollo/-/apollo-12.2.2.tgz", + "integrity": "sha512-gsDqSfsmTSvF0k3XaRESRgM3uE/YFO+59txCsq7T1EadDOVOuoF3zVQiFmi6D50Rlnqohqs63qjjf46mgiiXgQ==", "license": "MIT", "dependencies": { "@apollo/server-plugin-landing-page-graphql-playground": "4.0.0", "iterall": "1.3.0", "lodash.omit": "4.5.0", - "tslib": "2.8.0" + "tslib": "2.8.1" }, "peerDependencies": { "@apollo/gateway": "^2.0.0", @@ -4643,12 +4643,6 @@ } } }, - "node_modules/@nestjs/apollo/node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD" - }, "node_modules/@nestjs/cli": { "version": "10.4.8", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.8.tgz", @@ -5023,15 +5017,15 @@ } }, "node_modules/@nestjs/graphql": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/graphql/-/graphql-12.2.1.tgz", - "integrity": "sha512-eXbme7RcecXaz6pZOc3uR9gR7AEAS20BTkzToWab4ExdDJRLhd7ua4C/uNEPUK+82HbNfd3h3z4Mes29N2R+/w==", + "version": "12.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/graphql/-/graphql-12.2.2.tgz", + "integrity": "sha512-lUDy/1uqbRA1kBKpXcmY0aHhcPbfeG52Wg5+9Jzd1d57dwSjCAmuO+mWy5jz9ugopVCZeK0S/kdAMvA+r9fNdA==", "license": "MIT", "dependencies": { - "@graphql-tools/merge": "9.0.8", - "@graphql-tools/schema": "10.0.7", - "@graphql-tools/utils": "10.5.5", - "@nestjs/mapped-types": "2.0.5", + "@graphql-tools/merge": "9.0.11", + "@graphql-tools/schema": "10.0.10", + "@graphql-tools/utils": "10.6.1", + "@nestjs/mapped-types": "2.0.6", "chokidar": "4.0.1", "fast-glob": "3.3.2", "graphql-tag": "2.12.6", @@ -5039,8 +5033,8 @@ "lodash": "4.17.21", "normalize-path": "3.0.0", "subscriptions-transport-ws": "0.11.0", - "tslib": "2.8.0", - "uuid": "10.0.0", + "tslib": "2.8.1", + "uuid": "11.0.3", "ws": "8.18.0" }, "peerDependencies": { @@ -5069,12 +5063,12 @@ } }, "node_modules/@nestjs/graphql/node_modules/@graphql-tools/merge": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.8.tgz", - "integrity": "sha512-RG9NEp4fi0MoFi0te4ahqTMYuavQnXlpEZxxMomdCa6CI5tfekcVm/rsLF5Zt8O4HY+esDt9+4dCL+aOKvG79w==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.11.tgz", + "integrity": "sha512-AJL0XTozn31HoZN8tULzEkbDXyETA5vAFu4Q65kxJDu027p+auaNFYj/y51HP4BhMR4wykoteWyO7/VxKfdpiw==", "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.5.5", + "@graphql-tools/utils": "^10.6.1", "tslib": "^2.4.0" }, "engines": { @@ -5085,13 +5079,13 @@ } }, "node_modules/@nestjs/graphql/node_modules/@graphql-tools/schema": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.7.tgz", - "integrity": "sha512-Cz1o+rf9cd3uMgG+zI9HlM5mPlnHQUlk/UQRZyUlPDfT+944taLaokjvj7AI6GcOFVf4f2D11XthQp+0GY31jQ==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.10.tgz", + "integrity": "sha512-TSdDvwgk1Fq3URDuZBMCPXlWLpRpxwaQ+0KqvycVwoHozYnBRZ2Ql9HVgDKnebkGQKmIk2enSeku+ERKxxSG0g==", "license": "MIT", "dependencies": { - "@graphql-tools/merge": "^9.0.8", - "@graphql-tools/utils": "^10.5.5", + "@graphql-tools/merge": "^9.0.11", + "@graphql-tools/utils": "^10.6.1", "tslib": "^2.4.0", "value-or-promise": "^1.0.12" }, @@ -5103,9 +5097,9 @@ } }, "node_modules/@nestjs/graphql/node_modules/@graphql-tools/utils": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.5.tgz", - "integrity": "sha512-LF/UDWmMT0mnobL2UZETwYghV7HYBzNaGj0SAkCYOMy/C3+6sQdbcTksnoFaKR9XIVD78jNXEGfivbB8Zd+cwA==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.6.1.tgz", + "integrity": "sha512-XHl0/DWkMf/8Dmw1F3RRoMPt6ZwU4J707YWcbPjS+49WZNoTVz6f+prQ4GuwZT8RqTPtrRawnGU93AV73ZLTfQ==", "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", @@ -5148,23 +5142,17 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nestjs/graphql/node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD" - }, "node_modules/@nestjs/graphql/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/@nestjs/jwt": { @@ -5181,9 +5169,9 @@ } }, "node_modules/@nestjs/mapped-types": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", - "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.6.tgz", + "integrity": "sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==", "license": "MIT", "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", @@ -6019,9 +6007,9 @@ } }, "node_modules/@swc/core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.3.tgz", - "integrity": "sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.0.tgz", + "integrity": "sha512-+CuuTCmQFfzaNGg1JmcZvdUVITQXJk9sMnl1C2TiDLzOSVOJRwVD4dNo5dljX/qxpMAN+2BIYlwjlSkoGi6grg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -6037,16 +6025,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.9.3", - "@swc/core-darwin-x64": "1.9.3", - "@swc/core-linux-arm-gnueabihf": "1.9.3", - "@swc/core-linux-arm64-gnu": "1.9.3", - "@swc/core-linux-arm64-musl": "1.9.3", - "@swc/core-linux-x64-gnu": "1.9.3", - "@swc/core-linux-x64-musl": "1.9.3", - "@swc/core-win32-arm64-msvc": "1.9.3", - "@swc/core-win32-ia32-msvc": "1.9.3", - "@swc/core-win32-x64-msvc": "1.9.3" + "@swc/core-darwin-arm64": "1.10.0", + "@swc/core-darwin-x64": "1.10.0", + "@swc/core-linux-arm-gnueabihf": "1.10.0", + "@swc/core-linux-arm64-gnu": "1.10.0", + "@swc/core-linux-arm64-musl": "1.10.0", + "@swc/core-linux-x64-gnu": "1.10.0", + "@swc/core-linux-x64-musl": "1.10.0", + "@swc/core-win32-arm64-msvc": "1.10.0", + "@swc/core-win32-ia32-msvc": "1.10.0", + "@swc/core-win32-x64-msvc": "1.10.0" }, "peerDependencies": { "@swc/helpers": "*" @@ -6058,9 +6046,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz", - "integrity": "sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.0.tgz", + "integrity": "sha512-wCeUpanqZyzvgqWRtXIyhcFK3CqukAlYyP+fJpY2gWc/+ekdrenNIfZMwY7tyTFDkXDYEKzvn3BN/zDYNJFowQ==", "cpu": [ "arm64" ], @@ -6075,9 +6063,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.3.tgz", - "integrity": "sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.0.tgz", + "integrity": "sha512-0CZPzqTynUBO+SHEl/qKsFSahp2Jv/P2ZRjFG0gwZY5qIcr1+B/v+o74/GyNMBGz9rft+F2WpU31gz2sJwyF4A==", "cpu": [ "x64" ], @@ -6092,9 +6080,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.3.tgz", - "integrity": "sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.0.tgz", + "integrity": "sha512-oq+DdMu5uJOFPtRkeiITc4kxmd+QSmK+v+OBzlhdGkSgoH3yRWZP+H2ao0cBXo93ZgCr2LfjiER0CqSKhjGuNA==", "cpu": [ "arm" ], @@ -6109,9 +6097,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.3.tgz", - "integrity": "sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.0.tgz", + "integrity": "sha512-Y6+PC8knchEViRxiCUj3j8wsGXaIhuvU+WqrFqV834eiItEMEI9+Vh3FovqJMBE3L7d4E4ZQtgImHCXjrHfxbw==", "cpu": [ "arm64" ], @@ -6126,9 +6114,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.3.tgz", - "integrity": "sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.0.tgz", + "integrity": "sha512-EbrX9A5U4cECCQQfky7945AW9GYnTXtCUXElWTkTYmmyQK87yCyFfY8hmZ9qMFIwxPOH6I3I2JwMhzdi8Qoz7g==", "cpu": [ "arm64" ], @@ -6143,9 +6131,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz", - "integrity": "sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.0.tgz", + "integrity": "sha512-TaxpO6snTjjfLXFYh5EjZ78se69j2gDcqEM8yB9gguPYwkCHi2Ylfmh7iVaNADnDJFtjoAQp0L41bTV/Pfq9Cg==", "cpu": [ "x64" ], @@ -6160,9 +6148,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz", - "integrity": "sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.0.tgz", + "integrity": "sha512-IEGvDd6aEEKEyZFZ8oCKuik05G5BS7qwG5hO5PEMzdGeh8JyFZXxsfFXbfeAqjue4UaUUrhnoX+Ze3M2jBVMHw==", "cpu": [ "x64" ], @@ -6177,9 +6165,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.3.tgz", - "integrity": "sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.0.tgz", + "integrity": "sha512-UkQ952GSpY+Z6XONj9GSW8xGSkF53jrCsuLj0nrcuw7Dvr1a816U/9WYZmmcYS8tnG2vHylhpm6csQkyS8lpCw==", "cpu": [ "arm64" ], @@ -6194,9 +6182,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.3.tgz", - "integrity": "sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.0.tgz", + "integrity": "sha512-a2QpIZmTiT885u/mUInpeN2W9ClCnqrV2LnMqJR1/Fgx1Afw/hAtiDZPtQ0SqS8yDJ2VR5gfNZo3gpxWMrqdVA==", "cpu": [ "ia32" ], @@ -6211,9 +6199,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.3.tgz", - "integrity": "sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.0.tgz", + "integrity": "sha512-tZcCmMwf483nwsEBfUk5w9e046kMa1iSik4bP9Kwi2FGtOfHuDfIcwW4jek3hdcgF5SaBW1ktnK/lgQLDi5AtA==", "cpu": [ "x64" ], @@ -19002,9 +18990,9 @@ } }, "node_modules/prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", - "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index d393c30..2f632f8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lenne.tech/nest-server", - "version": "10.6.0", + "version": "10.7.0", "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).", "keywords": [ "node", @@ -66,10 +66,10 @@ "@getbrevo/brevo": "1.0.1", "@lenne.tech/mongoose-gridfs": "1.4.2", "@lenne.tech/multer-gridfs-storage": "5.0.6", - "@nestjs/apollo": "12.2.1", + "@nestjs/apollo": "12.2.2", "@nestjs/common": "10.4.13", "@nestjs/core": "10.4.13", - "@nestjs/graphql": "12.2.1", + "@nestjs/graphql": "12.2.2", "@nestjs/jwt": "10.2.0", "@nestjs/mongoose": "10.1.0", "@nestjs/passport": "10.0.3", @@ -114,7 +114,7 @@ "@nestjs/schematics": "10.2.3", "@nestjs/testing": "10.4.13", "@swc/cli": "0.5.2", - "@swc/core": "1.9.3", + "@swc/core": "1.10.0", "@swc/jest": "0.2.37", "@types/compression": "1.7.5", "@types/cookie-parser": "1.4.8", @@ -143,7 +143,7 @@ "jest": "29.7.0", "npm-watch": "0.13.0", "pm2": "5.4.3", - "prettier": "3.4.1", + "prettier": "3.4.2", "pretty-quick": "4.0.0", "supertest": "7.0.0", "ts-jest": "29.2.5", diff --git a/spectaql.yml b/spectaql.yml index b1fed2d..777bb2c 100644 --- a/spectaql.yml +++ b/spectaql.yml @@ -11,7 +11,7 @@ servers: info: title: lT Nest Server description: Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases). - version: 10.6.0 + version: 10.7.0 contact: name: lenne.Tech GmbH url: https://lenne.tech diff --git a/src/core/common/decorators/restricted.decorator.ts b/src/core/common/decorators/restricted.decorator.ts index 0f4ac2b..0363fca 100644 --- a/src/core/common/decorators/restricted.decorator.ts +++ b/src/core/common/decorators/restricted.decorator.ts @@ -3,7 +3,7 @@ import 'reflect-metadata'; import { ProcessType } from '../enums/process-type.enum'; import { RoleEnum } from '../enums/role.enum'; -import { getIncludedIds } from '../helpers/db.helper'; +import { equalIds, getIncludedIds } from '../helpers/db.helper'; import { RequireAtLeastOne } from '../types/required-at-least-one.type'; import _ = require('lodash'); @@ -22,6 +22,7 @@ export type RestrictedType = ( 'memberOf' | 'roles' > | string + | string[] )[]; /** @@ -63,11 +64,15 @@ export const checkRestricted = ( data: any, user: { hasRole: (roles: string[]) => boolean; id: any }, options: { + allowCreatorOfParent?: boolean; checkObjectItself?: boolean; dbObject?: any; debug?: boolean; + ignoreFunctions?: boolean; ignoreUndefined?: boolean; + isCreatorOfParent?: boolean; mergeRoles?: boolean; + noteCheckedObjects?: boolean; processType?: ProcessType; removeUndefinedFromResultArray?: boolean; throwError?: boolean; @@ -78,16 +83,20 @@ export const checkRestricted = ( // For Input: throwError = true // For Output: throwError = false const config = { + allowCreatorOfParent: true, checkObjectItself: false, + ignoreFunctions: true, ignoreUndefined: true, + isCreatorOfParent: false, mergeRoles: true, + noteCheckedObjects: true, removeUndefinedFromResultArray: true, throwError: true, ...options, }; // Primitives - if (!data || typeof data !== 'object') { + if (!data || typeof data !== 'object' || (config.noteCheckedObjects && data._objectAlreadyCheckedForRestrictions)) { return data; } @@ -109,6 +118,10 @@ export const checkRestricted = ( // Check function const validateRestricted = (restricted) => { + if (config.noteCheckedObjects && data?._objectAlreadyCheckedForRestrictions) { + return true; + } + // Check restrictions if (!restricted?.length) { return true; @@ -116,7 +129,7 @@ export const checkRestricted = ( let valid = false; - // Get roles + // Get roles restricted element const roles: string[] = []; restricted.forEach((item) => { if (typeof item === 'string') { @@ -130,6 +143,8 @@ export const checkRestricted = ( } else { roles.push(item.roles); } + } else if (Array.isArray(item)) { + roles.push(...item); } }); @@ -145,8 +160,10 @@ export const checkRestricted = ( roles.includes(RoleEnum.S_EVERYONE) || user?.hasRole?.(roles) || (user?.id && roles.includes(RoleEnum.S_USER)) - || (roles.includes(RoleEnum.S_SELF) && getIncludedIds(config.dbObject, user)) - || (roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user)) + || (roles.includes(RoleEnum.S_SELF) && equalIds(data, user)) + || (roles.includes(RoleEnum.S_CREATOR) + && (('createdBy' in data && equalIds(data.createdBy, user)) + || (config.allowCreatorOfParent && !('createdBy' in data) && config.isCreatorOfParent))) ) { valid = true; } @@ -200,7 +217,7 @@ export const checkRestricted = ( return valid; }; - // Check object + // Check data object const objectRestrictions = getRestricted(data.constructor) || []; if (config.checkObjectItself) { const objectIsValid = validateRestricted(objectRestrictions); @@ -218,6 +235,11 @@ export const checkRestricted = ( // Check properties of object for (const propertyKey of Object.keys(data)) { + // Ignore functions + if (typeof data[propertyKey] === 'function' && config.ignoreFunctions) { + continue; + } + // Check undefined if (data[propertyKey] === undefined && config.ignoreUndefined) { continue; @@ -230,6 +252,10 @@ export const checkRestricted = ( // Check rights if (valid) { + // Check if data is user or user is creator of data (for nested plain objects) + config.isCreatorOfParent + = equalIds(data, user) || ('createdBy' in data ? equalIds(data.createdBy, user) : config.isCreatorOfParent); + // Check deep data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects); } else { diff --git a/src/core/common/helpers/db.helper.ts b/src/core/common/helpers/db.helper.ts index 7d60f00..1881468 100644 --- a/src/core/common/helpers/db.helper.ts +++ b/src/core/common/helpers/db.helper.ts @@ -670,5 +670,9 @@ function getStringId(element: any): string { } // Other types - return element.toString(); + if (typeof element.toString === 'function') { + return element.toString(); + } + + return undefined; } diff --git a/src/core/common/helpers/input.helper.ts b/src/core/common/helpers/input.helper.ts index c305c57..2460b7e 100644 --- a/src/core/common/helpers/input.helper.ts +++ b/src/core/common/helpers/input.helper.ts @@ -212,7 +212,9 @@ export async function check( value: any, user: { hasRole: (roles: string[]) => boolean; id: any }, options?: { + allowCreatorOfParent?: boolean; dbObject?: any; + isCreatorOfParent?: boolean; metatype?: any; processType?: ProcessType; roles?: string | string[]; @@ -221,6 +223,8 @@ export async function check( }, ): Promise { const config = { + allowCreatorOfParent: true, + isCreatorOfParent: false, throwError: true, ...options, validatorOptions: { @@ -254,7 +258,12 @@ export async function check( // check if the user is herself / himself || (roles.includes(RoleEnum.S_SELF) && equalIds(config.dbObject, user)) // check if the user is the creator - || (roles.includes(RoleEnum.S_CREATOR) && equalIds(config.dbObject?.createdBy, user)) + || (roles.includes(RoleEnum.S_CREATOR) + && ((config.dbObject && 'createdBy' in config.dbObject && equalIds(config.dbObject.createdBy, user)) + || (config.allowCreatorOfParent + && config.dbObject + && !('createdBy' in config.dbObject) + && config.isCreatorOfParent))) ) { valid = true; } @@ -263,6 +272,9 @@ export async function check( } } + // Object check is done + delete config.roles; + // Return value if it is only a basic type if (!value || typeof value !== 'object') { return value; @@ -278,12 +290,12 @@ export async function check( const metatype = config.metatype; if (metatype) { - // Check metatype + // If metatype is a basic type, additional checks are not possible if (isBasicType(metatype)) { return value; } - // Convert to metatype + // Convert to metatype, validate rights if (!(value instanceof metatype)) { if ((metatype as any)?.map) { value = (metatype as any)?.map(value); @@ -293,7 +305,7 @@ export async function check( } } - // Validate + // Validate values (like isEmail, isNumber, etc.) const errors = await validate(value, config.validatorOptions); if (errors.length > 0 && config.throwError) { throw new BadRequestException('Validation failed'); diff --git a/src/core/common/interceptors/check-response.interceptor.ts b/src/core/common/interceptors/check-response.interceptor.ts index 3f379c8..8f79124 100644 --- a/src/core/common/interceptors/check-response.interceptor.ts +++ b/src/core/common/interceptors/check-response.interceptor.ts @@ -16,6 +16,7 @@ export class CheckResponseInterceptor implements NestInterceptor { debug: false, ignoreUndefined: true, mergeRoles: true, + noteCheckedObjects: true, removeUndefinedFromResultArray: true, throwError: false, }; @@ -38,7 +39,18 @@ export class CheckResponseInterceptor implements NestInterceptor { return next.handle().pipe( map((data) => { // Prepare response data for current user - return checkRestricted(data, currentUser, this.config); + const start = Date.now(); + const result = checkRestricted(data, currentUser, this.config); + if ( + this.config.debug + && Date.now() - start >= (typeof this.config.debug === 'number' ? this.config.debug : 100) + ) { + console.warn( + `Duration for CheckResponseInterceptor is too long: ${Date.now() - start}ms`, + Array.isArray(data) ? `${data[0].constructor.name}[]: ${data.length}` : data?.constructor?.name, + ); + } + return result; }), ); } diff --git a/src/core/common/interceptors/check-security.interceptor.ts b/src/core/common/interceptors/check-security.interceptor.ts index fb227d8..0f81384 100644 --- a/src/core/common/interceptors/check-security.interceptor.ts +++ b/src/core/common/interceptors/check-security.interceptor.ts @@ -5,13 +5,29 @@ import { map } from 'rxjs/operators'; import { getContextData } from '../helpers/context.helper'; import { getStringIds } from '../helpers/db.helper'; import { processDeep } from '../helpers/input.helper'; +import { ConfigService } from '../services/config.service'; /** * Verification of all outgoing data via securityCheck */ @Injectable() export class CheckSecurityInterceptor implements NestInterceptor { + config = { + debug: false, + noteCheckedObjects: true, + }; + + constructor(private readonly configService: ConfigService) { + const configuration = this.configService.getFastButReadOnly('security.checkSecurityInterceptor'); + if (typeof configuration === 'object') { + this.config = { ...this.config, ...configuration }; + } + } + intercept(context: ExecutionContext, next: CallHandler): Observable { + // Start time + const start = Date.now(); + // Get current user const user = getContextData(context)?.currentUser || null; @@ -25,7 +41,17 @@ export class CheckSecurityInterceptor implements NestInterceptor { } } - const check = (data) => { + // Data from next for check + let objectData: any; + + const check = (data: any) => { + objectData = data; + + // Check if data already checked + if (this.config.noteCheckedObjects && data?._objectAlreadyCheckedForRestrictions) { + return data; + } + // Check data if (data && typeof data === 'object' && typeof data.securityCheck === 'function') { const dataJson = JSON.stringify(data); @@ -73,6 +99,15 @@ export class CheckSecurityInterceptor implements NestInterceptor { }; // Check response - return next.handle().pipe(map(check)); + const result = next.handle().pipe(map(check)); + if (this.config.debug && Date.now() - start >= (typeof this.config.debug === 'number' ? this.config.debug : 100)) { + console.warn( + `Duration for CheckResponseInterceptor is too long: ${Date.now() - start}ms`, + Array.isArray(objectData) + ? `${objectData[0].constructor.name}[]: ${objectData.length}` + : objectData?.constructor?.name, + ); + } + return result; } } diff --git a/src/core/common/interfaces/server-options.interface.ts b/src/core/common/interfaces/server-options.interface.ts index 74a374c..0489473 100644 --- a/src/core/common/interfaces/server-options.interface.ts +++ b/src/core/common/interfaces/server-options.interface.ts @@ -421,10 +421,11 @@ export interface IServerOptions { checkObjectItself?: boolean; /** - * Whether to log if a restricted field is found + * Whether to log if a restricted field is found or process is slow + * boolean or number (time in ms) * default = false */ - debug?: boolean; + debug?: boolean | number; /** * Whether to ignore fields with undefined values @@ -438,6 +439,13 @@ export interface IServerOptions { */ mergeRoles?: boolean; + /** + * Whether objects that have already been checked should be ignored + * Objects with truly property `_objectAlreadyCheckedForRestrictions` will be ignored + * default = true + */ + noteCheckedObjects?: boolean; + /** * Remove undefined values from result array * default = true @@ -457,7 +465,22 @@ export interface IServerOptions { * See @lenne.tech/nest-server/src/core/common/interceptors/check-security.interceptor.ts * default = true */ - checkSecurityInterceptor?: boolean; + checkSecurityInterceptor?: + | { + /** + * Whether to log if a process is slow + * boolean or number (time in ms) + * default = false + */ + debug?: boolean | number; + + /** + * Whether objects with truly property `_objectAlreadyCheckedForRestrictions` will be ignored + * default = true + */ + noteCheckedObjects?: boolean; + } + | boolean; /** * Map incoming plain objects to meta-type and validate diff --git a/src/core/common/models/core-persistence.model.ts b/src/core/common/models/core-persistence.model.ts index 337fb5d..06bcdc4 100644 --- a/src/core/common/models/core-persistence.model.ts +++ b/src/core/common/models/core-persistence.model.ts @@ -57,28 +57,6 @@ export abstract class CorePersistenceModel extends CoreModel { @Prop({ onCreate: () => new Date() }) createdAt: Date = undefined; - /** - * Labels of the object - */ - @Restricted(RoleEnum.S_EVERYONE) - @Field(type => [String], { - description: 'Labels of the object', - nullable: true, - }) - @Prop([String]) - labels: string[] = undefined; - - /** - * Tags for the object - */ - @Restricted(RoleEnum.S_EVERYONE) - @Field(type => [String], { - description: 'Tags for the object', - nullable: true, - }) - @Prop([String]) - tags: string[] = undefined; - /** * Updated date is set automatically by mongoose */ @@ -97,9 +75,7 @@ export abstract class CorePersistenceModel extends CoreModel { override init() { super.init(); this.createdAt = this.createdAt === undefined ? new Date() : this.createdAt; - this.labels = this.labels === undefined ? [] : this.labels; - this.tags = this.tags === undefined ? [] : this.tags; - this.updatedAt = this.tags === undefined ? this.createdAt : this.updatedAt; + this.updatedAt = this.updatedAt === undefined ? this.createdAt : this.updatedAt; return this; } diff --git a/src/core/modules/user/core-user.model.ts b/src/core/modules/user/core-user.model.ts index 00e13b5..351b1ce 100644 --- a/src/core/modules/user/core-user.model.ts +++ b/src/core/modules/user/core-user.model.ts @@ -1,6 +1,6 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { Schema as MongooseSchema, Prop, raw } from '@nestjs/mongoose'; -import { IsEmail, IsOptional } from 'class-validator'; +import { IsOptional } from 'class-validator'; import { Document } from 'mongoose'; import { Restricted } from '../../common/decorators/restricted.decorator'; @@ -26,8 +26,7 @@ export abstract class CoreUserModel extends CorePersistenceModel { */ @Restricted(RoleEnum.S_EVERYONE) @Field({ description: 'Email of the user', nullable: true }) - @IsEmail() - @Prop({ lowercase: true, trim: true, unique: true }) + @Prop({ index: true, lowercase: true, trim: true }) email: string = undefined; /** diff --git a/src/server/modules/user/user.model.ts b/src/server/modules/user/user.model.ts index e22a7b6..b3f778d 100644 --- a/src/server/modules/user/user.model.ts +++ b/src/server/modules/user/user.model.ts @@ -1,6 +1,6 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { Schema as MongooseSchema, Prop, SchemaFactory } from '@nestjs/mongoose'; -import { IsOptional } from 'class-validator'; +import { IsEmail, IsOptional } from 'class-validator'; import { Document, Schema } from 'mongoose'; import { Restricted } from '../../../core/common/decorators/restricted.decorator'; @@ -42,6 +42,15 @@ export class User extends CoreUserModel implements PersistenceModel { @Prop({ ref: 'User', type: Schema.Types.ObjectId }) createdBy: string = undefined; + /** + * E-Mail address of the user + */ + @Restricted(RoleEnum.S_EVERYONE) + @Field({ description: 'Email of the user', nullable: true }) + @IsEmail() + @Prop({ lowercase: true, trim: true, unique: true }) + override email: string = undefined; + /** * Roles of the user */ @@ -105,8 +114,6 @@ export class User extends CoreUserModel implements PersistenceModel { // PersistenceModel and CorePersistenceModel this.createdAt = null; this.createdBy = null; - this.labels = null; - this.tags = null; this.updatedAt = null; this.updatedBy = null; }