diff --git a/package-lock.json b/package-lock.json index 3286025..a4c2120 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1781,6 +1781,27 @@ "node": ">=0.1.95" } }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "devOptional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "devOptional": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -3453,6 +3474,30 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "devOptional": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "devOptional": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "devOptional": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "devOptional": true + }, "node_modules/@types/applepayjs": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-3.0.4.tgz", @@ -3495,6 +3540,64 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, + "node_modules/@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", + "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, "node_modules/@types/eslint": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", @@ -3509,6 +3612,39 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, + "node_modules/@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "deprecated": "This is a stub types definition. expect provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "expect": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -3592,6 +3728,21 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", + "integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -3601,6 +3752,21 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" + "node_modules/@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "node_modules/@types/mongoose": { + "version": "5.11.97", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz", + "integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==", + "deprecated": "Mongoose publishes its own types, so you do not need to install this package.", + "dev": true, + "dependencies": { + "mongoose": "*" + } }, "node_modules/@types/node": { "version": "17.0.21", @@ -3617,6 +3783,47 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/passport": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", + "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.6.tgz", + "integrity": "sha512-cmAAMIRTaEwpqxlrZyiEY9kdibk94gP5KTF8AT1Ra4rWNZYHNMreqhKUEeC5WJtuN5SJZjPQmV+XO2P5PlnvNQ==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.34.tgz", + "integrity": "sha512-PSc07UdYx+jhadySxxIYWuv6sAnY5e+gesn/5lkPKfBeGuIYn9OPR+AAEDq73VRUh6NBTpvE/iPE62rzZUslog==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/prettier": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", @@ -3632,6 +3839,18 @@ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, "node_modules/@types/react": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz", @@ -3708,6 +3927,42 @@ "@types/react": "*" } }, + "node_modules/@types/request": { + "version": "2.48.8", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", + "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", + "dev": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request-promise": { + "version": "4.1.48", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.48.tgz", + "integrity": "sha512-sLsfxfwP5G3E3U64QXxKwA6ctsxZ7uKyl4I28pMj3JvV+ztWECRns73GL71KMOOJME5u1A5Vs5dkBqyiR1Zcnw==", + "dev": true, + "dependencies": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -3721,6 +3976,16 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -3731,11 +3996,27 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/superagent": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", + "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, "node_modules/@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", + "dev": true + }, "node_modules/@types/uglify-js": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", @@ -3752,6 +4033,11 @@ "node": ">=0.10.0" } }, + "node_modules/@types/validator": { + "version": "13.7.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.1.tgz", + "integrity": "sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q==" + }, "node_modules/@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -3996,6 +4282,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -4412,6 +4704,12 @@ "node": ">=10" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4607,6 +4905,15 @@ "inherits": "2.0.1" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -5234,7 +5541,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -5344,6 +5651,12 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -5723,6 +6036,42 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-http": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", + "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", + "dev": true, + "dependencies": { + "@types/chai": "4", + "@types/superagent": "^3.8.3", + "cookiejar": "^2.1.1", + "is-ip": "^2.0.0", + "methods": "^1.1.2", + "qs": "^6.5.1", + "superagent": "^3.7.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5760,6 +6109,15 @@ "node": ">=6" } }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -5769,13 +6127,13 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "devOptional": true, "funding": [ { "type": "individual", "url": "https://paulmillr.com/funding/" } ], - "optional": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6289,6 +6647,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "node_modules/cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, "node_modules/copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -6426,6 +6790,12 @@ "sha.js": "^2.4.8" } }, + "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==", + "devOptional": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7021,6 +7391,18 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -7359,6 +7741,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -9528,6 +9919,15 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -9777,6 +10177,16 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "dev": true, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9967,6 +10377,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -10186,6 +10605,15 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, "node_modules/growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -11158,7 +11586,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -11342,6 +11770,17 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=" + "node_modules/is-ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", + "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", + "dev": true, + "dependencies": { + "ip-regex": "^2.0.0" + }, + "engines": { + "node": ">=4" + } }, "node_modules/is-module": { "version": "1.0.0", @@ -11531,6 +11970,24 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "devOptional": true + }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -13987,6 +14444,11 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "node_modules/kareem": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz", @@ -14195,6 +14657,92 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/loglevel": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", @@ -14218,6 +14766,28 @@ "loose-envify": "cli.js" } }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "devOptional": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -14284,6 +14854,11 @@ "engines": { "node": ">= 10" } + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -14803,6 +15378,276 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/mocha/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/mongodb": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz", @@ -16301,6 +17146,15 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -18565,7 +19419,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -21153,6 +22007,75 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" }, + "node_modules/superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", + "dev": true, + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/superagent/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -21805,6 +22728,149 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "node_modules/ts-mocha": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-9.0.2.tgz", + "integrity": "sha512-WyQjvnzwrrubl0JT7EC1yWmNpcsU3fOuBFfdps30zbmFBgKniSaSOyZMZx+Wq7kytUs5CY+pEbSYEbGfIKnXTw==", + "dev": true, + "dependencies": { + "ts-node": "7.0.1" + }, + "bin": { + "ts-mocha": "bin/ts-mocha" + }, + "engines": { + "node": ">= 6.X.X" + }, + "optionalDependencies": { + "tsconfig-paths": "^3.5.0" + }, + "peerDependencies": { + "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X" + } + }, + "node_modules/ts-mocha/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ts-mocha/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-mocha/node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-mocha/node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "devOptional": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.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.0", + "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/ts-node/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "devOptional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "devOptional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -22298,6 +23364,12 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "devOptional": true + }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -22328,6 +23400,14 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -24079,6 +25159,12 @@ "microevent.ts": "~0.1.1" } }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -24227,6 +25313,71 @@ "node": ">=6" } }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": 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", @@ -24601,22 +25752,44 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/validator": "^13.7.1", "bcrypt": "^5.0.1", "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.6", "dotenv": "^16.0.0", "express": "^4.17.3", "jsonwebtoken": "^8.5.1", + "jwt-decode": "^3.1.2", "local": "^0.3.3", "mongoose": "^6.2.4", "passport": "^0.5.2", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "request": "^2.88.2", - "request-promise": "^4.2.6" + "request-promise": "^4.2.6", + "validator": "^13.7.0" }, "devDependencies": { - "prettier": "^2.5.1" + "@types/bcryptjs": "^2.4.2", + "@types/chai": "^4.3.0", + "@types/cookie-parser": "^1.4.2", + "@types/expect": "^24.3.0", + "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.28", + "@types/jsonwebtoken": "^8.5.8", + "@types/mocha": "^9.1.0", + "@types/mongoose": "^5.11.97", + "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/passport-local": "^1.0.34", + "@types/request-promise": "^4.1.48", + "chai": "^4.3.6", + "chai-http": "^4.3.0", + "mocha": "^9.2.2", + "prettier": "^2.5.1", + "ts-mocha": "^9.0.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.2" } } }, @@ -25807,6 +26980,21 @@ "minimist": "^1.2.0" } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "devOptional": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "devOptional": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -26947,6 +28135,30 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "devOptional": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "devOptional": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "devOptional": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "devOptional": true + }, "@types/applepayjs": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-3.0.4.tgz", @@ -26989,6 +28201,64 @@ "@babel/types": "^7.3.0" } }, + "@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, + "@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cookie-parser": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", + "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, "@types/eslint": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", @@ -27003,6 +28273,38 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, + "@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "dev": true, + "requires": { + "expect": "*" + } + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -27086,6 +28388,21 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "@types/jsonwebtoken": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", + "integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -27095,6 +28412,20 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" + "@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "@types/mongoose": { + "version": "5.11.97", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz", + "integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==", + "dev": true, + "requires": { + "mongoose": "*" + } }, "@types/node": { "version": "17.0.21", @@ -27111,6 +28442,47 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/passport": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", + "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/passport-jwt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.6.tgz", + "integrity": "sha512-cmAAMIRTaEwpqxlrZyiEY9kdibk94gP5KTF8AT1Ra4rWNZYHNMreqhKUEeC5WJtuN5SJZjPQmV+XO2P5PlnvNQ==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-local": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.34.tgz", + "integrity": "sha512-PSc07UdYx+jhadySxxIYWuv6sAnY5e+gesn/5lkPKfBeGuIYn9OPR+AAEDq73VRUh6NBTpvE/iPE62rzZUslog==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, "@types/prettier": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", @@ -27126,6 +28498,18 @@ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, "@types/react": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz", @@ -27202,6 +28586,41 @@ "@types/react": "*" } }, + "@types/request": { + "version": "2.48.8", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", + "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/request-promise": { + "version": "4.1.48", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.48.tgz", + "integrity": "sha512-sLsfxfwP5G3E3U64QXxKwA6ctsxZ7uKyl4I28pMj3JvV+ztWECRns73GL71KMOOJME5u1A5Vs5dkBqyiR1Zcnw==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -27215,6 +28634,16 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -27225,11 +28654,27 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "@types/superagent": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", + "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, "@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, + "@types/tough-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", + "dev": true + }, "@types/uglify-js": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", @@ -27245,6 +28690,11 @@ } } }, + "@types/validator": { + "version": "13.7.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.1.tgz", + "integrity": "sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q==" + }, "@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -27405,6 +28855,12 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -27862,20 +29318,42 @@ "adyen-demo-server": { "version": "file:packages/server", "requires": { + "@types/bcryptjs": "*", + "@types/chai": "^4.3.0", + "@types/cookie-parser": "^1.4.2", + "@types/expect": "^24.3.0", + "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.28", + "@types/jsonwebtoken": "^8.5.8", + "@types/mocha": "^9.1.0", + "@types/mongoose": "^5.11.97", + "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/passport-local": "^1.0.34", + "@types/request-promise": "^4.1.48", + "@types/validator": "^13.7.1", "bcrypt": "^5.0.1", "bcryptjs": "^2.4.3", + "chai": "^4.3.6", + "chai-http": "^4.3.0", "cookie-parser": "^1.4.6", "dotenv": "^16.0.0", "express": "^4.17.3", "jsonwebtoken": "^8.5.1", + "jwt-decode": "^3.1.2", "local": "^0.3.3", + "mocha": "^9.2.2", "mongoose": "^6.2.4", "passport": "^0.5.2", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "prettier": "^2.5.1", "request": "^2.88.2", - "request-promise": "^4.2.6" + "request-promise": "^4.2.6", + "ts-mocha": "^9.0.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.2", + "validator": "^13.7.0" } }, "agent-base": { @@ -27994,6 +29472,12 @@ "readable-stream": "^3.6.0" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -28148,6 +29632,12 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -28637,7 +30127,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true + "devOptional": true }, "bindings": { "version": "1.5.0", @@ -28742,6 +30232,12 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -29031,6 +30527,36 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chai-http": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", + "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", + "dev": true, + "requires": { + "@types/chai": "4", + "@types/superagent": "^3.8.3", + "cookiejar": "^2.1.1", + "is-ip": "^2.0.0", + "methods": "^1.1.2", + "qs": "^6.5.1", + "superagent": "^3.7.0" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -29058,6 +30584,12 @@ "resolved": "https://registry.npmjs.org/charcodes/-/charcodes-0.2.0.tgz", "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==" }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -29067,7 +30599,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "optional": true, + "devOptional": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -29482,6 +31014,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -29599,6 +31137,12 @@ "sha.js": "^2.4.8" } }, + "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==", + "devOptional": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -30050,6 +31594,15 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -30308,6 +31861,12 @@ } } }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -31955,6 +33514,12 @@ "path-exists": "^4.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -32153,6 +33718,12 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "dev": true + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -32313,6 +33884,12 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -32473,6 +34050,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -33226,7 +34809,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, + "devOptional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -33350,6 +34933,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=" + "is-ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", + "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", + "dev": true, + "requires": { + "ip-regex": "^2.0.0" + } }, "is-module": { "version": "1.0.0", @@ -33473,6 +35064,18 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "devOptional": true + }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -35305,6 +36908,11 @@ "safe-buffer": "^5.0.1" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "kareem": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz", @@ -35489,6 +37097,67 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "loglevel": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", @@ -35502,6 +37171,25 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "devOptional": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -35556,6 +37244,11 @@ "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true }, "makeerror": { "version": "1.0.12", @@ -35953,6 +37646,204 @@ "minimist": "^1.2.5" } }, + "mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, "mongodb": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz", @@ -37162,6 +39053,12 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -38955,7 +40852,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, + "devOptional": true, "requires": { "picomatch": "^2.2.1" } @@ -41034,6 +42931,70 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -41541,6 +43502,93 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "ts-mocha": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-9.0.2.tgz", + "integrity": "sha512-WyQjvnzwrrubl0JT7EC1yWmNpcsU3fOuBFfdps30zbmFBgKniSaSOyZMZx+Wq7kytUs5CY+pEbSYEbGfIKnXTw==", + "dev": true, + "requires": { + "ts-node": "7.0.1", + "tsconfig-paths": "^3.5.0" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } + }, + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "devOptional": true, + "requires": { + "@cspotcode/source-map-support": "0.7.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.0", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "devOptional": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "devOptional": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true + } + } + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -41922,6 +43970,12 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" }, + "v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "devOptional": true + }, "v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -41948,6 +44002,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -43385,6 +45444,12 @@ "microevent.ts": "~0.1.1" } }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -43497,6 +45562,44 @@ "decamelize": "^1.2.0" } }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx deleted file mode 100644 index c0eb19b..0000000 --- a/packages/client/src/App.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Header } from './components'; -import ApplicationRouter from './AppRouter'; - -const Application = () => { - const [options, setOptions] = useState({ - value: 25, - currency: 'EUR', - countryCode: 'NL', - component: 'dropin' - }); - const navigate = useNavigate(); - - const handleSubmit = () => { - navigate(options.component); - }; - - const handleChange = (e: React.ChangeEvent) => { - setOptions(prevState => ({ - ...prevState, - [e.target.name]: e.target.value - })); - }; - - return ( -
-
- -
- ); -}; - -export default Application; diff --git a/packages/client/src/AppRouter.tsx b/packages/client/src/AppRouter.tsx deleted file mode 100644 index 8f954fc..0000000 --- a/packages/client/src/AppRouter.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Route, Routes } from 'react-router-dom'; -import { PaymentsFormProps } from './types'; -import { PaymentsForm, ComponentBase, CheckoutBuilder } from './components'; - -const ApplicationRouter = ({ options, onSubmit, onChange }: PaymentsFormProps) => { - return ( - - } /> - } /> - - ); -}; - -export default ApplicationRouter; diff --git a/packages/client/src/app/actions.ts b/packages/client/src/app/actions.ts new file mode 100644 index 0000000..fcf79c7 --- /dev/null +++ b/packages/client/src/app/actions.ts @@ -0,0 +1 @@ +export { userActions, configurationActions } from './reducers'; diff --git a/packages/client/src/app/index.ts b/packages/client/src/app/index.ts new file mode 100644 index 0000000..5253f17 --- /dev/null +++ b/packages/client/src/app/index.ts @@ -0,0 +1,5 @@ +export * as actions from './actions'; +export * as selectors from './selectors'; +export { userReducer, configurationReducer } from './reducers'; + +export type { ConfigurationState, UserState } from './types'; diff --git a/packages/client/src/app/reducers/configuration.ts b/packages/client/src/app/reducers/configuration.ts new file mode 100644 index 0000000..602b795 --- /dev/null +++ b/packages/client/src/app/reducers/configuration.ts @@ -0,0 +1,26 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import type { ConfigurationState } from '../types'; + +const initialState: ConfigurationState = { + id: '', + owner: '', + name: '', + version: 0, + configuration: '' +}; + +export const configurationSlice = createSlice({ + name: 'configuration', + initialState, + reducers: { + updateConfigurationInfo: (state, action: PayloadAction) => { + state = action.payload; + }, + clearConfigurationInfo: state => { + state = initialState; + } + } +}); + +export const { actions, reducer } = configurationSlice; diff --git a/packages/client/src/app/reducers/index.ts b/packages/client/src/app/reducers/index.ts new file mode 100644 index 0000000..8d75807 --- /dev/null +++ b/packages/client/src/app/reducers/index.ts @@ -0,0 +1,2 @@ +export { reducer as userReducer, actions as userActions } from './user'; +export { reducer as configurationReducer, actions as configurationActions } from './configuration'; diff --git a/packages/client/src/app/reducers/user.ts b/packages/client/src/app/reducers/user.ts new file mode 100644 index 0000000..107b99d --- /dev/null +++ b/packages/client/src/app/reducers/user.ts @@ -0,0 +1,23 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { UserState } from '../types'; + +const initialState: UserState = { + id: '', + username: '', + configurations: [] +}; + +export const userSlice = createSlice({ + name: 'user', + initialState, + reducers: { + updateUserInfo: (state, action: PayloadAction) => { + state = action.payload; + }, + clearUserInfo: state => { + state = initialState; + } + } +}); + +export const { actions, reducer } = userSlice; diff --git a/packages/client/src/app/selectors.ts b/packages/client/src/app/selectors.ts new file mode 100644 index 0000000..459a73f --- /dev/null +++ b/packages/client/src/app/selectors.ts @@ -0,0 +1,5 @@ +import type { RootState } from '../store'; + +export const selectUserState = (state: RootState) => state.user; + +export const selectConfigurationState = (state: RootState) => state.configuration; diff --git a/packages/client/src/app/types.ts b/packages/client/src/app/types.ts new file mode 100644 index 0000000..5695e73 --- /dev/null +++ b/packages/client/src/app/types.ts @@ -0,0 +1,13 @@ +export type ConfigurationState = { + id: string; + owner: string; + name: string; + version: number; + configuration: string; +}; + +export type UserState = { + id: string; + username: string; + configurations: [ConfigurationState] | []; +}; diff --git a/packages/client/src/components/App.tsx b/packages/client/src/components/App.tsx new file mode 100644 index 0000000..4ca219d --- /dev/null +++ b/packages/client/src/components/App.tsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Header } from '.'; +import ApplicationRouter from './AppRouter'; + +const Application = () => { + const [options, setOptions] = useState({ + value: 25, + currency: 'EUR', + countryCode: 'NL', + component: 'dropin' + }); + const navigate = useNavigate(); + + const handleSubmit = () => { + navigate(options.component); + }; + + const handleChange = (e: React.ChangeEvent) => { + setOptions(prevState => ({ + ...prevState, + [e.target.name]: e.target.value + })); + }; + + return ( +
+
+ +
+ ); +}; + +export default Application; diff --git a/packages/client/src/components/AppRouter.tsx b/packages/client/src/components/AppRouter.tsx new file mode 100644 index 0000000..d27cf37 --- /dev/null +++ b/packages/client/src/components/AppRouter.tsx @@ -0,0 +1,14 @@ +import { Route, Routes } from 'react-router-dom'; +import { PaymentsFormProps } from './types'; +import { CheckoutBuilder,PaymentsForm, ComponentBase } from '.'; + +const ApplicationRouter = ({ options, onSubmit, onChange }: any) => { + return ( + + } /> + } /> + + ); +}; + +export default ApplicationRouter; diff --git a/packages/client/src/components/CheckoutBuilder/ApiConfig.tsx b/packages/client/src/components/CheckoutBuilder/ApiConfig.tsx index 2ccae88..e8e25e6 100644 --- a/packages/client/src/components/CheckoutBuilder/ApiConfig.tsx +++ b/packages/client/src/components/CheckoutBuilder/ApiConfig.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { useEffect, useState } from 'react'; import { getSessions_Response } from '../../helpers/payloadSamples'; -import { CheckoutBuilderProps } from '../../types'; import EditOptions from './EditOptions'; const ApiConfig = (props: any) => { diff --git a/packages/client/src/components/CheckoutBuilder/CheckoutBuilder.tsx b/packages/client/src/components/CheckoutBuilder/CheckoutBuilder.tsx index 31e6d7d..d852ac7 100644 --- a/packages/client/src/components/CheckoutBuilder/CheckoutBuilder.tsx +++ b/packages/client/src/components/CheckoutBuilder/CheckoutBuilder.tsx @@ -9,7 +9,6 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; import * as React from 'react'; import { useState } from 'react'; -import { PaymentsFormProps } from '../../types'; import ApiConfig from './ApiConfig'; import OptionalConfig from './OptionalConfig'; import ProfileForm from './ProfileForm'; @@ -20,7 +19,7 @@ const theme = createTheme(); //Create init config class -const CheckoutBuilder = ({ options: { value, currency, countryCode, component }, onSubmit, onChange }: PaymentsFormProps) => { +const CheckoutBuilder = ({ options: { value, currency, countryCode, component }, onSubmit, onChange }: any) => { const [activeStep, setActiveStep] = useState(0); const [configuration, setConfiguration] = useState({ name: '', diff --git a/packages/client/src/components/CheckoutBuilder/OptionalConfig.tsx b/packages/client/src/components/CheckoutBuilder/OptionalConfig.tsx index b326219..3510479 100644 --- a/packages/client/src/components/CheckoutBuilder/OptionalConfig.tsx +++ b/packages/client/src/components/CheckoutBuilder/OptionalConfig.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; -import EditOptions from './EditOptions'; -import { CheckoutBuilderProps } from '../../types'; -import { getClientConfiguration_Response } from '../../helpers/payloadSamples'; import { useEffect, useState } from 'react'; +import { getClientConfiguration_Response } from '../../helpers/payloadSamples'; +import EditOptions from './EditOptions'; const OptionalConfig = (props: any) => { const { configuration, setConfiguration } = props; diff --git a/packages/client/src/components/CheckoutBuilder/ProfileForm.tsx b/packages/client/src/components/CheckoutBuilder/ProfileForm.tsx index 90c3499..e09e8ec 100644 --- a/packages/client/src/components/CheckoutBuilder/ProfileForm.tsx +++ b/packages/client/src/components/CheckoutBuilder/ProfileForm.tsx @@ -7,7 +7,6 @@ import Select, { SelectChangeEvent } from '@mui/material/Select'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import * as React from 'react'; -import { CheckoutBuilderProps } from '../../types'; const ProfileForm = (props: any) => { const { configuration, setConfiguration } = props; diff --git a/packages/client/src/components/ComponentBase/Component.tsx b/packages/client/src/components/ComponentBase/Component.tsx index 4970f8e..ca26aaf 100644 --- a/packages/client/src/components/ComponentBase/Component.tsx +++ b/packages/client/src/components/ComponentBase/Component.tsx @@ -1,18 +1,20 @@ import { useSearchParams } from 'react-router-dom'; import { useCheckout } from '../../hooks'; +import type { EditableCheckoutConfigFields } from '../../hooks/types'; -const Component = ({ type, sessionId, sessionData }: { type: string; sessionId: string; sessionData: string }) => { - const [redirectInfo] = useSearchParams(); - const redirectResult = { - redirectResult: redirectInfo.get('redirectResult'), - redirectSessionId: redirectInfo.get('sessionId') - }; - const [checkout] = useCheckout({ sessionId, sessionData, redirectResult }); +const Component = ({ type, options }: { type: string; options: EditableCheckoutConfigFields }) => { + //TODO: move to own redirect handling component with useRedirect + const [redirectInfo] = useSearchParams(); + const redirectResult = { + redirectResult: redirectInfo.get('redirectResult'), + redirectSessionId: redirectInfo.get('sessionId') + }; + const [checkout] = useCheckout(options); - if (checkout) { - checkout.create(type).mount('#checkout'); - } - return
; + if (checkout) { + checkout.create(type).mount('#checkout'); + } + return
; }; export default Component; diff --git a/packages/client/src/components/ComponentBase/ComponentBase.tsx b/packages/client/src/components/ComponentBase/ComponentBase.tsx index d667666..a03c553 100644 --- a/packages/client/src/components/ComponentBase/ComponentBase.tsx +++ b/packages/client/src/components/ComponentBase/ComponentBase.tsx @@ -1,21 +1,16 @@ import { useParams } from 'react-router-dom'; -import { useStartSession } from '../../hooks'; -import { FormDataProps } from '../../types'; +import { useInitializeCheckout } from '../../hooks'; +import { InitializationRequest } from '../../hooks/types'; import Component from './Component'; -const ComponentBase = ({ value, currency, countryCode }: FormDataProps) => { - const params = useParams(); - const component = params.component; - const [sessionInfo] = useStartSession({ - value, - currency, - countryCode, - component - }); - if (sessionInfo && component) { - return ; - } - return
Loading...
; +const ComponentBase = (options: InitializationRequest, endpoint: string) => { + const params = useParams(); + const component = params.component; + const [checkoutInfo] = useInitializeCheckout(options, component, endpoint); + if (checkoutInfo && component) { + return ; + } + return
Loading...
; }; export default ComponentBase; diff --git a/packages/client/src/components/PaymentsForm/PaymentsForm.tsx b/packages/client/src/components/PaymentsForm/PaymentsForm.tsx index e28b7c7..3c82d39 100644 --- a/packages/client/src/components/PaymentsForm/PaymentsForm.tsx +++ b/packages/client/src/components/PaymentsForm/PaymentsForm.tsx @@ -1,19 +1,17 @@ import React from 'react'; -import { PaymentsFormProps } from '../../types'; +import { PaymentsFormProps } from '../types'; -const PaymentsForm = ({ options: { value, currency, countryCode, component }, onSubmit, onChange }: PaymentsFormProps) => { - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - onSubmit(); - }; +const PaymentsForm = ({ options: { value, currency, countryCode, component }, onSubmit, onChange }: any) => { + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSubmit(); + }; - const handleChange = (e: React.ChangeEvent) => { - onChange(e); - }; + const handleChange = (e: React.ChangeEvent) => { + onChange(e); + }; - return ( -
PaymentForm
- ); + return
PaymentForm
; }; export default PaymentsForm; diff --git a/packages/client/src/components/types.ts b/packages/client/src/components/types.ts new file mode 100644 index 0000000..a64f448 --- /dev/null +++ b/packages/client/src/components/types.ts @@ -0,0 +1,15 @@ +export type FormDataProps = { + amount: { + value: number; + currency: string; + }; + countryCode: string; + endpoint?: string; + component?: string; +}; + +export type PaymentsFormProps = { + options: FormDataProps; + onSubmit: () => void; + onChange: (e: React.ChangeEvent) => void; +}; diff --git a/packages/client/src/config.js b/packages/client/src/config.ts similarity index 100% rename from packages/client/src/config.js rename to packages/client/src/config.ts diff --git a/packages/client/src/helpers/index.ts b/packages/client/src/helpers/index.ts index 03474c2..39d4939 100644 --- a/packages/client/src/helpers/index.ts +++ b/packages/client/src/helpers/index.ts @@ -1,22 +1,31 @@ -import { prepareChallengeData } from '@adyen/adyen-web/dist/types/components/ThreeDS2/components/utils'; -import { FormDataProps } from '../types'; +import { FormDataProps } from '../components/types'; export const compareFormData = (prev: any, next: FormDataProps) => { - if (!prev) { - return false; - } + if (!prev) { + return false; + } - const valueMatch = prev.value && prev.value === next.value; - const currencyMatch = prev.currency && prev.currency === next.currency; - const countryCodeMatch = prev.countryCode && prev.countryCode === next.countryCode; + const valueMatch = prev.amount.value && prev.amount.value === next.amount.value; + const currencyMatch = prev.amout.currency && prev.amount.currency === next.amount.currency; + const countryCodeMatch = prev.countryCode && prev.countryCode === next.countryCode; - return countryCodeMatch && currencyMatch && valueMatch; + return countryCodeMatch && currencyMatch && valueMatch; }; export const compareSessionData = (prev: any, next: { sessionId: string }) => { - if (!prev) { - return false; - } + if (!prev) { + return false; + } - return prev.sessionId && prev.sessionId === next.sessionId; + return prev.sessionId && prev.sessionId === next.sessionId; +}; + +export const compareCheckoutData = (prev: any, next: [any]) => { + if (!prev) { + return false; + } + + const paymentMethodNames = next.map(pm => pm.name); + + return Array.isArray(prev) && Array.isArray(paymentMethodNames) && prev.every((name, i) => name === paymentMethodNames[i]); }; diff --git a/packages/client/src/hooks/checkout/useCheckout.ts b/packages/client/src/hooks/checkout/useCheckout.ts new file mode 100644 index 0000000..b8d98f3 --- /dev/null +++ b/packages/client/src/hooks/checkout/useCheckout.ts @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react'; +import AdyenCheckout from '@adyen/adyen-web'; +import { useMemoCompare } from '../helpers/useMemoCompare'; +import { compareCheckoutData } from '../../helpers'; +import type { CheckoutConfig, EditableCheckoutConfigFields } from '../types'; + +export const useCheckout = (options: EditableCheckoutConfigFields) => { + const [checkout, setCheckout] = useState(null); + + // creates ref and uses data compare callback to decide if re-rendering should occur. Without this, there is an infinite loop. + const opts = useMemoCompare(options, compareCheckoutData); + + useEffect(() => { + let configuration: CheckoutConfig; + if (options.session) { + configuration = { + ...options, + onPaymentCompleted: (result, component) => { + console.info(result, component); + }, + onError: (error, component) => { + console.error(error.name, error.message, error.stack, component); + } + }; + } else { + configuration = { + ...options, + onSubmit: (state, dropin) => { + console.info(state, dropin); + }, + onChange: (state, dropin) => { + console.info(state, dropin); + }, + onAdditionalDetails: (state, dropin) => { + console.info(state, dropin); + }, + onError: error => { + console.error(error.name, error.message, error.stack); + } + }; + } + const initializeCheckout: (config: object) => void = async config => { + const component = await AdyenCheckout(config); + + setCheckout(component); + }; + + initializeCheckout(configuration); + }, [opts]); + + return [checkout]; +}; diff --git a/packages/client/src/hooks/checkout/useInitializeCheckout.ts b/packages/client/src/hooks/checkout/useInitializeCheckout.ts new file mode 100644 index 0000000..7279f79 --- /dev/null +++ b/packages/client/src/hooks/checkout/useInitializeCheckout.ts @@ -0,0 +1,40 @@ +import { useState, useEffect } from 'react'; +import { useMemoCompare } from '../helpers/useMemoCompare'; +import { compareFormData } from '../../helpers'; +import type { InitializationRequest } from '../types'; + +export const useInitializeCheckout = (options: InitializationRequest, component?: string, endpoint?: string) => { + const [checkoutResponse, setCheckoutResponse] = useState(null); + + // creates ref and uses data compare callback to decide if re-rendering should occur. Without this, there is an infinite loop. + const opts = useMemoCompare(options, compareFormData); + + useEffect(() => { + const requestOptions = { + method: 'POST', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify(options) + }; + + const initialize: () => void = async () => { + try { + const response = await fetch(`http://localhost:8080/${endpoint}`, requestOptions); + + const parsed = await response.json(); + setCheckoutResponse(parsed); + } catch (err) { + if (err && typeof err === 'object') { + console.error('Error', err); + } else { + console.error('Something went wrong'); + } + } + }; + + initialize(); + }, [opts, endpoint]); + + return [checkoutResponse]; +}; diff --git a/packages/client/src/hooks/checkout/useRedirect.ts b/packages/client/src/hooks/checkout/useRedirect.ts new file mode 100644 index 0000000..cdae71d --- /dev/null +++ b/packages/client/src/hooks/checkout/useRedirect.ts @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react'; +import AdyenCheckout from '@adyen/adyen-web'; +import { useMemoCompare } from '../helpers/useMemoCompare'; +import { compareSessionData } from '../../helpers'; +import { CLIENT_KEY, ENVIRONMENT } from '../../config'; +import type { EditableCheckoutConfigFields, CheckoutConfig } from '../types'; + +export const useRedirect = (options: EditableCheckoutConfigFields) => { + const [checkout, setCheckout] = useState(null); + + // creates ref and uses data compare callback to decide if re-rendering should occur. Without this, there is an infinite loop. + const opts = useMemoCompare(options, compareSessionData); + + // TODO: This config will be brought in from front end. Add as argument above + useEffect(() => { + const { session: sessionInfo, redirectResult } = options; + + let session; + + if (redirectResult && redirectResult.redirectSessionId) { + session = { id: redirectResult.redirectSessionId }; + } else if (sessionInfo) { + session = sessionInfo; + } + + const configuration: CheckoutConfig = { + ...options, + ...session, + onPaymentCompleted: (result, component) => { + console.info(result, component); + }, + onError: (error, component) => { + console.error(error.name, error.message, error.stack, component); + } + }; + + const initializeCheckout: (config: object) => void = async config => { + const component = await AdyenCheckout(config); + if (redirectResult && redirectResult.redirectResult && redirectResult.redirectSessionId) { + component.submitDetails({ + details: { redirectResult: redirectResult.redirectResult } + }); + } + + setCheckout(component); + }; + + initializeCheckout(configuration); + }, [opts]); + + return [checkout]; +}; diff --git a/packages/client/src/hooks/useMemoCompare.ts b/packages/client/src/hooks/helpers/useMemoCompare.ts similarity index 100% rename from packages/client/src/hooks/useMemoCompare.ts rename to packages/client/src/hooks/helpers/useMemoCompare.ts diff --git a/packages/client/src/hooks/index.ts b/packages/client/src/hooks/index.ts index 12cfdd7..5bcf302 100644 --- a/packages/client/src/hooks/index.ts +++ b/packages/client/src/hooks/index.ts @@ -1,4 +1,10 @@ -import { useCheckout } from './useCheckout'; -import { useStartSession } from './useStartSession'; +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from '../store'; -export { useCheckout, useStartSession }; +export type { InitializationRequest, EditableCheckoutConfigFields, CheckoutConfig, PaymentAmount, PaymentMethodsResponseInterface } from './types'; + +export { useCheckout } from './checkout/useCheckout'; +export { useInitializeCheckout } from './checkout/useInitializeCheckout'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/packages/client/src/hooks/types.ts b/packages/client/src/hooks/types.ts new file mode 100644 index 0000000..6c5d0ae --- /dev/null +++ b/packages/client/src/hooks/types.ts @@ -0,0 +1,43 @@ +import type { PaymentAmount, PaymentMethodsResponseInterface } from '@adyen/adyen-web/dist/types/types'; + +export type InitializationRequest = { + merchantAccount: string; + amount: PaymentAmount; + returnUrl: string; + reference: string; + expiresAt?: Date; + countryCode?: string; + shopperLocale?: string; + shopperEmail?: string; + shopperIP?: string; + shopperReference?: string; +}; + +export interface EditableCheckoutConfigFields { + session?: { + id: string; + data?: string; + }; + paymentMethodsResponse?: PaymentMethodsResponseInterface; + redirectResult?: { + redirectResult: string; + redirectSessionId: string; + }; + environment: string; + clientKey: string; + paymentMethodsConfiguration?: object; + amount?: PaymentAmount; + showPayButton?: boolean; +} + +export interface CheckoutConfig extends EditableCheckoutConfigFields { + onChange?: (state: any, element: any) => void; + onValid?: (state: any, element: any) => void; + onSubmit?: (state: any, element: any) => void; + onComplete?: (state: any, element: any) => void; + onAdditionalDetails?: (state: any, element: any) => void; + onError?: (error: any, element?: any) => void; + onPaymentCompleted?: (result: any, element: any) => void; +} + +export { PaymentAmount, PaymentMethodsResponseInterface }; diff --git a/packages/client/src/hooks/useCheckout.ts b/packages/client/src/hooks/useCheckout.ts deleted file mode 100644 index 00b85f1..0000000 --- a/packages/client/src/hooks/useCheckout.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useState, useEffect } from 'react'; -import AdyenCheckout from '@adyen/adyen-web'; -import { useMemoCompare } from './useMemoCompare'; -import { compareSessionData } from '../helpers'; -import { CLIENT_KEY, ENVIRONMENT } from '../config'; - -type SessionDataConfig = { - id: string; - sessionData?: string; -}; - -type CheckoutConfiguration = { - environment: string; - clientKey: string; - session: SessionDataConfig; - onPaymentCompleted: (result: any, component: any) => void; - onError: (result: any, component: any) => void; -}; - -export const useCheckout = (options: { - sessionId: string; - sessionData: string; - redirectResult?: { - redirectResult: string | null; - redirectSessionId: string | null; - }; -}) => { - const [checkout, setCheckout] = useState(null); - - // creates ref and uses data compare callback to decide if re-rendering should occur. Without this, there is an infinite loop. - const opts = useMemoCompare(options, compareSessionData); - - // TODO: This config will be brought in from front end. Add as argument above - useEffect(() => { - const { sessionId, sessionData, redirectResult } = options; - - let session: SessionDataConfig = { - id: sessionId - }; - - if (redirectResult && redirectResult.redirectSessionId) { - session = { id: redirectResult.redirectSessionId }; - } else { - session.sessionData = sessionData; - } - const configuration: CheckoutConfiguration = { - environment: ENVIRONMENT, - clientKey: CLIENT_KEY, // Public key used for client-side authentication: https://docs.adyen.com/development-resources/client-side-authentication - session, - onPaymentCompleted: (result, component) => { - console.info(result, component); - }, - onError: (error, component) => { - console.error(error.name, error.message, error.stack, component); - } - }; - const initializeCheckout: (config: object) => void = async config => { - const component = await AdyenCheckout(config); - if (redirectResult && redirectResult.redirectResult && redirectResult.redirectSessionId) { - component.submitDetails({ - details: { redirectResult: redirectResult.redirectResult } - }); - } - - setCheckout(component); - }; - - initializeCheckout(configuration); - }, [opts]); - - return [checkout]; -}; diff --git a/packages/client/src/hooks/useStartSession.ts b/packages/client/src/hooks/useStartSession.ts deleted file mode 100644 index ce4a08b..0000000 --- a/packages/client/src/hooks/useStartSession.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useMemoCompare } from './useMemoCompare'; -import { compareFormData } from '../helpers'; -import { MERCHANT_ACCOUNT, RETURN_URL_BASE } from '../config'; - -type SessionConfig = { - merchantAccount: string; - amount: { - value: number; - currency: string; - }; - returnUrl: string; - reference: string; - countryCode: string; -}; - -export const useStartSession = (options: { value: number; currency: string; countryCode: string; component: string | undefined }) => { - const [sessionInfo, setSessionInfo] = useState(null); - - // creates ref and uses data compare callback to decide if re-rendering should occur. Without this, there is an infinite loop. - const opts = useMemoCompare(options, compareFormData); - - useEffect(() => { - const paymentData: SessionConfig = { - merchantAccount: MERCHANT_ACCOUNT, - amount: { - value: options.value * 100, - currency: options.currency - }, - returnUrl: `${RETURN_URL_BASE}/${options.component}`, - reference: `${Math.floor(Math.random() * 100000000)}`, - countryCode: options.countryCode - }; - - const requestOptions = { - method: 'POST', - headers: { - 'Content-type': 'application/json' - }, - body: JSON.stringify(paymentData) - }; - - const startSession: () => void = async () => { - try { - const response = await fetch('http://localhost:8080/startSession', requestOptions); - - const parsed = await response.json(); - setSessionInfo(parsed); - } catch (err) { - if (err && typeof err === 'object') { - console.error('Error', err); - } else { - console.error('Something went wrong'); - } - } - }; - - startSession(); - }, [opts]); - - return [sessionInfo]; -}; diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx index f17d9ec..7a6f73d 100644 --- a/packages/client/src/index.tsx +++ b/packages/client/src/index.tsx @@ -1,13 +1,19 @@ import { render } from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; +import { Provider } from 'react-redux'; + import '@adyen/adyen-web/dist/adyen.css'; import './index.scss'; -import App from './App'; + +import { store } from './store'; +import App from './components/App'; const rootElement = document.getElementById('root'); render( + - - , - rootElement + + + , + rootElement ); diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts new file mode 100644 index 0000000..543a799 --- /dev/null +++ b/packages/client/src/store.ts @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { userReducer, configurationReducer } from './app'; + +export const store = configureStore({ + reducer: { + user: userReducer, + configuration: configurationReducer + } +}); + +export type RootState = ReturnType; + +export type AppDispatch = typeof store.dispatch; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts deleted file mode 100644 index 3b3edb3..0000000 --- a/packages/client/src/types.ts +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -export type FormDataProps = { - value: number; - currency: string; - countryCode: string; - component?: string; -}; - -export type PaymentsFormProps = { - options: FormDataProps; - onSubmit: () => void; - onChange: (e: React.ChangeEvent) => void; -}; - -export type CheckoutBuilderProps = { - configuration: { - name: string; - product: string; - checkout_version: string; - dropin_version: string; - optionalConfiguration: any; - apiConfiguration: any; - }; - setConfiguration: any; -}; - -type SessionConfig = { - merchantAccount: string; - amount: { - value: number; - currency: string; - }; - returnUrl: string; - reference: string; - countryCode: string; -}; diff --git a/packages/server/config.js b/packages/server/config.js deleted file mode 100644 index 449ad58..0000000 --- a/packages/server/config.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - PORT: process.env.PORT || 8080, - ADYEN_API_KEY: process.env.ADYEN_API_KEY || null, - ADYEN_BASE_URL: - process.env.ADYEN_BASE_URL || "https://checkout-test.adyen.com", - DATABASE_URL: process.env.DATABASE_URL || "mongodb://localhost/my-store", - TEST_DATABASE_URL: - process.env.TEST_DATABASE_URL || "mongodb://localhost/my-store-test", - JWT_SECRET: process.env.JWT_SECRET || "PROJECT_AW_ULTRA", - JWT_EXPIRY: process.env.JWT_EXPIRY || "1d", -}; diff --git a/packages/server/config.ts b/packages/server/config.ts new file mode 100644 index 0000000..cd39a82 --- /dev/null +++ b/packages/server/config.ts @@ -0,0 +1,7 @@ +export const PORT = process.env.PORT || 8080; +export const ADYEN_API_KEY = process.env.ADYEN_API_KEY || ''; +export const ADYEN_BASE_URL = process.env.ADYEN_BASE_URL || 'https://checkout-test.adyen.com'; +export const DATABASE_URL = process.env.DATABASE_URL || 'mongodb://localhost/adyen-demo'; +export const TEST_DATABASE_URL = process.env.TEST_DATABASE_URL || 'mongodb://localhost/adyen-demo-test'; +export const JWT_SECRET = process.env.JWT_SECRET || 'PROJECT_AW_ULTRA'; +export const JWT_EXPIRY = process.env.JWT_EXPIRY || '1d'; diff --git a/packages/server/db-mongoose.js b/packages/server/db-mongoose.js deleted file mode 100644 index 9fa17a2..0000000 --- a/packages/server/db-mongoose.js +++ /dev/null @@ -1,31 +0,0 @@ -const mongoose = require('mongoose'); -mongoose.Promise = global.Promise; - -const { DATABASE_URL } = require('./config'); - -const mongoOptions = { - useNewUrlParser: true, - useUnifiedTopology: true -}; - -const dbConnect = (url = DATABASE_URL) => { - return mongoose.connect(url, mongoOptions).catch(err => { - console.error('Mongoose failed to connect'); - console.error(err); - }); -}; - -const dbDisconnect = () => { - return mongoose.disconnect(); -}; - -const dbGet = () => { - return mongoose; -}; - -module.exports = { - dbGet, - dbConnect, - dbDisconnect, - mongoOptions -}; diff --git a/packages/server/db-mongoose.ts b/packages/server/db-mongoose.ts new file mode 100644 index 0000000..203605a --- /dev/null +++ b/packages/server/db-mongoose.ts @@ -0,0 +1,23 @@ +import mongoose, { ConnectOptions } from 'mongoose'; + +mongoose.Promise = global.Promise; + +export const mongoOptions = { + useNewUrlParser: true, + useUnifiedTopology: true +}; + +export const dbConnect = (url: string) => { + return mongoose.connect(url, mongoOptions as ConnectOptions).catch(err => { + console.error('Mongoose failed to connect'); + console.error(err); + }); +}; + +export const dbDisconnect = () => { + return mongoose.disconnect(); +}; + +export const dbGet = () => { + return mongoose; +}; diff --git a/packages/server/index.js b/packages/server/index.ts similarity index 55% rename from packages/server/index.js rename to packages/server/index.ts index d408b27..93eb1bb 100644 --- a/packages/server/index.js +++ b/packages/server/index.ts @@ -1,14 +1,15 @@ -require('dotenv').config(); -const path = require('path'); -const express = require('express'); -const mongoose = require('mongoose'); -const cookieParser = require('cookie-parser'); +import path from 'path'; +import express from 'express'; -const { dbConnect, mongoOptions } = require('./db-mongoose'); -const { PORT, DATABASE_URL, TEST_DATABASE_URL } = require('./config'); -const { authRouter, userRouter, sessionsRouter, paymentsRouter, configurationRouter } = require('./routes'); +import passport from 'passport'; +import mongoose, { ConnectOptions } from 'mongoose'; +import cookieParser from 'cookie-parser'; -const app = express(); +import { dbConnect, mongoOptions } from './db-mongoose'; +import { PORT, DATABASE_URL } from './config'; +import { authRouter, userRouter, sessionsRouter, paymentsRouter, configurationRouter, localStrategy, jwtStrategy } from './routes'; + +export const app = express(); app.use(express.json()); app.use(cookieParser()); @@ -20,21 +21,24 @@ app.use(function (req, res, next) { const root = path.join(__dirname, '../client', 'build'); app.use(express.static(root)); -app.get('*', (req, res) => { +app.get('/', (req, res) => { res.sendFile('index.html', { root }); }); +passport.use(localStrategy); +passport.use(jwtStrategy); + app.use('/auth', authRouter); app.use('/users', userRouter); app.use('/sessions', sessionsRouter); app.use('/payments', paymentsRouter); app.use('/configurations', configurationRouter); -let server; +let server: any; -const runServer = (databaseUrl = DATABASE_URL, port = PORT) => { - return new Promise((resolve, reject) => { - mongoose.connect(databaseUrl, mongoOptions, err => { +export const runServer = (databaseUrl = DATABASE_URL, port = PORT) => { + return new Promise((resolve, reject) => { + mongoose.connect(databaseUrl, mongoOptions as ConnectOptions, err => { if (err) { return reject(err); } @@ -51,11 +55,11 @@ const runServer = (databaseUrl = DATABASE_URL, port = PORT) => { }); }; -const closeServer = () => { +export const closeServer = () => { return mongoose.disconnect().then(() => { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { console.log('Closing server'); - server.close(err => { + return server.close((err: any) => { if (err) { return reject(err); } @@ -66,8 +70,6 @@ const closeServer = () => { }; if (require.main === module) { - dbConnect(); + dbConnect(DATABASE_URL); runServer(); } - -module.exports = { app, runServer, closeServer }; diff --git a/packages/server/models/index.js b/packages/server/models/index.js deleted file mode 100644 index bbae4ab..0000000 --- a/packages/server/models/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const { User, Configuration } = require('./users'); - -module.exports = { - User, - Configuration -}; diff --git a/packages/server/models/index.ts b/packages/server/models/index.ts new file mode 100644 index 0000000..36f0487 --- /dev/null +++ b/packages/server/models/index.ts @@ -0,0 +1,2 @@ +export { User, Configuration } from './users'; +export type { UserDocument, ConfigurationDocument } from './types'; diff --git a/packages/server/models/types.ts b/packages/server/models/types.ts new file mode 100644 index 0000000..1529de1 --- /dev/null +++ b/packages/server/models/types.ts @@ -0,0 +1,19 @@ +import { Document, Types } from 'mongoose'; + +export interface UserDocument extends Document { + _id?: Types.ObjectId; + id?: string; + username: string; + password: string; + email: string; + adyenKey?: string; + merchantAccounts?: string[]; + configurations?: Types.ObjectId[]; +} + +export interface ConfigurationDocument extends Document { + owner: Types.ObjectId; + name: string; + version: number; + configuration: string; +} diff --git a/packages/server/models/users/configurations.js b/packages/server/models/users/configurations.js deleted file mode 100644 index 4c29cdc..0000000 --- a/packages/server/models/users/configurations.js +++ /dev/null @@ -1,22 +0,0 @@ -const mongoose = require('mongoose'); - -mongoose.Promise = global.Promise; - -const ConfigurationSchema = mongoose.Schema({ - owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, - name: { type: String, required: true }, - version: { type: Number, required: true }, - configuration: { type: String, required: true } -}); - -ConfigurationSchema.methods.apiRepr = () => ({ - id: this._id, - owner: this.owner || '', - name: this.name || null, - version: this.version || [], - configuration: this.configuration -}); - -const Configuration = mongoose.model('Configuration', ConfigurationSchema); - -module.exports = { Configuration }; diff --git a/packages/server/models/users/configurations.ts b/packages/server/models/users/configurations.ts new file mode 100644 index 0000000..10cd206 --- /dev/null +++ b/packages/server/models/users/configurations.ts @@ -0,0 +1,32 @@ +import { Schema, SchemaTypes, Model, model } from 'mongoose'; + +import { ConfigurationDocument } from '../types'; + +export interface Configuration extends ConfigurationDocument { + apiRepr(): ConfigurationDocument; +} + +export interface ConfigurationModel extends Model {} + +export const ConfigurationSchema: Schema = new Schema({ + owner: { type: SchemaTypes.ObjectId, ref: 'User', required: true }, + name: { type: String, required: true }, + version: { type: Number, required: true }, + configuration: { type: String, required: true } +}); + +// arrow functions not possible here, since they close over lexically enclosing context (i.e: this remains this) + +ConfigurationSchema.method('apiRepr', function () { + return { + id: this._id.toString(), + owner: this.owner || '', + name: this.name || null, + version: this.version || [], + configuration: this.configuration + }; +}); + +export const Configuration: ConfigurationModel = model('Configuration', ConfigurationSchema); + +export default Configuration; diff --git a/packages/server/models/users/index.js b/packages/server/models/users/index.js deleted file mode 100644 index 2eb5be0..0000000 --- a/packages/server/models/users/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const { User } = require('./users'); -const { Configuration } = require('./configurations'); - -module.exports = { - User, - Configuration -}; diff --git a/packages/server/models/users/index.ts b/packages/server/models/users/index.ts new file mode 100644 index 0000000..2dd232b --- /dev/null +++ b/packages/server/models/users/index.ts @@ -0,0 +1,4 @@ +import User from './users'; +import Configuration from './configurations'; + +export { User, Configuration }; diff --git a/packages/server/models/users/users.js b/packages/server/models/users/users.js deleted file mode 100644 index 51c7745..0000000 --- a/packages/server/models/users/users.js +++ /dev/null @@ -1,41 +0,0 @@ -const bcrypt = require('bcryptjs'); -const mongoose = require('mongoose'); - -mongoose.Promise = global.Promise; - -const UserSchema = mongoose.Schema({ - username: { - type: String, - required: true, - unique: true - }, - password: { - type: String, - required: true - }, - adyenKey: { type: String, required: false }, - merchantAccounts: [{ type: String, required: false }], - configurations: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: 'Configuration', - required: false - } - ] -}); - -UserSchema.methods.apiRepr = () => ({ - id: this._id, - username: this.username || '', - adyenKey: this.adyenKey || null, - merchantAccounts: this.merchantAccounts || [], - configurations: this.configurations || [] -}); - -UserSchema.methods.validatePassword = password => bcrypt.compare(password, this.password); - -UserSchema.statics.hashPassword = password => bcrypt.hash(password, 10); - -const User = mongoose.model('User', UserSchema); - -module.exports = { User }; diff --git a/packages/server/models/users/users.ts b/packages/server/models/users/users.ts new file mode 100644 index 0000000..7f980d8 --- /dev/null +++ b/packages/server/models/users/users.ts @@ -0,0 +1,61 @@ +import { compare, hash } from 'bcryptjs'; +import { Schema, SchemaTypes, Model, model } from 'mongoose'; + +import { UserDocument } from '../types'; + +export interface User extends UserDocument { + validatePassword(password: string): boolean; + apiRepr(): UserDocument; +} + +export interface UserModel extends Model { + hashPassword(password: string): string; +} + +export const UserSchema: Schema = new Schema({ + username: { + type: String, + required: true, + unique: true + }, + password: { + type: String, + required: true + }, + email: { + type: String, + required: true + }, + adyenKey: { type: String, required: false }, + merchantAccounts: [{ type: String, required: false }], + configurations: [ + { + type: SchemaTypes.ObjectId, + ref: 'Configuration', + required: false + } + ] +}); + +// arrow functions not possible here, since they close over lexically enclosing context (i.e: this remains this) + +UserSchema.method('apiRepr', function () { + return { + id: this._id.toString(), + username: this.username || '', + email: this.email || '', + adyenKey: this.adyenKey || null, + merchantAccounts: this.merchantAccounts || [], + configurations: this.configurations || [] + }; +}); + +UserSchema.method('validatePassword', function (password: string): Promise { + return compare(password, this.password); +}); + +UserSchema.static('hashPassword', (password: string): Promise => hash(password, 10)); + +export const User: UserModel = model('User', UserSchema); + +export default User; diff --git a/packages/server/package.json b/packages/server/package.json index a8cbc4a..ddbff19 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -4,27 +4,49 @@ "description": "Adyen Demo Back End", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "ts-mocha -p ./tsconfig.json ./test/**/*.ts --exit", "start": "node ." }, "author": "Mike Ossig & HernĂ¡n Chalco", "license": "ISC", "dependencies": { + "@types/validator": "^13.7.1", "bcrypt": "^5.0.1", "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.6", "dotenv": "^16.0.0", "express": "^4.17.3", "jsonwebtoken": "^8.5.1", + "jwt-decode": "^3.1.2", "local": "^0.3.3", "mongoose": "^6.2.4", "passport": "^0.5.2", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "request": "^2.88.2", - "request-promise": "^4.2.6" + "request-promise": "^4.2.6", + "validator": "^13.7.0" }, "devDependencies": { - "prettier": "^2.5.1" + "@types/bcryptjs": "^2.4.2", + "@types/chai": "^4.3.0", + "@types/cookie-parser": "^1.4.2", + "@types/expect": "^24.3.0", + "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.28", + "@types/jsonwebtoken": "^8.5.8", + "@types/mocha": "^9.1.0", + "@types/mongoose": "^5.11.97", + "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/passport-local": "^1.0.34", + "@types/request-promise": "^4.1.48", + "chai": "^4.3.6", + "chai-http": "^4.3.0", + "mocha": "^9.2.2", + "prettier": "^2.5.1", + "ts-mocha": "^9.0.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.2" } } diff --git a/packages/server/routes/adyen-endpoints/index.js b/packages/server/routes/adyen-endpoints/index.js deleted file mode 100644 index 746b9c1..0000000 --- a/packages/server/routes/adyen-endpoints/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const { router: sessionsRouter } = require('./sessions'); -const { router: paymentsRouter } = require('./payments'); - -module.exports = { - sessionsRouter, - paymentsRouter -}; diff --git a/packages/server/routes/adyen-endpoints/index.ts b/packages/server/routes/adyen-endpoints/index.ts new file mode 100644 index 0000000..32825b5 --- /dev/null +++ b/packages/server/routes/adyen-endpoints/index.ts @@ -0,0 +1,4 @@ +export { router as sessionsRouter } from './sessions'; +export { router as paymentsRouter } from './payments'; + +export type { BaseAdyenRequest, InitializationRequest, RequestOptions, PaymentAmount } from './types'; diff --git a/packages/server/routes/adyen-endpoints/payments.ts b/packages/server/routes/adyen-endpoints/payments.ts new file mode 100644 index 0000000..ae25f88 --- /dev/null +++ b/packages/server/routes/adyen-endpoints/payments.ts @@ -0,0 +1,71 @@ +import { Request, Response, Router } from 'express'; +import request from 'request-promise'; +import { errorHandler } from '../helpers'; +import { ADYEN_API_KEY, ADYEN_BASE_URL } from '../../config'; + +import type { InitializationRequest, RequestOptions } from './types'; +import type { PaymentMethodsResponseInterface } from '@adyen/adyen-web/dist/types/types'; + +const router = Router(); + +router.post('/getPaymentMethods', async (req: Request, res: Response) => { + const { version, apiKey, payload }: InitializationRequest = req.body; + try { + const options: RequestOptions = { + url: `${ADYEN_BASE_URL}/${version}/paymentMethods`, + headers: { + 'Content-type': 'application/json', + 'x-api-key': apiKey || ADYEN_API_KEY + }, + body: payload, + json: true + }; + + const response: PaymentMethodsResponseInterface = await request(options); + res.send(201).json(response); + } catch (err: any) { + errorHandler('/getPaymentMethods', 500, err.message, res); + } +}); + +router.post('/makePayment', async (req: Request, res: Response) => { + const { version, apiKey, payload } = req.body; + try { + const options: RequestOptions = { + url: `${ADYEN_BASE_URL}/${version}/payments`, + headers: { + 'Content-type': 'application/json', + 'x-api-key': apiKey || ADYEN_API_KEY + }, + body: payload, + json: true + }; + + const response = await request(options); + res.send(201).json(response); + } catch (err: any) { + errorHandler('/makePayment', 500, err.message, res); + } +}); + +router.post('/additionalDetails', async (req: Request, res: Response) => { + const { version, apiKey, payload } = req.body; + try { + const options: RequestOptions = { + url: `${ADYEN_BASE_URL}/${version}/payments/details`, + headers: { + 'Content-type': 'application/json', + 'x-api-key': apiKey || ADYEN_API_KEY + }, + body: payload, + json: true + }; + + const response = await request(options); + res.send(201).json(response); + } catch (err: any) { + errorHandler('/additionalDetails', 500, err.message, res); + } +}); + +export { router }; diff --git a/packages/server/routes/adyen-endpoints/payments/index.js b/packages/server/routes/adyen-endpoints/payments/index.js deleted file mode 100644 index 0045fbd..0000000 --- a/packages/server/routes/adyen-endpoints/payments/index.js +++ /dev/null @@ -1,72 +0,0 @@ -const express = require("express"); -const request = require("request-promise"); -const mongoose = require("mongoose"); - -const { errorHandler } = require("../../helpers"); -const { ADYEN_API_KEY, ADYEN_BASE_URL } = require("../../../config"); - -const router = express.Router(); - -mongoose.Promise = global.Promise; - -router.post("/getPaymentMethods", async (req, res) => { - const { version, apiKey, payload } = req.body; - try { - const options = { - url: `${ADYEN_BASE_URL}/${version}/paymentMethods`, - headers: { - "Content-type": "application/json", - "x-api-key": apiKey || ADYEN_API_KEY, - }, - body: payload, - json: true, - }; - - const response = await request(options); - res.send(201).json(response); - } catch (err) { - errorHandler("/getPaymentMethods", 500, err.message, res); - } -}); - -router.post("/makePayment", async (req, res) => { - const { version, apiKey, payload } = req.body; - try { - const options = { - url: `${ADYEN_BASE_URL}/${version}/payments`, - headers: { - "Content-type": "application/json", - "x-api-key": apiKey || ADYEN_API_KEY, - }, - body: payload, - json: true, - }; - - const response = await request(options); - res.send(201).json(response); - } catch (err) { - errorHandler("/makePayment", 500, err.message, res); - } -}); - -router.post("/additionalDetails", async (req, res) => { - const { version, apiKey, payload } = req.body; - try { - const options = { - url: `${ADYEN_BASE_URL}/${version}/payments/details`, - headers: { - "Content-type": "application/json", - "x-api-key": apiKey || ADYEN_API_KEY, - }, - body: payload, - json: true, - }; - - const response = await request(options); - res.send(201).json(response); - } catch (err) { - errorHandler("/additionalDetails", 500, err.message, res); - } -}); - -module.exports = { router }; diff --git a/packages/server/routes/adyen-endpoints/sessions.ts b/packages/server/routes/adyen-endpoints/sessions.ts new file mode 100644 index 0000000..860fb75 --- /dev/null +++ b/packages/server/routes/adyen-endpoints/sessions.ts @@ -0,0 +1,32 @@ +import { Router, Request, Response } from 'express'; +import request from 'request-promise'; + +import { errorHandler } from '../helpers'; +import { ADYEN_API_KEY, ADYEN_BASE_URL } from '../../config'; + +import type { InitializationRequest, RequestOptions } from './types'; +import type { CheckoutSessionSetupResponse } from '@adyen/adyen-web/dist/types/types'; + +const router = Router(); + +router.post('/sessionStart', async (req: Request, res: Response) => { + const { version, apiKey, payload }: InitializationRequest = req.body; + try { + const options: RequestOptions = { + url: `${ADYEN_BASE_URL}/${version}/sessions`, + headers: { + 'Content-type': 'application/json', + 'x-api-key': apiKey || ADYEN_API_KEY + }, + body: payload, + json: true + }; + + const response: CheckoutSessionSetupResponse = await request(options); + res.send(201).json(response); + } catch (err: any) { + errorHandler('/sessionStart', 500, err.message, res); + } +}); + +export { router }; diff --git a/packages/server/routes/adyen-endpoints/sessions/index.js b/packages/server/routes/adyen-endpoints/sessions/index.js deleted file mode 100644 index 1c79283..0000000 --- a/packages/server/routes/adyen-endpoints/sessions/index.js +++ /dev/null @@ -1,32 +0,0 @@ -const express = require("express"); -const request = require("request-promise"); -const mongoose = require("mongoose"); - -const { errorHandler } = require("../../helpers"); -const { ADYEN_API_KEY, ADYEN_BASE_URL } = require("../../../config"); - -const router = express.Router(); - -mongoose.Promise = global.Promise; - -router.post("/sessionStart", async (req, res) => { - const { version, apiKey, payload } = req.body; - try { - const options = { - url: `${ADYEN_BASE_URL}/${version}/sessions`, - headers: { - "Content-type": "application/json", - "x-api-key": apiKey || ADYEN_API_KEY, - }, - body: payload, - json: true, - }; - - const response = await request(options); - res.send(201).json(response); - } catch (err) { - errorHandler("/sessionStart", 500, err.message, res); - } -}); - -module.exports = { router }; diff --git a/packages/server/routes/adyen-endpoints/types.ts b/packages/server/routes/adyen-endpoints/types.ts new file mode 100644 index 0000000..8cccdeb --- /dev/null +++ b/packages/server/routes/adyen-endpoints/types.ts @@ -0,0 +1,33 @@ +import type { PaymentAmount } from '@adyen/adyen-web/dist/types/types'; + +export interface BaseAdyenRequest { + version: string; + apiKey?: string; +} + +export interface InitializationRequest extends BaseAdyenRequest { + payload: { + merchantAccount: string; + amount: PaymentAmount; + returnUrl: string; + reference: string; + expiresAt?: Date; + countryCode?: string; + shopperLocale?: string; + shopperEmail?: string; + shopperIP?: string; + shopperReference?: string; + }; +} + +export interface RequestOptions { + url: string; + headers: { + 'Content-type': string; + 'x-api-key': string; + }; + body: any; + json: boolean; +} + +export type { PaymentAmount }; diff --git a/packages/server/routes/auth/auth.js b/packages/server/routes/auth/auth.js deleted file mode 100644 index 3a5262f..0000000 --- a/packages/server/routes/auth/auth.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; -const express = require('express'); -const passport = require('passport'); -const bodyParser = require('body-parser'); -const jwt = require('jsonwebtoken'); - -const config = require('../../config'); - -const router = express.Router(); - -const createAuthToken = function (user) { - return jwt.sign({ user }, config.JWT_SECRET, { - subject: user.username, - expiresIn: config.JWT_EXPIRY, - algorithm: 'HS256' - }); -}; - -const localAuth = passport.authenticate('local', { session: false }); -router.use(bodyParser.json()); -router.post('/login', localAuth, (req, res) => { - const authToken = createAuthToken(req.user.apiRepr()); - res.json({ authToken, userId: req.user.apiRepr().id }); -}); - -const jwtAuth = passport.authenticate('jwt', { session: false }); - -router.post('/refresh', jwtAuth, (req, res) => { - const authToken = createAuthToken(req.user); - res.json({ authToken }); -}); - -module.exports = { router, jwtAuth }; diff --git a/packages/server/routes/auth/auth.ts b/packages/server/routes/auth/auth.ts new file mode 100644 index 0000000..a1ef7c8 --- /dev/null +++ b/packages/server/routes/auth/auth.ts @@ -0,0 +1,30 @@ +import jwt from 'jsonwebtoken'; +import passport from 'passport'; +import express, { Request, Response } from 'express'; +import { JWT_EXPIRY, JWT_SECRET } from '../../config'; + +const router = express.Router(); + +const createAuthToken = (user: any): string => { + return jwt.sign({ user }, JWT_SECRET, { + subject: user.username, + expiresIn: JWT_EXPIRY, + algorithm: 'HS256' + }); +}; + +const localAuth = passport.authenticate('local', { session: false }); + +router.post('/login', localAuth, (req: Request, res: Response) => { + const authToken = createAuthToken(req.user); + res.json({ authToken }); +}); + +const jwtAuth = passport.authenticate('jwt', { session: false }); + +router.post('/refresh', jwtAuth, (req: Request, res: Response) => { + const authToken = createAuthToken(req.user); + res.json({ authToken }); +}); + +export { router, jwtAuth }; diff --git a/packages/server/routes/auth/index.js b/packages/server/routes/auth/index.js deleted file mode 100644 index 0f950bb..0000000 --- a/packages/server/routes/auth/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const { router: authRouter, jwtAuth } = require('./auth'); -const { localStrategy, jwtStrategy } = require('./strategies'); - -module.exports = { - jwtAuth, - authRouter, - jwtStrategy, - localStrategy -}; diff --git a/packages/server/routes/auth/index.ts b/packages/server/routes/auth/index.ts new file mode 100644 index 0000000..1c96872 --- /dev/null +++ b/packages/server/routes/auth/index.ts @@ -0,0 +1,3 @@ +export { isAuthorizedForAction } from './middleware'; +export { localStrategy, jwtStrategy } from './strategies'; +export { router as authRouter, jwtAuth } from './auth'; diff --git a/packages/server/routes/auth/middleware.ts b/packages/server/routes/auth/middleware.ts new file mode 100644 index 0000000..920bd71 --- /dev/null +++ b/packages/server/routes/auth/middleware.ts @@ -0,0 +1,34 @@ +import { Request, Response, NextFunction } from 'express'; +import { ParamsDictionary } from 'express-serve-static-core'; +import jwt_decode from 'jwt-decode'; + +type TokenData = { + user: { + _id: string; + username: string; + password: string; + email: string; + merchantAccounts?: [any]; + configurations?: [any]; + }; +}; + +type AuthorizedParams = { + userId: string; +}; + +interface AuthorizationRequest extends Request { + params: T; +} + +export const isAuthorizedForAction = (req: AuthorizationRequest, res: Response, next: NextFunction) => { + const userToken: string = req.headers.authorization ? req.headers.authorization.split(' ')[1] : ''; + const { userId }: { userId: string } = req.params; + const tokenData: TokenData = jwt_decode(userToken); + return tokenData.user._id === userId + ? next() + : res.status(401).json({ + code: 401, + reason: 'Not authorized' + }); +}; diff --git a/packages/server/routes/auth/strategies.js b/packages/server/routes/auth/strategies.js deleted file mode 100644 index 2509906..0000000 --- a/packages/server/routes/auth/strategies.js +++ /dev/null @@ -1,49 +0,0 @@ -const { Strategy: LocalStrategy } = require('passport-local'); - -const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); - -const { User } = require('../../models'); -const { JWT_SECRET } = require('../../config'); - -const localStrategy = new LocalStrategy((username, password, callback) => { - let user; - User.findOne({ username: username }) - .then(_user => { - user = _user; - if (!user) { - return Promise.reject({ - reason: 'LoginError', - message: 'Incorrect username or password' - }); - } - return user.validatePassword(password); - }) - .then(isValid => { - if (!isValid) { - return Promise.reject({ - reason: 'LoginError', - message: 'Incorrect username or password' - }); - } - return callback(null, user); - }) - .catch(err => { - if (err.reason === 'LoginError') { - return callback(null, false, err); - } - return callback(err, false); - }); -}); - -const jwtStrategy = new JwtStrategy( - { - secretOrKey: JWT_SECRET, - jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'), - algorithms: ['HS256'] - }, - (payload, done) => { - done(null, payload.user); - } -); - -module.exports = { localStrategy, jwtStrategy }; diff --git a/packages/server/routes/auth/strategies.ts b/packages/server/routes/auth/strategies.ts new file mode 100644 index 0000000..ae5e086 --- /dev/null +++ b/packages/server/routes/auth/strategies.ts @@ -0,0 +1,45 @@ +import { Strategy as LocalStrategy } from 'passport-local'; +import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'; + +import { User } from '../../models'; +import { JWT_SECRET } from '../../config'; + +export const localStrategy = new LocalStrategy(async (username, password, callback) => { + try { + const user = await User.findOne({ username: username }); + + if (!user) { + return Promise.reject({ + reason: 'LoginError', + message: 'Incorrect username or password' + }); + } + + const isValid = await user.validatePassword(password); + + if (!isValid) { + return Promise.reject({ + reason: 'LoginError', + message: 'Incorrect username or password' + }); + } + + return callback(null, user); + } catch (err: any) { + if (err.reason === 'LoginError') { + return callback(null, false, err); + } + return callback(err, false); + } +}); + +export const jwtStrategy = new JwtStrategy( + { + secretOrKey: JWT_SECRET, + jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'), + algorithms: ['HS256'] + }, + (payload, done) => { + done(null, payload.user); + } +); diff --git a/packages/server/routes/helpers/index.js b/packages/server/routes/helpers/index.ts similarity index 51% rename from packages/server/routes/helpers/index.js rename to packages/server/routes/helpers/index.ts index 7ee1b61..23197b2 100644 --- a/packages/server/routes/helpers/index.js +++ b/packages/server/routes/helpers/index.ts @@ -1,9 +1,7 @@ +export { runUserValidation } from './users'; + // TODO: Decide if we want to log to an external source. Would take up too much DB room for a free version in the meantime -const errorHandler = (endpoint, statusCode, message, res) => { - console.error("ERROR:", endpoint, message); +export const errorHandler = (endpoint, statusCode, message, res) => { + console.error('ERROR:', endpoint, message); res.send(statusCode).json({ message }); }; - -module.exports = { - errorHandler, -}; diff --git a/packages/server/routes/helpers/users/index.js b/packages/server/routes/helpers/users/index.js deleted file mode 100644 index 657e010..0000000 --- a/packages/server/routes/helpers/users/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const { runUserValidation } = require('./validations'); - -module.exports = { - runUserValidation -}; diff --git a/packages/server/routes/helpers/users/index.ts b/packages/server/routes/helpers/users/index.ts new file mode 100644 index 0000000..5314e6f --- /dev/null +++ b/packages/server/routes/helpers/users/index.ts @@ -0,0 +1 @@ +export { runUserValidation } from './validations'; diff --git a/packages/server/routes/helpers/users/validations.js b/packages/server/routes/helpers/users/validations.ts similarity index 71% rename from packages/server/routes/helpers/users/validations.js rename to packages/server/routes/helpers/users/validations.ts index 98b80cc..c787866 100644 --- a/packages/server/routes/helpers/users/validations.js +++ b/packages/server/routes/helpers/users/validations.ts @@ -1,4 +1,5 @@ -const { User } = require('../../../models'); +import isEmail from 'validator/lib/isEmail'; +import { User } from '../../../models'; const checkForExistingUser = async ({ username }) => { try { @@ -37,41 +38,49 @@ const checkSizedFields = reqBody => { : checkForExistingUser(reqBody); }; -const checkPasswordForInvalidChars = ({ password }) => { +const checkEmail = reqBody => { + return isEmail(reqBody.email) + ? checkSizedFields(reqBody) + : { + message: 'Invalid Email address', + location: 'reqBody.email' + }; +}; + +const checkPasswordForInvalidChars = reqBody => { const invalidChars = /[ ]/g; - return invalidChars.test(password) + return invalidChars.test(reqBody.password) ? { message: 'Password contains invalid characters', location: 'Password' } - : checkSizedFields(reqBody); + : checkEmail(reqBody); }; -const checkUsernameForInvalidChars = ({ username }) => { +const checkUsernameForInvalidChars = reqBody => { const invalidChars = /[^A-Za-z0-9]+/g; - return invalidChars.test(username) + return invalidChars.test(reqBody.username) ? { message: 'Username can only contain numbers and letters', - location: username + location: reqBody.username } : checkPasswordForInvalidChars(reqBody); }; const checkFieldTypes = reqBody => { - const stringFields = ['username', 'password', 'adyenKey', 'merchantAccount']; + const stringFields = ['username', 'password', 'email']; const nonStringField = stringFields.find(field => field in reqBody && typeof reqBody[field] !== 'string'); - const nonStringMerchantAccount = reqBody.merchantAccount.find(field => typeof field !== 'string'); - return nonStringField || nonStringMerchantAccount + return nonStringField ? { message: 'Incorrect field type: expected string', - location: nonStringField || nonStringMerchantAccount + location: nonStringField } : checkUsernameForInvalidChars(reqBody); }; const checkRequiredFields = reqBody => { - const requiredFields = ['username', 'password']; + const requiredFields = ['username', 'password', 'email']; const missingField = requiredFields.find(field => !(field in reqBody)); return missingField @@ -82,6 +91,4 @@ const checkRequiredFields = reqBody => { : checkFieldTypes(reqBody); }; -const runUserValidation = reqBody => checkRequiredFields(reqBody); - -module.exports = { runUserValidation }; +export const runUserValidation = reqBody => checkRequiredFields(reqBody); diff --git a/packages/server/routes/index.js b/packages/server/routes/index.js deleted file mode 100644 index 4dd3588..0000000 --- a/packages/server/routes/index.js +++ /dev/null @@ -1,11 +0,0 @@ -const { authRouter } = require('./auth'); -const { userRouter, configurationRouter } = require('./users'); -const { sessionsRouter, paymentsRouter } = require('./adyen-endpoints'); - -module.exports = { - authRouter, - userRouter, - sessionsRouter, - paymentsRouter, - configurationRouter -}; diff --git a/packages/server/routes/index.ts b/packages/server/routes/index.ts new file mode 100644 index 0000000..27e27e8 --- /dev/null +++ b/packages/server/routes/index.ts @@ -0,0 +1,3 @@ +export { authRouter, localStrategy, jwtStrategy } from './auth'; +export { userRouter, configurationRouter } from './users'; +export { sessionsRouter, paymentsRouter } from './adyen-endpoints'; diff --git a/packages/server/routes/users/configurations.js b/packages/server/routes/users/configurations.js deleted file mode 100644 index 51e329e..0000000 --- a/packages/server/routes/users/configurations.js +++ /dev/null @@ -1,60 +0,0 @@ -const express = require('express'); - -const { jwtAuth } = require('../auth'); -const { User, Configuration } = require('../../models'); - -const router = express.Router(); - -router.post('/', jwtAuth, async (req, res) => { - try { - const { owner, name, version, configuration } = req.body; - - const existingUser = await User.find({ _id: owner }); - if (!existingUser || !existingUser.length) { - return res.status(422).json({ - code: 422, - reason: 'ValidationError', - message: 'User does not exist', - location: owner - }); - } - - const createdConfiguration = await Configuration.create({ owner, name, version, configuration }); - return res.send(200).json({ - id: createdConfiguration.id, - owner, - name, - version, - configuration - }); - } catch (err) { - console.error('CONFIGURATIONS CREATION ERROR', err); - res.status(500).json({ code: 500, message: 'Internal server error' }); - } -}); - -router.put('/:id', jwtAuth, async (req, res) => { - if (!(req.params.id === req.body.id)) { - const message = `Request patch id (${req.params.id} and request body id (${req.body.id}) must match)`; - console.error(message); - res.status(400).json({ message: message }); - } - try { - const toUpdate = {}; - const updateableFields = ['name', 'version', 'configuration']; - - updateableFields.forEach(field => { - if (field in req.body) { - toUpdate[field] = req.body[field]; - } - }); - - const { adyenKey, merchantAccounts } = await Configuration.findOneAndUpdate({ _id: req.params.id }, { $set: toUpdate }, { new: true }).exec(); - res.send(200).json({ adyenKey: adyenKey.substr(adyenKey.length - 5), merchantAccounts }); - } catch (err) { - console.error('CONFIGURATIONS UPDATE ERROR', err); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -module.exports = { router }; diff --git a/packages/server/routes/users/configurations.ts b/packages/server/routes/users/configurations.ts new file mode 100644 index 0000000..b0132cd --- /dev/null +++ b/packages/server/routes/users/configurations.ts @@ -0,0 +1,108 @@ +import { Router, Request, Response } from 'express'; + +import { User, Configuration } from '../../models'; +import { jwtAuth, isAuthorizedForAction } from '../auth'; + +import type { ConfigToUpdate } from './types'; + +const router = Router(); + +router.get('/:userId', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { + try { + const existingUser = await User.find({ _id: req.params.userId }); + if (!existingUser || !existingUser.length) { + return res.status(422).json({ + code: 422, + reason: 'Not found', + message: 'User does not exist', + location: req.params.userId + }); + } + + const relatedConfigurations = await Configuration.find({ owner: req.params.userId }); + if (!relatedConfigurations || !relatedConfigurations.length) { + return res.status(422).json({ + code: 422, + reason: 'Not found', + message: 'No related configurations', + location: req.params.userId + }); + } + + res.status(201).json(relatedConfigurations.map(config => config.apiRepr())); + } catch (err: any) { + console.error('ERROR GETTING CONFIGS', err); + res.status(500).json({ code: 500, message: 'Internal server error' }); + } +}); + +router.get('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { + try { + const existingConfiguration = await Configuration.find({ _id: req.params.id }); + if (!existingConfiguration || !existingConfiguration.length) { + return res.status(404).json({ + code: 404, + reason: 'Not found', + message: 'Configuration by this ID does not exist', + location: req.body.id + }); + } + + res.status(201).json(existingConfiguration[0].apiRepr()); + } catch (err) { + console.error('ERROR GETTING CONFIGURATION', err); + res.status(500).json({ code: 500, message: 'Internal server error' }); + } +}); + +router.post('/:userId', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { + try { + const { owner, name, version, configuration } = req.body; + + const existingUser = await User.find({ _id: owner }); + if (!existingUser || !existingUser.length) { + return res.status(422).json({ + code: 422, + reason: 'ValidationError', + message: 'User does not exist', + location: owner + }); + } + + const createdConfiguration = await Configuration.create({ owner, name, version, configuration }); + res.status(200).json(createdConfiguration.apiRepr()); + } catch (err) { + console.error('CONFIGURATIONS CREATION ERROR', err); + res.status(500).json({ code: 500, message: 'Internal server error' }); + } +}); + +router.put('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { + if (!(req.params.id === req.body.id)) { + const message = `Request patch id (${req.params.id} and request body id (${req.body.id}) must match)`; + console.error(message); + res.status(400).json({ message: message }); + } + try { + const toUpdate: ConfigToUpdate = {}; + const updateableFields = ['name' as const, 'version' as const, 'configuration' as const]; + + updateableFields.forEach(field => { + if (field in req.body) { + toUpdate[field] = req.body[field]; + } + }); + + const updatedConfig = await Configuration.findOneAndUpdate({ _id: req.body.id }, { $set: toUpdate }, { new: true }).exec(); + if (updatedConfig) { + const { owner, name, version, configuration } = updatedConfig; + return res.send(200).json({ id: req.body.id, owner, name, version, configuration }); + } + res.status(500).json({ message: 'Internal server error' }); + } catch (err) { + console.error('CONFIGURATIONS UPDATE ERROR', err); + res.status(500).json({ message: 'Internal server error' }); + } +}); + +export { router }; diff --git a/packages/server/routes/users/index.js b/packages/server/routes/users/index.js deleted file mode 100644 index e9835d4..0000000 --- a/packages/server/routes/users/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const { router: userRouter } = require('./users'); -const { router: configurationRouter } = require('./configurations'); - -module.exports = { - userRouter, - configurationRouter -}; diff --git a/packages/server/routes/users/index.ts b/packages/server/routes/users/index.ts new file mode 100644 index 0000000..828d2cf --- /dev/null +++ b/packages/server/routes/users/index.ts @@ -0,0 +1,2 @@ +export { router as userRouter } from './users'; +export { router as configurationRouter } from './configurations'; diff --git a/packages/server/routes/users/types.ts b/packages/server/routes/users/types.ts new file mode 100644 index 0000000..9f2e529 --- /dev/null +++ b/packages/server/routes/users/types.ts @@ -0,0 +1,11 @@ +export type ConfigToUpdate = { + name?: string; + version?: number; + configuration?: string; +}; + +export type UserToUpdate = { + adyenKey?: string; + merchantAccounts?: string[]; + configurations?: string[]; +}; diff --git a/packages/server/routes/users/users.js b/packages/server/routes/users/users.js deleted file mode 100644 index 0bc6e6b..0000000 --- a/packages/server/routes/users/users.js +++ /dev/null @@ -1,88 +0,0 @@ -const express = require('express'); -const { jwtAuth } = require('../auth'); -const { runValidation } = require('../helpers/users'); - -const router = express.Router(); - -const { User } = require('../../models'); - -router.get('/:id', jwtAuth, async (req, res) => { - try { - const existingUser = await User.find({ _id: req.body.id }); - if (!existingUser || !existingUser.length) { - return res.status(404).json({ - code: 404, - reason: 'Not found', - message: 'User by this ID does not exist', - location: req.body.id - }); - } - - const { username, adyenKey, merchantAccounts, configurations } = user.apiRepr(); - res.status(201).send({ - username, - adyenKey: adyenKey.substr(adyenKey.length - 5), - merchantAccounts, - configurations - }); - } catch (err) { - console.error('ERROR GETTING USER', err); - } -}); - -router.post('/', async (req, res) => { - try { - const invalidEntry = await runValidation(req.body); - if (invalidEntry) { - return res.status(422).json({ - code: 422, - reason: 'ValidationError', - message: invalidEntry.message, - location: invalidEntry.location - }); - } - - const { username, password } = req.body; - - const hashedPassword = await User.hashPassword(password); - - const createdUser = await User.create({ username, password: hashedPassword }); - const { id, adyenKey, merchantAccounts, configurations } = createdUser.apiRepr(); - return res.send(200).json({ - id, - username, - adyenKey: adyenKey.substr(adyenKey.length - 5), - merchantAccounts, - configurations - }); - } catch (err) { - console.error('USER CREATION ERROR', err); - res.status(500).json({ code: 500, message: 'Internal server error' }); - } -}); - -router.put('/:id', jwtAuth, async (req, res) => { - if (!(req.params.id === req.body.id)) { - const message = `Request patch id (${req.params.id} and request body id (${req.body.id}) must match)`; - console.error(message); - res.status(400).json({ message: message }); - } - try { - const toUpdate = {}; - const updateableFields = ['adyenKey', 'merchantAccounts']; - - updateableFields.forEach(field => { - if (field in req.body) { - toUpdate[field] = req.body[field]; - } - }); - - const { adyenKey, merchantAccounts } = await User.findOneAndUpdate({ _id: req.params.id }, { $set: toUpdate }, { new: true }).exec(); - res.send(200).json({ adyenKey: adyenKey.substr(adyenKey.length - 5), merchantAccounts }); - } catch (err) { - console.error('USER UPDATE ERROR', err); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -module.exports = { router }; diff --git a/packages/server/routes/users/users.ts b/packages/server/routes/users/users.ts new file mode 100644 index 0000000..ea1c97e --- /dev/null +++ b/packages/server/routes/users/users.ts @@ -0,0 +1,96 @@ +import { Router } from 'express'; + +import { User } from '../../models'; +import { runUserValidation } from '../helpers'; + +import { jwtAuth, isAuthorizedForAction } from '../auth'; + +import type { UserToUpdate } from './types'; + +const router = Router(); + +router.post('/', async (req, res) => { + try { + const invalidEntry = await runUserValidation(req.body); + + if (invalidEntry) { + return res.status(422).json({ + code: 422, + reason: 'ValidationError', + message: invalidEntry.message, + location: invalidEntry.location + }); + } + + const { username, password, email } = req.body; + const hashedPassword = await User.hashPassword(password); + const createdUser = await User.create({ username, password: hashedPassword, email }); + const { id } = createdUser.apiRepr(); + + return res.status(200).json({ + id, + username, + email + }); + } catch (err) { + console.error('USER CREATION ERROR', err); + res.status(500).json({ code: 500, message: 'Internal server error' }); + } +}); + +router.get('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { + try { + const existingUser = await User.find({ _id: req.params.userId }); + if (!existingUser || !existingUser.length) { + return res.status(404).json({ + code: 404, + reason: 'Not found', + message: 'User by this ID does not exist', + location: req.params.userId + }); + } + + const { id, username, email, adyenKey, merchantAccounts, configurations } = existingUser[0].apiRepr(); + res.status(201).send({ + id, + username, + email, + adyenKey: adyenKey && adyenKey.length ? adyenKey.substr(adyenKey.length - 5) : '', + merchantAccounts, + configurations + }); + } catch (err) { + console.error('ERROR GETTING USER', err); + } +}); + +router.put('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { + if (!(req.params.userId === req.body.id)) { + const message = `Request patch id (${req.params.userId} and request body id (${req.body.id}) must match)`; + console.error(message); + res.status(400).json({ message: message }); + } + + try { + const toUpdate: UserToUpdate = {}; + const updateableFields = ['adyenKey' as const, 'merchantAccounts' as const, 'configurations' as const]; + + updateableFields.forEach(field => { + if (field in req.body) { + toUpdate[field] = req.body[field]; + } + }); + + const foundUser = await User.findOneAndUpdate({ _id: req.body.id }, { $set: toUpdate }, { new: true }).exec(); + + if (foundUser) { + const { adyenKey, merchantAccounts, configurations } = foundUser; + res.status(200).json({ id: req.body.id, adyenKey: adyenKey ? adyenKey.substring(adyenKey.length - 5) : '', merchantAccounts, configurations }); + } + } catch (err) { + console.error('USER UPDATE ERROR', err); + res.status(500).json({ message: 'Internal server error' }); + } +}); + +export { router }; diff --git a/packages/server/test/helpers/helpers.ts b/packages/server/test/helpers/helpers.ts new file mode 100644 index 0000000..5519c87 --- /dev/null +++ b/packages/server/test/helpers/helpers.ts @@ -0,0 +1,40 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { app } from '../../index'; +import { userTestData } from '../structures'; + +chai.use(chaiHttp); + +const { testUserData, testConfigData } = userTestData; + +export const createMockUser = (): any => { + return chai + .request(app) + .post('/users') + .send(testUserData) + .then(res => res) + .catch(err => console.log(err)); +}; + +export const logUserIn = (): any => { + return chai + .request(app) + .post('/auth/login') + .send({ username: testUserData.username, password: testUserData.password }) + .then(res => res.body.authToken) + .catch(err => console.log(err)); +}; + +export const createMockConfigurations = async (): Promise => { + const user = await createMockUser(); + const authToken = await logUserIn(); + const userId = user.body.id; + testConfigData.owner = userId; + return chai + .request(app) + .post(`/configurations/${userId}`) + .set('Authorization', `Bearer ${authToken}`) + .send(testConfigData) + .then(res => ({ mockConfig: res, authToken, userId })) + .catch(err => console.log(err)); +}; diff --git a/packages/server/test/helpers/index.ts b/packages/server/test/helpers/index.ts new file mode 100644 index 0000000..fdb7167 --- /dev/null +++ b/packages/server/test/helpers/index.ts @@ -0,0 +1 @@ +export * as userHelpers from './helpers'; diff --git a/packages/server/test/routes/auth/auth.test.ts b/packages/server/test/routes/auth/auth.test.ts new file mode 100644 index 0000000..db122cf --- /dev/null +++ b/packages/server/test/routes/auth/auth.test.ts @@ -0,0 +1,197 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import { User } from '../../../models'; +import { userTestData } from '../../structures'; +import { JWT_SECRET, TEST_DATABASE_URL } from '../../../config'; +import { app, runServer, closeServer } from '../../../index'; + +const assert = chai.assert; + +chai.use(chaiHttp); + +const { + testUserData: { username, password, email } +} = userTestData; + +describe('Authorization API', () => { + let _id: string; + before(() => { + return runServer(TEST_DATABASE_URL); + }); + + after(() => { + return closeServer(); + }); + + beforeEach(async () => { + const hashedPassword = await User.hashPassword(password); + if (hashedPassword) { + const { id } = await User.create({ + username, + password: hashedPassword, + email + }); + if (id) { + _id = id; + } + } + }); + + afterEach(() => { + return User.deleteOne({ _id }); + }); + + describe('/login', () => { + it('Should reject requests with no credentials', async () => { + try { + return await chai.request(app).post('/auth/login'); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 400); + } + }); + + it('Should reject requests with incorrect usernames', async () => { + try { + return await chai.request(app).post('/auth/login').auth('wrongUsername', password); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 400); + } + }); + + it('Should reject requests with incorrect passwords', async () => { + try { + return await chai.request(app).post('/auth/login').auth(username, 'wrongPassword'); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 400); + } + }); + + it('Should return a valid auth token', async () => { + const res = await chai.request(app).post('/auth/login').send({ username, password }); + assert.equal(res.status, 200); + assert.equal(typeof res.body, 'object'); + const token = res.body.authToken; + assert.equal(typeof token, 'string'); + const payload: any = jwt.verify(token, JWT_SECRET, { + algorithms: ['HS256'] + }); + assert.equal(payload.user.username, username, 'failed username match'); + assert.equal(payload.user._id, _id, 'failed id match'); + }); + }); + + describe('/auth/refresh', () => { + it('Should reject requests with no credentials', async () => { + try { + return await chai.request(app).post('/auth/refresh'); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 401); + } + }); + + it('Should reject requests with an invalid token', async () => { + const token = jwt.sign( + { + username, + email + }, + 'wrongSecret', + { + algorithm: 'HS256', + expiresIn: '7d' + } + ); + + try { + return await chai.request(app).post('/auth/refresh').set('Authorization', `Bearer ${token}`); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 401); + } + }); + + it('Should reject requests with an expired token', async () => { + const token = jwt.sign( + { + user: { + username, + email + }, + exp: Math.floor(Date.now() / 1000) - 10 // Expired ten seconds ago + }, + JWT_SECRET, + { + algorithm: 'HS256', + subject: username + } + ); + + try { + return await chai.request(app).post('/auth/refresh').set('authorization', `Bearer ${token}`); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 401); + } + }); + + it('Should return a valid auth token with a newer expiry date', async () => { + const token = jwt.sign( + { + user: { + username, + email + } + }, + JWT_SECRET, + { + algorithm: 'HS256', + subject: username, + expiresIn: '7d' + } + ); + const decoded: any = jwt.decode(token); + + const res = await chai.request(app).post('/auth/refresh').set('authorization', `Bearer ${token}`); + assert.equal(res.status, 200); + assert.equal(typeof res.body, 'object'); + + const token_2 = res.body.authToken; + assert.equal(typeof token_2, 'string'); + + const payload: any = jwt.verify(token_2, JWT_SECRET, { + algorithms: ['HS256'] + }); + assert.deepEqual(payload.user, { username, email }); + assert.isAtLeast(decoded.exp, payload.exp); + }); + }); +}); diff --git a/packages/server/test/routes/users/configurations.test.ts b/packages/server/test/routes/users/configurations.test.ts new file mode 100644 index 0000000..25d5c96 --- /dev/null +++ b/packages/server/test/routes/users/configurations.test.ts @@ -0,0 +1,73 @@ +import chai from 'chai'; +import mongoose from 'mongoose'; +import chaiHttp from 'chai-http'; +import { userTestData } from '../../structures'; +import { TEST_DATABASE_URL } from '../../../config'; +import { userHelpers } from '../../helpers'; +import { app, runServer, closeServer } from '../../../index'; + +const assert = chai.assert; + +chai.use(chaiHttp); + +const { createMockConfigurations } = userHelpers; + +const tearDownDb = () => { + return new Promise((resolve, reject) => { + mongoose.connection + .dropDatabase() + .then(result => resolve(result)) + .catch(err => reject(err)); + }); +}; + +describe('Configurations API', () => { + const configFields = ['id', 'owner', 'name', 'version', 'configuration']; + + before(() => { + return runServer(TEST_DATABASE_URL); + }); + + afterEach(() => { + return tearDownDb(); + }); + + after(() => { + return closeServer(); + }); + + it('Should create a configuration on POST', async () => { + const { mockConfig } = await createMockConfigurations(); + const hasKeys = configFields.reduce((acc, x) => acc && mockConfig.body.hasOwnProperty(x)); + assert.equal(mockConfig.status, 200, 'failed status check'); + assert.isTrue(hasKeys, 'failed key compare'); + }); + + it('Should reject request for config with wrong auth token', async () => { + const { mockConfig, userId } = await createMockConfigurations(); + let agent = chai.request.agent(app); + return agent + .get(`/configurations/${userId}/${mockConfig.body.id}`) + .set('Authorization', `Bearer ${userTestData.wrongAuthToken}`) + .then(res => { + assert.equal(res.status, 401, 'failed status check'); + return res; + }); + }); + + it('Should send back a configuration by id', async () => { + const { mockConfig, authToken, userId } = await createMockConfigurations(); + let agent = chai.request.agent(app); + return agent + .get(`/configurations/${userId}/${mockConfig.body.id}`) + .set('Authorization', `Bearer ${authToken}`) + .then(res => { + const hasKeys = configFields.reduce((acc, x) => acc && res.body.hasOwnProperty(x)); + assert.equal(res.status, 201, 'failed status check'); + assert.equal(typeof res.body, 'object', 'failed res body'); + assert.equal(res.body.id, mockConfig.body.id, 'failed id check'); + assert.isTrue(hasKeys, 'failed key compare'); + return res; + }); + }); +}); diff --git a/packages/server/test/routes/users/users.test.ts b/packages/server/test/routes/users/users.test.ts new file mode 100644 index 0000000..86b6330 --- /dev/null +++ b/packages/server/test/routes/users/users.test.ts @@ -0,0 +1,88 @@ +import chai from 'chai'; +import mongoose from 'mongoose'; +import chaiHttp from 'chai-http'; +import { userTestData } from '../../structures'; +import { TEST_DATABASE_URL } from '../../../config'; +import { userHelpers } from '../../helpers'; +import { app, runServer, closeServer } from '../../../index'; + +const assert = chai.assert; + +chai.use(chaiHttp); + +const { createMockUser, logUserIn } = userHelpers; + +const tearDownDb = () => { + return new Promise((resolve, reject) => { + mongoose.connection + .dropDatabase() + .then(result => resolve(result)) + .catch(err => reject(err)); + }); +}; + +describe('Users API', () => { + const userFields = ['id', 'username', 'email']; + + before(() => { + return runServer(TEST_DATABASE_URL); + }); + + afterEach(() => { + return tearDownDb(); + }); + + after(() => { + return closeServer(); + }); + + it('Should create a user on POST', async () => { + const mockUser = await createMockUser(); + const hasKeys = userFields.reduce((acc, x) => acc && mockUser.body.hasOwnProperty(x)); + assert.equal(mockUser.status, 200, 'failed status check'); + assert.isTrue(hasKeys, 'failed key compare'); + }); + + it('Should send back a user by id', async () => { + const mockUser = await createMockUser(); + const authToken = await logUserIn(); + let agent = chai.request.agent(app); + return agent + .get(`/users/${mockUser.body.id}`) + .set('Authorization', `Bearer ${authToken}`) + .then(res => { + const hasKeys = [...userFields, 'adyenKey', 'merchantAccounts', 'configurations'].reduce((acc, x) => acc && res.body.hasOwnProperty(x)); + assert.equal(res.status, 201, 'failed status check'); + assert.equal(typeof res.body, 'object', 'failed body type compare'); + assert.equal(res.body.id, mockUser.body.id, 'failed id compare'); + assert.isTrue(hasKeys, 'failed key compare'); + return res; + }); + }); + + it('Should update users on PUT', async () => { + const mockUser = await createMockUser(); + const authToken = await logUserIn(); + const mockPayload = { + id: mockUser.body.id, + adyenKey: userTestData.wrongAuthToken, + merchantAccounts: ['TestMerchant1', 'TestMerchant2'] + }; + + let agent = chai.request.agent(app); + return agent + .put(`/users/${mockUser.body.id}`) + .set('Authorization', `Bearer ${authToken}`) + .send(mockPayload) + .then(res => { + const hasValues = mockPayload.merchantAccounts.reduce((acc, x) => acc && res.body.merchantAccounts.includes(x)); + assert.equal(res.status, 200, 'failed status check'); + assert.equal(typeof res.body, 'object', 'failed body type compare'); + assert.equal(res.body.id, mockPayload.id, 'failed id compare'); + assert.equal(res.body.adyenKey, mockPayload.adyenKey.substring(mockPayload.adyenKey.length - 5), 'failed adyenKey compare'); + assert.equal(res.body.merchantAccounts.length, 2, 'failed length compare'); + assert.isTrue(hasValues, 'failed value compare'); + return res; + }); + }); +}); diff --git a/packages/server/test/structures/adyen-responses/index.ts b/packages/server/test/structures/adyen-responses/index.ts new file mode 100644 index 0000000..f19b436 --- /dev/null +++ b/packages/server/test/structures/adyen-responses/index.ts @@ -0,0 +1,5 @@ +import sessionsResponse from './sessionsResponse.json'; +import paymentsResponse from './paymentsResponse.json'; +import paymentMethodsResponse from './paymentMethodsResponse.json'; + +export { sessionsResponse, paymentsResponse, paymentMethodsResponse }; diff --git a/packages/server/test/structures/adyen-responses/paymentMethodsResponse.json b/packages/server/test/structures/adyen-responses/paymentMethodsResponse.json new file mode 100644 index 0000000..49d7e4c --- /dev/null +++ b/packages/server/test/structures/adyen-responses/paymentMethodsResponse.json @@ -0,0 +1,40 @@ +{ + "paymentMethods": [ + { + "brands": ["visa", "mc", "discover", "cup", "maestro", "diners", "jcb"], + "name": "Credit Card", + "type": "scheme" + }, + { + "configuration": { + "intent": "capture" + }, + "name": "PayPal", + "type": "paypal" + }, + { + "name": "AliPay", + "type": "alipay" + }, + { + "name": "UnionPay", + "type": "unionpay" + }, + { + "name": "ACH Direct Debit", + "type": "ach" + }, + { + "name": "WeChat Pay", + "type": "wechatpayMiniProgram" + }, + { + "name": "WeChat Pay", + "type": "wechatpayQR" + }, + { + "name": "WeChat Pay", + "type": "wechatpayWeb" + } + ] +} diff --git a/packages/server/test/structures/adyen-responses/paymentsResponse.json b/packages/server/test/structures/adyen-responses/paymentsResponse.json new file mode 100644 index 0000000..2a67589 --- /dev/null +++ b/packages/server/test/structures/adyen-responses/paymentsResponse.json @@ -0,0 +1,36 @@ +{ + "additionalData": { + "avsResult": "5 No AVS data provided", + "eci": "07", + "threeDSVersion": "1.0.2", + "acquirerAccountCode": "TestPmmAcquirerAccountMarketPlace", + "xid": "N/A", + "cavvAlgorithm": "N/A", + "cardBin": "411111", + "threeDAuthenticated": "false", + "paymentMethodVariant": "visa", + "merchantReference": "Your order number", + "cardIssuingCountry": "NL", + "liabilityShift": "false", + "authCode": "077666", + "cardHolderName": "John Smith", + "threeDOffered": "true", + "cardIssuingBank": "ADYEN TEST BANK", + "threeDOfferedResponse": "N", + "authorisationMid": "900", + "issuerCountry": "NL", + "cvcResult": "1 Matches", + "cavv": "N/A", + "threeDAuthenticatedResponse": "N/A", + "threeds2.cardEnrolled": "false", + "paymentMethod": "visa", + "cardPaymentMethod": "visa" + }, + "pspReference": "S99S35XPMSGLNK82", + "resultCode": "Authorised", + "amount": { + "currency": "USD", + "value": 1000 + }, + "merchantReference": "Your order number" +} diff --git a/packages/server/test/structures/adyen-responses/sessionsResponse.json b/packages/server/test/structures/adyen-responses/sessionsResponse.json new file mode 100644 index 0000000..4765e0e --- /dev/null +++ b/packages/server/test/structures/adyen-responses/sessionsResponse.json @@ -0,0 +1,13 @@ +{ + "amount": { + "currency": "USD", + "value": 1000 + }, + "countryCode": "NL", + "expiresAt": "2022-03-16T16:59:13+01:00", + "id": "CS586835B3512556CE", + "merchantAccount": "MikeOssig", + "reference": "YOUR_PAYMENT_REFERENCE", + "returnUrl": "https://your-company.com/checkout?shopperOrder=12xy..", + "sessionData": "Ab02b4c0!BQABAgBPT6JboEAomd9Nz1SP/Vh3pcbBlAVOGBeV6IIEzyiESJvGheLpJI6iqOvNJx/NoLv5kaGiRk2e2AKcgbf0y71N4lIHJ0LuCgyXRSo2GQ9ioAymsq0IcgD7rwltoTrK0A7hH10STuz1VSLvPGgN0g+pu1rWXvBhAFf5c13nnBhd7DPfEPkOGRbUePrY9XqNK1zdOu5uZsY3RLrusRNLXn7Vt4X8ffKUT8eguLt0atIkRvWiEZtCTzU7xQhIiCltM1N9Fp8uwKrPeurObn6M/PH+3rGs0y4QWcvYyiXVULasO7aLl5mxBWF0O5Rl3r+YomFF8rvWYuaGGJFJvUrl2+Mx5xAbfQi+2uBWgAihAbb9H4CY7HhhWpf/mL0aEnBBvyYS8UC65C0K0b4T0KWRMsaS64DtTPtltzNdVszrUbEGNeNuXduJ2DlTJNNy0KBARe7+WRzS2gTDwAmN4VoES5n73ZgjnHsaOw1AZIIqRIVKpEzoJfYdhkOadKUppDsaUEP4ItNU7vqBFAzg46PdDQHrDFxQaaU89tyYZR0le3n6FbdBJOugwE3DBbUhz6wvk66lnm31rKKuyo79NoBE/WdyiKjPMvB2saf+aCR/ZSksUoHXba8vAugIi1/nXyMjo7uqGnnZbttO3b+Ydfc6xQuaH3GIJygxnO4+cIUD+vMQuxAUiV9H4C18OTRt20uy9TD7AEp7ImtleSI6IkFGMEFBQTEwM0NBNTM3RUFFRDg3QzI0REQ1MzkwOUI4MEE3OEE5MjNFMzgyM0Q2OERBQ0M5NEI5RkY4MzA1REMifSpMSO+9zCitGJwddVlL6Qt8Gmrbgu3fmUy8ZphL3Cm7LqxJA+oxQky3QwhCqwJtONcUnqMf72Nkw8AYDfaXVt/gfnBNvz0VX6jmtoSn7Xso1YkdOGIxt7hUQxwoLLCpuoZaaUQtzJiWh9js11lvGR6D2cFXFs+Hm1JPNpgxTZm/REzrN43l+BbreJB9egobCPPFHtc7tZAjyp0rzR7Whf7922MHY3n+2xFn0aRZMnuWJsOwQkT3NGCB43272FF5OyoyuYIHRcmvJQ+GxQ6Uaq555bj702rH8/L4+XZdADC4UR1vD8ZFHY4FhTSq/NBGWWeabxdHD0gxQA/SQXP89AaOzjyvR5YYoRyrxwxA3uB/qPyLndaViJ4ismUHlwSbSPCC0gG1hHZGxAErqCTltcVA8PUNrU9BPqIGzQ==" +} diff --git a/packages/server/test/structures/index.ts b/packages/server/test/structures/index.ts new file mode 100644 index 0000000..f6fd680 --- /dev/null +++ b/packages/server/test/structures/index.ts @@ -0,0 +1,2 @@ +export * as adyenTestData from './adyen-responses'; +export * as userTestData from './users'; diff --git a/packages/server/test/structures/users/index.ts b/packages/server/test/structures/users/index.ts new file mode 100644 index 0000000..2dbf33e --- /dev/null +++ b/packages/server/test/structures/users/index.ts @@ -0,0 +1,7 @@ +import testUserData from './testUser.json'; +import testConfigData from './testConfigs.json'; + +const wrongAuthToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjIzMzc5YjljZjQyNjVjMDNmYjc4ZmI0IiwidXNlcm5hbWUiOiJ1c2VybmFtZSIsImVtYWlsIjoidGVzdEB0ZXN0LmNvbSIsImFkeWVuS2V5IjpudWxsLCJtZXJjaGFudEFjY291bnRzIjpbXSwiY29uZmlndXJhdGlvbnMiOltdfSwiaWF0IjoxNjQ3NTQwNjY1LCJleHAiOjE2NDc2MjcwNjUsInN1YiI6InVzZXJuYW1lIn0.QCj6r6-nM9s8unajhUTc3He7Ga_tSxs_rW_FwxVqsNo'; + +export { testUserData, testConfigData, wrongAuthToken }; diff --git a/packages/server/test/structures/users/testConfigs.json b/packages/server/test/structures/users/testConfigs.json new file mode 100644 index 0000000..74a042c --- /dev/null +++ b/packages/server/test/structures/users/testConfigs.json @@ -0,0 +1,7 @@ +{ + "id": "62323c39295cd4f531dc3346", + "owner": "62323c39295cd4f531dc3341", + "name": "testConfig-1", + "version": 1, + "configuration": "{\"testField\": \"active\"}" +} diff --git a/packages/server/test/structures/users/testUser.json b/packages/server/test/structures/users/testUser.json new file mode 100644 index 0000000..9904ba8 --- /dev/null +++ b/packages/server/test/structures/users/testUser.json @@ -0,0 +1,5 @@ +{ + "username": "username", + "password": "password", + "email": "test@test.com" +} diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json new file mode 100644 index 0000000..6512d3a --- /dev/null +++ b/packages/server/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "types": ["mocha"], + "target": "es6", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true + } +}