diff --git a/.ci/runChecks.sh b/.ci/runChecks.sh index 25eb320..5c334d2 100755 --- a/.ci/runChecks.sh +++ b/.ci/runChecks.sh @@ -4,4 +4,4 @@ npm ci npm run build npm run lint npx license-check -npx better-npm-audit audit --exclude=1096727,1097682 +npx better-npm-audit audit --exclude=1106509,1096727,1097682 diff --git a/package-lock.json b/package-lock.json index ee496ae..dce3a9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@nmshd/typescript-rest", - "version": "3.1.6", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@nmshd/typescript-rest", - "version": "3.1.6", + "version": "3.2.0", "license": "MIT", "dependencies": { "@types/body-parser": "1.19.5", @@ -586,34 +586,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@es-joy/jsdoccomment": { "version": "0.46.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", @@ -1351,42 +1323,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2003,21 +1939,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2105,15 +2026,6 @@ "node": ">=14" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2807,15 +2719,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -2961,18 +2864,6 @@ "wrappy": "1" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -3143,6 +3034,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3943,14 +3850,18 @@ } }, "node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" }, "engines": { "node": ">= 0.12" @@ -4251,6 +4162,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -7475,52 +7402,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -7703,15 +7584,6 @@ "uuid": "bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -7930,18 +7802,6 @@ "node": ">=12" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -8318,31 +8178,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, "@es-joy/jsdoccomment": { "version": "0.46.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", @@ -8863,38 +8698,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true - }, "@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -9351,17 +9154,6 @@ "dev": true, "requires": {} }, - "acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "acorn": "^8.11.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -9419,14 +9211,6 @@ "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -9907,14 +9691,6 @@ "prompts": "^2.0.1" } }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -10004,14 +9780,6 @@ "wrappy": "1" } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true - }, "diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -10131,6 +9899,18 @@ "es-errors": "^1.3.0" } }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -10682,14 +10462,17 @@ "dev": true }, "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" } }, "forwarded": { @@ -10880,6 +10663,15 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -13122,29 +12914,6 @@ } } }, - "ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - }, "tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -13258,14 +13027,6 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, "v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -13423,14 +13184,6 @@ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 88fb35e..1e08e07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/typescript-rest", - "version": "3.1.6", + "version": "3.2.0", "description": "A Library to create RESTFul APIs with Typescript", "keywords": [ "API", diff --git a/src/decorators/services.ts b/src/decorators/services.ts index 6eed48b..1e6fac5 100644 --- a/src/decorators/services.ts +++ b/src/decorators/services.ts @@ -73,8 +73,11 @@ export function Path(path: string) { * GET http://mydomain/people/123 (For all authorized users) * ``` */ -export function Security(roles?: string | Array, name?: string) { - roles = _.castArray(roles || '*'); +export function Security(roles: string | [string, ...Array], name?: string) { + roles = Array.isArray(roles) ? roles : [roles]; + + if (roles.length === 0) throw new Error('At least one role must be specified.'); + return new SecurityServiceDecorator('Security') .withObjectProperty('authenticator', name || 'default', roles) .createDecorator(); diff --git a/src/middlewares/routeRequiresAuthorization.ts b/src/middlewares/routeRequiresAuthorization.ts new file mode 100644 index 0000000..9197e85 --- /dev/null +++ b/src/middlewares/routeRequiresAuthorization.ts @@ -0,0 +1,52 @@ +import * as debug from 'debug'; +import { NextFunction, Request, Response } from 'express'; +import * as Errors from '../server/model/errors'; + +const debuggerInstance = debug('typescript-rest:middlewares:routeRequiresAuthorization'); + +export function routeRequiresAuthorization( + authenticator: { getRoles: (req: Request, res: Response) => Array }, + ...permittedRoles: [string, ...Array] +) { + if (permittedRoles.length === 0) throw new Error('At least one permitted role must be specified.'); + + const roleRegex = /^[a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+){0,}$/; + const nonMatchingRoles = permittedRoles.filter((role) => !roleRegex.test(role)); + if (nonMatchingRoles.length > 0) { + throw new Error( + `Invalid permitted role(s) specified: ${nonMatchingRoles.join(', ')}. Roles must match the pattern: ${roleRegex}.` + ); + } + + return (req: Request, res: Response, next: NextFunction) => { + const requestRoles = authenticator.getRoles(req, res); + if (debuggerInstance.enabled) debuggerInstance('Validating authentication roles: <%j>.', requestRoles); + + const transformedRoles = requestRoles.map(transformUserRole); + const isAuthorized = permittedRoles.some((permittedRole) => isRoleMatched(permittedRole, transformedRoles)); + if (!isAuthorized) { + next(new Errors.ForbiddenError('You are not allowed to access this endpoint.')); + return; + } + + next(); + }; +} + +function isRoleMatched(permittedRole: string, userRoles: Array): boolean { + for (const userRole of userRoles) { + const isMatch = userRole.test(permittedRole); + if (isMatch) return true; + } + + return false; +} + +function transformUserRole(userRole: string): RegExp { + if (!userRole.includes('*')) return new RegExp(`^${userRole}$`); + + const regexString = userRole + .replaceAll('**', '[a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+){0,}') + .replaceAll('*', '[a-zA-Z0-9_-]+'); + return new RegExp(`^${regexString}$`); +} diff --git a/src/server/model/server-types.ts b/src/server/model/server-types.ts index 4f11084..a56486a 100644 --- a/src/server/model/server-types.ts +++ b/src/server/model/server-types.ts @@ -43,18 +43,22 @@ export class ServiceContext { * The resolved language to be used in the current request handling. */ public language: string; + /** * The preferred media type to be used in the current request handling. */ public accept: string; + /** * The request object. */ public request: express.Request; + /** * The response object */ public response: express.Response; + /** * The next function. It can be used to delegate to the next middleware * registered the processing of the current request. @@ -107,14 +111,16 @@ export interface ServiceAuthenticator { * Get the user list of roles. */ getRoles: (req: express.Request, res: express.Response) => Array; + /** * Initialize the authenticator */ - initialize(router: express.Router): void; + initialize?: (router: express.Router) => void; + /** * Retrieve the middleware used to authenticate users. */ - getMiddleware(): express.RequestHandler; + getMiddleware?: () => express.RequestHandler; } export type ServiceProcessor = (req: express.Request, res?: express.Response) => void | Promise; diff --git a/src/server/server-container.ts b/src/server/server-container.ts index 8ef2747..e3b7b41 100644 --- a/src/server/server-container.ts +++ b/src/server/server-container.ts @@ -5,9 +5,9 @@ import * as bodyParser from 'body-parser'; import * as cookieParser from 'cookie-parser'; import * as debug from 'debug'; import * as express from 'express'; -import { NextFunction, Request, Response } from 'express'; import * as _ from 'lodash'; import * as multer from 'multer'; +import { routeRequiresAuthorization } from '../middlewares/routeRequiresAuthorization'; import * as Errors from './model/errors'; import { ServiceClass, ServiceMethod } from './model/metadata'; import { @@ -53,8 +53,7 @@ export class ServerContainer { public router: express.Router; private debugger = { - build: debug('typescript-rest:server-container:build'), - runtime: debug('typescript-rest:server-container:runtime') + build: debug('typescript-rest:server-container:build') }; private upload: multer.Multer; private serverClasses: Map = new Map(); @@ -114,7 +113,7 @@ export class ServerContainer { if (this.authenticator) { this.authenticator.forEach((auth, name) => { this.debugger.build('Initializing authenticator: %s', name); - auth.initialize(this.router); + if (auth.initialize) auth.initialize(this.router); }); } this.serverClasses.forEach((classData) => { @@ -337,24 +336,25 @@ export class ServerContainer { if (this.authenticator && authenticatorMap) { const authenticatorNames: Array = Object.keys(authenticatorMap); for (const authenticatorName of authenticatorNames) { - let roles: Array = authenticatorMap[authenticatorName]; + const roles: Array = authenticatorMap[authenticatorName]; + if (roles.length === 0) continue; + this.debugger.build( 'Registering an authenticator middleware <%s> for method <%s>.', authenticatorName, serviceMethod.name ); + const authenticator = this.getAuthenticator(authenticatorName); - result.push(authenticator.getMiddleware()); - roles = roles.filter((role) => role !== '*'); - if (roles.length) { - this.debugger.build( - 'Registering a role validator middleware <%s> for method <%s>.', - authenticatorName, - serviceMethod.name - ); - this.debugger.build('Roles: <%j>.', roles); - result.push(this.buildAuthMiddleware(authenticator, roles)); - } + if (authenticator.getMiddleware) result.push(authenticator.getMiddleware()); + + this.debugger.build( + 'Registering a role validator middleware <%s> for method <%s>.', + authenticatorName, + serviceMethod.name + ); + this.debugger.build('Roles: <%j>.', roles); + result.push(routeRequiresAuthorization(authenticator, roles[0], ...roles.slice(1))); } } @@ -365,21 +365,8 @@ export class ServerContainer { if (!this.authenticator.has(authenticatorName)) { throw new Error(`Invalid authenticator name ${authenticatorName}`); } - return this.authenticator.get(authenticatorName); - } - private buildAuthMiddleware(authenticator: ServiceAuthenticator, roles: Array): express.RequestHandler { - return (req: Request, res: Response, next: NextFunction) => { - const requestRoles = authenticator.getRoles(req, res); - if (this.debugger.runtime.enabled) { - this.debugger.runtime('Validating authentication roles: <%j>.', requestRoles); - } - if (requestRoles.some((role: string) => roles.indexOf(role) >= 0)) { - next(); - } else { - throw new Errors.ForbiddenError(); - } - }; + return this.authenticator.get(authenticatorName); } private buildParserMiddlewares( diff --git a/src/typescript-rest.ts b/src/typescript-rest.ts index 6246161..a95aef6 100644 --- a/src/typescript-rest.ts +++ b/src/typescript-rest.ts @@ -7,6 +7,7 @@ import * as Return from './server/model/return-types'; export * from './decorators/methods'; export * from './decorators/parameters'; export * from './decorators/services'; +export * from './middlewares/routeRequiresAuthorization'; export * from './server/model/server-types'; export * from './server/server'; diff --git a/test/tsconfig.json b/test/tsconfig.json index 59b2879..22b068b 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -3,7 +3,7 @@ "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "lib": ["es6", "dom"], + "lib": ["es2021", "dom"], "module": "commonjs", "newLine": "LF", "noFallthroughCasesInSwitch": true, @@ -14,7 +14,7 @@ "noUnusedLocals": true, "sourceMap": true, "strictNullChecks": false, - "target": "es5" + "target": "es2021" }, "include": ["**/*.ts"] } diff --git a/test/unit/decorators.spec.ts b/test/unit/decorators.spec.ts index f629887..4160eee 100644 --- a/test/unit/decorators.spec.ts +++ b/test/unit/decorators.spec.ts @@ -127,8 +127,7 @@ describe('Decorators', () => { }); it('should add a security set of roles to methods of a class', () => { - const roles = ['test-role', 'tes-role2']; - serviceDecorators.Security(roles)( + serviceDecorators.Security(['test-role', 'test-role2'])( TestService.prototype, 'test', Object.getOwnPropertyDescriptor(TestService.prototype, 'test') @@ -136,34 +135,19 @@ describe('Decorators', () => { expect(serverContainer.registerServiceMethod).toHaveBeenCalledTimes(1); expect(serviceMethod.authenticator.default).toHaveLength(2); - expect(serviceMethod.authenticator.default).toContain(roles[0]); - expect(serviceMethod.authenticator.default).toContain(roles[1]); + expect(serviceMethod.authenticator.default).toContain('test-role'); + expect(serviceMethod.authenticator.default).toContain('test-role2'); }); - it('should add a security validation to accept any role when empty is received', () => { - const role = ''; - serviceDecorators.Security(role)( - TestService.prototype, - 'test', - Object.getOwnPropertyDescriptor(TestService.prototype, 'test') - ); - - expect(serverContainer.registerServiceMethod).toHaveBeenCalledTimes(1); - expect(serviceMethod.authenticator.default).toHaveLength(1); - expect(serviceMethod.authenticator.default).toContain('*'); - }); - - it('should add a security validation to accept any role when undefined is received', () => { - const role: string = undefined; - serviceDecorators.Security(role)( - TestService.prototype, - 'test', - Object.getOwnPropertyDescriptor(TestService.prototype, 'test') - ); - - expect(serverContainer.registerServiceMethod).toHaveBeenCalledTimes(1); - expect(serviceMethod.authenticator.default).toHaveLength(1); - expect(serviceMethod.authenticator.default).toContain('*'); + it('should throw an error when no roles are specified', () => { + expect(() => + // @ts-expect-error: Testing error throwing + serviceDecorators.Security([])( + TestService.prototype, + 'test', + Object.getOwnPropertyDescriptor(TestService.prototype, 'test') + ) + ).toThrow('At least one role must be specified.'); }); it('should set the default authenticator if no name is provided', () => { diff --git a/test/unit/middlewares/routeRequiresAuthorization.spec.ts b/test/unit/middlewares/routeRequiresAuthorization.spec.ts new file mode 100644 index 0000000..c1d8cd4 --- /dev/null +++ b/test/unit/middlewares/routeRequiresAuthorization.spec.ts @@ -0,0 +1,96 @@ +import * as express from 'express'; +import { Errors, routeRequiresAuthorization } from '../../../src/typescript-rest'; + +describe('routeRequiresAuthorization middleware', () => { + const res = {} as any as express.Response; + + test.each([ + [['admin'], ['admin']], + [['admin', 'core:messages'], ['core:messages']], + [['admin', 'core:*'], ['core:messages']], + [ + ['admin', 'core:*'], + ['core:messages', 'admin'] + ], + [ + ['admin', 'core:*'], + ['core:messages', 'aRandomRole'] + ], + [ + ['admin', 'core:*'], + ['core:messages', 'admin', 'aRandomRole'] + ], + [ + ['admin', 'core:*'], + ['core:messages', 'admin', 'aRandomRole', 'anotherRole'] + ], + [ + ['admin', 'core:*'], + ['core:messages', 'admin', 'anotherRole'] + ], + [ + ['admin', 'core:*'], + ['core:messages', 'admin', 'anotherRole', 'aRandomRole'] + ], + [['*:*'], ['core:messages']], + [['**'], ['core:messages']], + [['core:*'], ['core:messages']], + [['core:**'], ['core:messages']], + [['core:**'], ['core:messages:whatever']], + [['core:*:*:*'], ['core:messages:whatever:whatever']], + [['core:*:*:whatever'], ['core:messages:whatever:whatever']], + [['core:**:whatever'], ['core:messages:whatever']], + [['core:**:whatever'], ['core:messages:whatever:whatever']], + [['**:whatever'], ['core:messages:whatever']], + [['**:whatever'], ['core:messages:whatever:whatever']] + ])('should accept the given (%s) and permitted (%s) roles', (userRoles, permittedRoles) => { + const next = jest.fn(); + + const authenticator = { getRoles: () => userRoles }; + const fn = routeRequiresAuthorization(authenticator, permittedRoles[0], ...permittedRoles.slice(1)); + fn({ userRoles: userRoles } as any as express.Request, res, next); + + expect(next).toHaveBeenCalledTimes(1); + + expect(next).toHaveBeenCalledWith(); + }); + + test.each([ + [[], ['admin']], + [['admin'], ['aRandomRole']], + [['admin', 'core:*'], ['aRandomRole']], + [ + ['admin', 'core:*'], + ['aRandomRole', 'anotherRole'] + ], + [['core:*'], ['core:messages:whatever']], + [['core:*:*:*'], ['core:messages:whatever']], + [['core:*:*:whatever'], ['core:messages:whatever']] + ])('should reject the given (%s) and permitted (%s) roles', (userRoles, permittedRoles) => { + const next = jest.fn(); + + const authenticator = { getRoles: () => userRoles }; + const fn = routeRequiresAuthorization(authenticator, permittedRoles[0], ...permittedRoles.slice(1)); + fn({ userRoles: userRoles } as any as express.Request, res, next); + + expect(next).toHaveBeenCalledTimes(1); + + expect(next).toHaveBeenCalledWith(new Errors.ForbiddenError('You are not allowed to access this endpoint.')); + }); + + test('should throw an error if no permitted roles are specified', () => { + expect( + // @ts-expect-error: Testing error throwing + () => routeRequiresAuthorization({ getRoles: () => [] }) + ).toThrow('At least one permitted role must be specified.'); + }); + + test.each(['admin:', ':admin', 'admin::core', 'admin::', '::admin', '*', '**'])( + 'should throw an error because the permitted role (%s) does not match the pattern', + (role) => { + expect(() => routeRequiresAuthorization({ getRoles: () => [] }, role)).toThrow( + 'Invalid permitted role(s) specified' + ); + } + ); +}); diff --git a/tsconfig.json b/tsconfig.json index b8c05b1..f07505b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "lib": ["es6", "dom"], + "lib": ["es2021", "dom"], "module": "commonjs", "newLine": "LF", "noFallthroughCasesInSwitch": true, @@ -15,7 +15,7 @@ "outDir": "dist", "sourceMap": true, "strictNullChecks": false, - "target": "es6" + "target": "es2021" }, "include": ["src/**/*.ts"] }