diff --git a/package-lock.json b/package-lock.json index 652fdd8..fd1ad3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@types/supertest": "^2.0.11", "dotenv": "^10.0.0", "jest": "^27.0.6", + "mongodb-memory-server": "^8.0.0", "nodemon": "^2.0.7", "prettier": "^2.3.1", "rimraf": "^3.0.2", @@ -2285,6 +2286,12 @@ "@types/superagent": "*" } }, + "node_modules/@types/tmp": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.2.tgz", + "integrity": "sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A==", + "dev": true + }, "node_modules/@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -2576,6 +2583,15 @@ "node": ">=0.10.0" } }, + "node_modules/async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "dev": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2756,6 +2772,23 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "node_modules/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -2910,6 +2943,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -3281,6 +3323,12 @@ "node": ">= 0.8" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -4286,6 +4334,15 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4330,6 +4387,23 @@ "node": ">= 0.8" } }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -4432,6 +4506,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/fs-extra": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", @@ -4512,6 +4592,18 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -7196,6 +7288,18 @@ "node": ">=8" } }, + "node_modules/md5-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", + "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", + "dev": true, + "bin": { + "md5-file": "cli.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7434,6 +7538,97 @@ "node": ">=12" } }, + "node_modules/mongodb-memory-server": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-8.0.0.tgz", + "integrity": "sha512-5ddOZ0yqSzYNLF5WyZrN6LudtKTEa4JaTf5Ov8+wh71RLrz2zu/Um/wSLu5GtgL33N2NS1Zv88vV77Q/+Zxo1w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "mongodb-memory-server-core": "8.0.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/mongodb-memory-server-core": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-8.0.0.tgz", + "integrity": "sha512-Gt4fmGVnMP6sBK/eq5rJ9ItLSBg8u2Ya/WD29llc9OkMxZywdxIVPBqYUs4Fq8v/I+rDIVj7tbueomV3gnu/zg==", + "dev": true, + "dependencies": { + "@types/tmp": "^0.2.2", + "async-mutex": "^0.3.2", + "camelcase": "^6.1.0", + "debug": "^4.2.0", + "find-cache-dir": "^3.3.2", + "get-port": "^5.1.1", + "https-proxy-agent": "^5.0.0", + "md5-file": "^5.0.0", + "mongodb": "^4.1.3", + "new-find-package-json": "^1.1.0", + "semver": "^7.3.5", + "tar-stream": "^2.1.4", + "tmp": "^0.2.1", + "tslib": "^2.3.1", + "uuid": "^8.3.1", + "yauzl": "^2.10.0" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mongodb-memory-server-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mongodb-memory-server-core/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mongoose": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz", @@ -7564,6 +7759,42 @@ "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", "dev": true }, + "node_modules/new-find-package-json": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-1.1.0.tgz", + "integrity": "sha512-KOH3BNZcTKPzEkaJgG2iSUaurxKmefqRKmCOYH+8xqJytNIgjqU4J88BHfK+gy/UlEzlhccLyuJDJAcCgexSwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.2", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/new-find-package-json/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/new-find-package-json/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -10861,6 +11092,12 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -12284,6 +12521,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -12396,6 +12649,18 @@ "readable-stream": "3" } }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, "node_modules/tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -12595,6 +12860,12 @@ } } }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -12778,6 +13049,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-to-istanbul": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", @@ -13090,6 +13370,16 @@ "node": ">=10" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -14924,6 +15214,12 @@ "@types/superagent": "*" } }, + "@types/tmp": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.2.tgz", + "integrity": "sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A==", + "dev": true + }, "@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -15158,6 +15454,15 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -15290,6 +15595,25 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -15402,6 +15726,12 @@ "ieee754": "^1.1.13" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -15693,6 +16023,12 @@ "delayed-stream": "~1.0.0" } }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -16509,6 +16845,15 @@ "bser": "2.1.1" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -16541,6 +16886,17 @@ "unpipe": "~1.0.0" } }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -16623,6 +16979,12 @@ } } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs-extra": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", @@ -16681,6 +17043,12 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -18735,6 +19103,12 @@ } } }, + "md5-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", + "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -18909,6 +19283,72 @@ } } }, + "mongodb-memory-server": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-8.0.0.tgz", + "integrity": "sha512-5ddOZ0yqSzYNLF5WyZrN6LudtKTEa4JaTf5Ov8+wh71RLrz2zu/Um/wSLu5GtgL33N2NS1Zv88vV77Q/+Zxo1w==", + "dev": true, + "requires": { + "mongodb-memory-server-core": "8.0.0", + "tslib": "^2.3.1" + } + }, + "mongodb-memory-server-core": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-8.0.0.tgz", + "integrity": "sha512-Gt4fmGVnMP6sBK/eq5rJ9ItLSBg8u2Ya/WD29llc9OkMxZywdxIVPBqYUs4Fq8v/I+rDIVj7tbueomV3gnu/zg==", + "dev": true, + "requires": { + "@types/tmp": "^0.2.2", + "async-mutex": "^0.3.2", + "camelcase": "^6.1.0", + "debug": "^4.2.0", + "find-cache-dir": "^3.3.2", + "get-port": "^5.1.1", + "https-proxy-agent": "^5.0.0", + "md5-file": "^5.0.0", + "mongodb": "^4.1.3", + "new-find-package-json": "^1.1.0", + "semver": "^7.3.5", + "tar-stream": "^2.1.4", + "tmp": "^0.2.1", + "tslib": "^2.3.1", + "uuid": "^8.3.1", + "yauzl": "^2.10.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "mongoose": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz", @@ -19015,6 +19455,33 @@ "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", "dev": true }, + "new-find-package-json": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-1.1.0.tgz", + "integrity": "sha512-KOH3BNZcTKPzEkaJgG2iSUaurxKmefqRKmCOYH+8xqJytNIgjqU4J88BHfK+gy/UlEzlhccLyuJDJAcCgexSwA==", + "dev": true, + "requires": { + "debug": "^4.3.2", + "tslib": "^2.3.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -21397,6 +21864,12 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -22488,6 +22961,19 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -22569,6 +23055,15 @@ "readable-stream": "3" } }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -22702,6 +23197,12 @@ "yn": "3.1.1" } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -22839,6 +23340,12 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, "v8-to-istanbul": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", @@ -23081,6 +23588,16 @@ "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", "dev": true }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index fbf17b4..d37fa24 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@types/supertest": "^2.0.11", "dotenv": "^10.0.0", "jest": "^27.0.6", + "mongodb-memory-server": "^8.0.0", "nodemon": "^2.0.7", "prettier": "^2.3.1", "rimraf": "^3.0.2", diff --git a/src/controllers/createClient.ts b/src/controllers/createClient.ts new file mode 100644 index 0000000..b768d87 --- /dev/null +++ b/src/controllers/createClient.ts @@ -0,0 +1,22 @@ +import Client from '../model/Client' + +export const createClient = async (data: any) => { + const { clientid, clientsecret } = data + + // Checking if client is already in the database + const clientExist = await Client.findOne({ clientid }) + if (clientExist) throw new Error('Client ID already exists.') + + // Create a new client + const client = new Client({ + clientid, + clientsecret + }) + + const savedClient = await client.save() + + return { + clientid: savedClient.clientid, + clientsecret: savedClient.clientsecret + } +} diff --git a/src/controllers/createUser.ts b/src/controllers/createUser.ts new file mode 100644 index 0000000..1586e58 --- /dev/null +++ b/src/controllers/createUser.ts @@ -0,0 +1,32 @@ +import bcrypt from 'bcryptjs' +import User from '../model/User' + +export const createUser = async (data: any) => { + const { displayname, username, password, isadmin, isactive } = data + + // Checking if user is already in the database + const usernameExist = await User.findOne({ username }) + if (usernameExist) throw new Error('Username already exists.') + + // Hash passwords + const salt = await bcrypt.genSalt(10) + const hashPassword = await bcrypt.hash(password, salt) + + // Create a new user + const user = new User({ + displayname, + username, + password: hashPassword, + isadmin, + isactive + }) + + const savedUser = await user.save() + + return { + displayname: savedUser.displayname, + username: savedUser.username, + isadmin: savedUser.isadmin, + isactive: savedUser.isactive + } +} diff --git a/src/routes/api/auth.ts b/src/routes/api/auth.ts index 5659f14..c2a738c 100644 --- a/src/routes/api/auth.ts +++ b/src/routes/api/auth.ts @@ -1,6 +1,6 @@ import express from 'express' import bcrypt from 'bcryptjs' -import mongoose from 'mongoose' +import mongoose, { Mongoose } from 'mongoose' import jwt from 'jsonwebtoken' import Client from '../../model/Client' @@ -14,18 +14,32 @@ const clients: { [key: string]: string } = {} const clientIDs = new Set() const authCodes: { [key: string]: string } = {} -// connect to DB -mongoose.connect(process.env.DB_CONNECT as string, async (err) => { - if (err) throw err - - console.log('Connected to db!') - +export const populateClients = async () => { const result = await Client.find() + clientIDs.clear() result.forEach((r) => { clients[r.clientid] = r.clientsecret clientIDs.add(r.clientid) }) -}) +} + +export const connectDB = () => { + // NOTE: when exporting app.js as agent for supertest + // we should exlcude connecting to the real database + if (process.env.NODE_ENV !== 'test') { + mongoose.connect(process.env.DB_CONNECT as string, async (err) => { + if (err) throw err + + console.log('Connected to db!') + + await populateClients() + }) + } +} + +export const saveCode = (client_id: string, code: string) => + (authCodes[client_id] = code) +export const deleteCode = (client_id: string) => delete authCodes[client_id] const refreshTokens: string[] = [] @@ -37,14 +51,14 @@ authRouter.post('/authorize', async (req, res) => { // Authenticate User const user = await User.findOne({ username }) - if (!user) return res.status(400).send('Username is not found.') + if (!user) return res.status(403).send('Username is not found.') const validPass = await bcrypt.compare(password, user.password) - if (!validPass) return res.status(400).send('Invalid password.') + if (!validPass) return res.status(403).send('Invalid password.') // Verify client ID if (!clientIDs.has(client_id)) { - return res.sendStatus(403) + return res.status(403).send('Invalid client_id.') } // generate authorization code against client_id @@ -54,9 +68,10 @@ authRouter.post('/authorize', async (req, res) => { isadmin: user.isadmin, isactive: user.isactive } - authCodes[client_id] = generateAuthCode(userInfo) - res.json({ code: authCodes[client_id] }) + const code = saveCode(client_id, generateAuthCode(userInfo)) + + res.json({ code }) }) authRouter.post('/token', async (req, res) => { @@ -66,20 +81,21 @@ authRouter.post('/token', async (req, res) => { const { client_id, client_secret, code } = value const userInfo = await verifyAuthCode(client_id, client_secret, code) - if (userInfo) { - const accessToken = generateAccessToken(userInfo) - const refreshToken = jwt.sign( - userInfo, - process.env.REFRESH_TOKEN_SECRET as string - ) - refreshTokens.push(refreshToken) - delete authCodes[client_id] - - res.json({ accessToken: accessToken, refreshToken: refreshToken }) - } else { - res.sendStatus(403) + if (!userInfo) { + return res.sendStatus(403) } + + const accessToken = generateAccessToken(userInfo) + const refreshToken = jwt.sign( + userInfo, + process.env.REFRESH_TOKEN_SECRET as string + ) + refreshTokens.push(refreshToken) + + deleteCode(client_id) + + res.json({ accessToken: accessToken, refreshToken: refreshToken }) }) // authRouter.post('/refresh', (req, res) => { @@ -109,7 +125,7 @@ const generateAccessToken = (data: InfoJWT) => expiresIn: '1day' }) -const generateAuthCode = (data: InfoJWT) => +export const generateAuthCode = (data: InfoJWT) => jwt.sign(data, process.env.AUTH_CODE_SECRET as string, { expiresIn: '30s' }) diff --git a/src/routes/api/index.ts b/src/routes/api/index.ts index f5c0dd5..f25bbd0 100644 --- a/src/routes/api/index.ts +++ b/src/routes/api/index.ts @@ -6,9 +6,10 @@ import { InfoJWT } from '../../types' import driveRouter from './drive' import stpRouter from './stp' import userRouter from './user' +import authRouter, { connectDB } from './auth' dotenv.config() -import authRouter from './auth' +connectDB() const router = express.Router() diff --git a/src/routes/api/spec/auth.spec.ts b/src/routes/api/spec/auth.spec.ts new file mode 100644 index 0000000..f86f063 --- /dev/null +++ b/src/routes/api/spec/auth.spec.ts @@ -0,0 +1,276 @@ +import mongoose, { Mongoose } from 'mongoose' +import { MongoMemoryServer } from 'mongodb-memory-server' +import request from 'supertest' +import app from '../../../app' +import { createUser } from '../../../controllers/createUser' +import { createClient } from '../../../controllers/createClient' +import { generateAuthCode, populateClients, saveCode } from '../auth' +import { InfoJWT } from '../../../types' + +const client = { + clientid: 'someclientID', + clientsecret: 'someclientSecret' +} +// const adminUser = { +// displayname: 'Test Admin', +// username: 'testAdminUsername', +// password: '12345678', +// isadmin: true, +// isactive: true +// } +const user = { + displayname: 'Test User', + username: 'testUsername', + password: '87654321', + isadmin: false, + isactive: true +} + +describe('auth', () => { + let con: Mongoose + let mongoServer: MongoMemoryServer + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create() + con = await mongoose.connect(mongoServer.getUri()) + await createClient(client) + await populateClients() + }) + + afterAll(async () => { + if (con) { + await con.connection.dropDatabase() + await con.connection.close() + } + if (mongoServer) { + await mongoServer.stop() + } + }) + + describe('authorize', () => { + afterEach(async () => { + const collections = mongoose.connection.collections + const collection = collections['users'] + await collection.deleteMany({}) + }) + + it('should respond with authorization code', async () => { + await createUser(user) + + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + username: user.username, + password: user.password, + client_id: client.clientid + }) + .expect(200) + + expect(res.body).toHaveProperty('code') + }) + + it('should respond with Bad Request if username is missing', async () => { + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + password: user.password, + client_id: client.clientid + }) + .expect(400) + + expect(res.text).toEqual(`"username" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Bad Request if password is missing', async () => { + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + username: user.username, + client_id: client.clientid + }) + .expect(400) + + expect(res.text).toEqual(`"password" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Bad Request if client_id is missing', async () => { + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + username: user.username, + password: user.password + }) + .expect(400) + + expect(res.text).toEqual(`"client_id" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if username is incorrect', async () => { + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + username: user.username, + password: user.password, + client_id: client.clientid + }) + .expect(403) + + expect(res.text).toEqual('Username is not found.') + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if password is incorrect', async () => { + await createUser(user) + + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + username: user.username, + password: 'WrongPassword', + client_id: client.clientid + }) + .expect(403) + + expect(res.text).toEqual('Invalid password.') + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if client_id is incorrect', async () => { + await createUser(user) + + const res = await request(app) + .post('/SASjsApi/auth/authorize') + .send({ + username: user.username, + password: user.password, + client_id: 'WrongClientID' + }) + .expect(403) + + expect(res.text).toEqual('Invalid client_id.') + expect(res.body).toEqual({}) + }) + }) + + describe('token', () => { + const userInfo: InfoJWT = { + client_id: client.clientid, + username: user.username, + isadmin: user.isadmin, + isactive: user.isactive + } + beforeAll(async () => { + await createUser(user) + }) + afterAll(async () => { + const collections = mongoose.connection.collections + const collection = collections['users'] + await collection.deleteMany({}) + }) + + it('should respond with access and refresh tokens', async () => { + const code = saveCode(userInfo.client_id, generateAuthCode(userInfo)) + + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_id: client.clientid, + client_secret: client.clientsecret, + code + }) + .expect(200) + + expect(res.body).toHaveProperty('accessToken') + expect(res.body).toHaveProperty('refreshToken') + }) + + it('should respond with Bad Request if code is missing', async () => { + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_id: client.clientid, + client_secret: client.clientsecret + }) + .expect(400) + + expect(res.text).toEqual(`"code" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Bad Request if client_id is missing', async () => { + const code = saveCode(userInfo.client_id, generateAuthCode(userInfo)) + + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_secret: client.clientsecret, + code + }) + .expect(400) + + expect(res.text).toEqual(`"client_id" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Bad Request if client_secret is missing', async () => { + const code = saveCode(userInfo.client_id, generateAuthCode(userInfo)) + + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_id: client.clientid, + code + }) + .expect(400) + + expect(res.text).toEqual(`"client_secret" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if code is invalid', async () => { + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_id: client.clientid, + client_secret: client.clientsecret, + code: 'InvalidCode' + }) + .expect(403) + + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if client_id is invalid', async () => { + const code = saveCode(userInfo.client_id, generateAuthCode(userInfo)) + + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_id: 'WrongClientID', + client_secret: client.clientsecret, + code + }) + .expect(403) + + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if client_secret is invalid', async () => { + const code = saveCode(userInfo.client_id, generateAuthCode(userInfo)) + + const res = await request(app) + .post('/SASjsApi/auth/token') + .send({ + client_id: client.clientid, + client_secret: 'WrongClientSecret', + code + }) + .expect(403) + + expect(res.body).toEqual({}) + }) + }) +}) diff --git a/src/routes/api/user.ts b/src/routes/api/user.ts index 151d6fd..78bcb56 100644 --- a/src/routes/api/user.ts +++ b/src/routes/api/user.ts @@ -1,35 +1,15 @@ import express from 'express' -import bcrypt from 'bcryptjs' -import User from '../../model/User' +import { createUser } from '../../controllers/createUser' import { registerValidation } from '../../utils' const userRouter = express.Router() userRouter.post('/', async (req, res) => { - const { error, value } = registerValidation(req.body) + const { error, value: data } = registerValidation(req.body) if (error) return res.status(400).send(error.details[0].message) - const { displayname, username, password, isadmin, isactive } = value - - // Checking if user is already in the database - const usernameExist = await User.findOne({ username }) - if (usernameExist) return res.status(400).send('Username already exists.') - - // Hash passwords - const salt = await bcrypt.genSalt(10) - const hashPassword = await bcrypt.hash(password, salt) - - // Create a new user - const user = new User({ - displayname, - username, - password: hashPassword, - isadmin, - isactive - }) - try { - const savedUser = await user.save() + const savedUser = await createUser(data) res.send({ displayname: savedUser.displayname, username: savedUser.username,