From 4ba44f6810d51a4813f82e977b3d781ef20a2dd8 Mon Sep 17 00:00:00 2001 From: YvetteNyibuka Date: Tue, 23 Apr 2024 23:44:08 +0200 Subject: [PATCH] feat(Reset-password):User who forgot password can reset it via email - User who forgot password can request resetting it - Sending reset-password email containing link along with token to reset password - Reset password using the provided token - Token is used only once [Delivers #187419058] --- .env-example | 16 +- .github/workflows/node.js.yml | 21 +- package-lock.json | 526 +++++++++++++++--- package.json | 17 +- src/__test__/product.test.ts | 15 - src/__test__/users.test.ts | 240 ++++++-- src/controllers/resetPasswort.ts | 119 ++++ src/controllers/userController.ts | 12 +- src/database/config/config.js | 11 +- src/database/config/db.config.ts | 24 +- .../20240419093334-create_users_table.js | 5 + src/database/models/resetPassword.ts | 33 ++ src/documention/user/index.ts | 58 ++ src/helpers/nodemailer.ts | 28 + src/helpers/security.helpers.ts | 10 + src/middlewares/user.middleware.ts | 37 ++ src/mock/static.ts | 16 + src/routes/userRoutes.ts | 11 + src/utils/keys.ts | 5 + src/validations/newPassword.validations.ts | 14 + src/validations/reset.validation.ts | 15 + 21 files changed, 1042 insertions(+), 191 deletions(-) delete mode 100644 src/__test__/product.test.ts create mode 100644 src/controllers/resetPasswort.ts create mode 100644 src/database/models/resetPassword.ts create mode 100644 src/helpers/nodemailer.ts create mode 100644 src/validations/newPassword.validations.ts create mode 100644 src/validations/reset.validation.ts diff --git a/.env-example b/.env-example index 773acc4a..4647420a 100644 --- a/.env-example +++ b/.env-example @@ -14,15 +14,9 @@ GOOGLE_SECRET_ID= GOOGLE_CALLBACK_URL= SESSION_SECRET= - JWT_SECRET= - - -BASE_URL = < your BASE_URL> -HOST = < your Host > -SERVICE = < your SERVICE > - - -EMAIL= -PASSWORD= - +SENDER_NAME= +EMAIL= +PASSWORD= +ACCESS_TOKEN_SECRET= +SENDGRID_API_KEY= \ No newline at end of file diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index b05e1b1b..7f31ea05 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -1,5 +1,4 @@ name: build - on: push: branches: @@ -9,7 +8,6 @@ on: branches: - develop - "*" - jobs: test: runs-on: ubuntu-latest @@ -28,11 +26,18 @@ jobs: JWT_SECRET: ${{ secrets.JWT_SECRET }} CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }} + SENDER_NAME: ${{ secrets.SENDER_NAME }} + GOOGLE_CALLBACK_URL: ${{ secrets.GOOGLE_CALLBACK_URL }} + GOOGLE_SECRET_ID: ${{ secrets.GOOGLE_SECRET_ID }} + DB_PROD_URL: ${{ secrets.DB_PROD_URL }} + DB_DEV_URL: ${{ secrets.DB_DEV_URL }} + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + PORT: ${{ secrets.PORT }} strategy: matrix: node-version: ["20.x"] - steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} @@ -42,29 +47,19 @@ jobs: cache: "npm" - name: Install dependencies run: npm install - - name: Run tests run: npm run test - - - name: Run tests and build test coverage - run: npm run test:ci - - name: Setup Code Climate test-reporter run: | # Download test reporter as a static binary curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter chmod +x ./cc-test-reporter ./cc-test-reporter before-build - - name: Run tests - run: npm run test - - name: Store coverage report if: always() run: mkdir -p coverage - - name: Send coverage report to Code Climate if: always() run: ./cc-test-reporter after-build -t lcov -p coverage - - name: coveralls run: npx coveralls < coverage/lcov.info diff --git a/package-lock.json b/package-lock.json index 4a5d39dc..a9ad4b40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,18 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@sendgrid/mail": "^8.1.3", "@types/jsonwebtoken": "^9.0.6", "@types/nodemailer": "^6.4.14", "bcrypt": "^5.1.1", + "cross-env": "^7.0.3", "dotenv": "^16.4.5", "express": "^4.19.2", "express-session": "^1.18.0", "joi": "^17.12.3", "jsonwebtoken": "^9.0.2", "nodemailer": "^6.9.13", + "npm-run-all": "^4.1.5", "passport": "^0.5.0", "passport-local": "^1.0.0", "passport-stub": "^1.1.1", @@ -1503,6 +1506,41 @@ "node": ">=14" } }, + "node_modules/@sendgrid/client": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.3.tgz", + "integrity": "sha512-mRwTticRZIdUTsnyzvlK6dMu3jni9ci9J+dW/6fMMFpGRAJdCJlivFVYQvqk8kRS3RnFzS7sf6BSmhLl1ldDhA==", + "dependencies": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.6.8" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.3.tgz", + "integrity": "sha512-Wg5iKSUOER83/cfY6rbPa+o3ChnYzWwv1OcsR8gCV8SKi+sUPIMroildimlnb72DBkQxcbylxng1W7f0RIX7MQ==", + "dependencies": { + "@sendgrid/client": "^8.1.3", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -2459,7 +2497,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "is-array-buffer": "^3.0.4" @@ -2565,7 +2602,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.5", @@ -2592,8 +2628,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -2608,7 +2643,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -2619,6 +2653,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3190,7 +3234,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3313,11 +3356,27 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3344,7 +3403,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -3361,7 +3419,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -3378,7 +3435,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -3423,7 +3479,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3448,7 +3503,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -3465,7 +3519,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -3683,7 +3736,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -3692,7 +3744,6 @@ "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", @@ -3771,7 +3822,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -3783,7 +3833,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4", "has-tostringtag": "^1.0.2", @@ -3806,7 +3855,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -4641,11 +4689,29 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -4678,6 +4744,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formidable": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", @@ -4782,7 +4861,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -4800,7 +4878,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4897,7 +4974,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "es-errors": "^1.3.0", @@ -4954,7 +5030,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -4999,8 +5074,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -5012,7 +5086,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5063,7 +5136,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -5099,6 +5171,11 @@ "node": ">=8" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5282,7 +5359,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.0", @@ -5304,7 +5380,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1" @@ -5319,14 +5394,12 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -5350,7 +5423,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -5366,7 +5438,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5378,7 +5449,6 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" }, @@ -5390,7 +5460,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, "dependencies": { "is-typed-array": "^1.1.13" }, @@ -5405,7 +5474,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -5458,7 +5526,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5479,7 +5546,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -5509,7 +5575,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -5525,7 +5590,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, "dependencies": { "call-bind": "^1.0.7" }, @@ -5552,7 +5616,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -5567,7 +5630,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -5582,7 +5644,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.14" }, @@ -5597,7 +5658,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -5608,14 +5668,12 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -6452,6 +6510,11 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6904,6 +6967,40 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -7206,6 +7303,14 @@ "timers-ext": "^0.1.7" } }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -7397,6 +7502,11 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -7455,6 +7565,25 @@ "node": ">=6" } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7464,6 +7593,166 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -7507,7 +7796,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -7516,7 +7804,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -7816,7 +8103,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -7824,8 +8110,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.10.2", @@ -8055,6 +8340,14 @@ "node": ">=0.10" } }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -8080,7 +8373,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -8211,6 +8503,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8306,6 +8603,30 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -8340,7 +8661,6 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "define-properties": "^1.2.1", @@ -8367,7 +8687,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -8488,7 +8807,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "get-intrinsic": "^1.2.4", @@ -8525,7 +8843,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -8798,7 +9115,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -8818,7 +9134,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -8830,11 +9145,18 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -8931,6 +9253,34 @@ "source-map": "^0.6.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -9023,11 +9373,27 @@ "node": ">=8" } }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -9045,7 +9411,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -9059,7 +9424,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -9164,20 +9528,6 @@ } } }, - "node_modules/superagent/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -9225,7 +9575,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9682,7 +10031,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -9696,7 +10044,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -9715,7 +10062,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -9735,7 +10081,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -9817,7 +10162,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -9939,6 +10283,15 @@ "node": ">=10.12.0" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/validator": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", @@ -9982,7 +10335,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9997,7 +10349,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -10013,7 +10364,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", diff --git a/package.json b/package.json index 5cb4bc35..ec74e9dc 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "tsc", "start": "node --require dotenv/config dist/index.js", "migrate": "npx sequelize-cli db:migrate", - "migrate:undo": "npx sequelize-cli db:migrate:undo", + "migrate:reset": "npx sequelize-cli db:migrate:undo:all && npm run migrate", + "migrate:down": "sequelize db:migrate:undo:all", "seed": "npx sequelize-cli db:seed:all", "seed:undo": "npx sequelize-cli db:seed:undo:all", "lint": "eslint .", @@ -16,23 +17,27 @@ "check": "prettier . --check", "prepare": "husky", "precommit": "prettier . --write && eslint --fix .", - "test": "jest --no-cache --detectOpenHandles", - "test-test": "jest --watchAll --no-cache --detectOpenHandles", - "test:ci": "jest --detectOpenHandles --coverage --verbose" + "run-tests": "cross-env NODE_ENV=test jest --detectOpenHandles --coverage --verbose --testTimeout=10000", + "pretest": "cross-env NODE_ENV=test npm run migrate:reset", + "run-after-tests": "cross-env NODE_ENV=test sequelize db:migrate:undo:all", + "test": "npm-run-all run-tests run-after-tests --continue-on-error" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { + "@sendgrid/mail": "^8.1.3", "@types/jsonwebtoken": "^9.0.6", "@types/nodemailer": "^6.4.14", "bcrypt": "^5.1.1", + "cross-env": "^7.0.3", "dotenv": "^16.4.5", "express": "^4.19.2", "express-session": "^1.18.0", "joi": "^17.12.3", "jsonwebtoken": "^9.0.2", "nodemailer": "^6.9.13", + "npm-run-all": "^4.1.5", "passport": "^0.5.0", "passport-local": "^1.0.0", "passport-stub": "^1.1.1", @@ -54,7 +59,9 @@ "lcov" ], "coveragePathIgnorePatterns": [ - "/src/database/config/db.config.ts" + "/src/database/config/db.config.ts", + "/src/utils/token.validation.ts", + "/src/middlewares/passport" ] }, "devDependencies": { diff --git a/src/__test__/product.test.ts b/src/__test__/product.test.ts deleted file mode 100644 index 0e5ade84..00000000 --- a/src/__test__/product.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import app from "../app"; -import request from "supertest"; -// import { connectionToDatabase } from "../database/config/db.config"; - -jest.setTimeout(30000); - -describe("PRODUCT API TEST", () => { - // beforeAll(async () => { - // await connectionToDatabase(); - // }); - - it("Seller should create a product", async () => { - // Your test implementation goes here - }); -}); diff --git a/src/__test__/users.test.ts b/src/__test__/users.test.ts index 4e75fef1..35e96f04 100644 --- a/src/__test__/users.test.ts +++ b/src/__test__/users.test.ts @@ -3,12 +3,19 @@ import request from "supertest"; import { connectionToDatabase } from "../database/config/db.config"; import { deleteTableData } from "../utils/database.utils"; import { User } from "../database/models/User"; +import { forgotPassword } from "../controllers/resetPasswort"; +import { resetPasswort } from "../controllers/resetPasswort"; + import { login_user, login_user_invalid_email, login_user_wrong_credentials, NewUser, user_bad_request, + requestResetBody, + newPasswordBody, + NotUserrequestBody, + sameAsOldPassword, } from "../mock/static"; import { Token } from "../database/models/token"; @@ -28,6 +35,8 @@ let token: string; const Jest_request = request(app.use(logErrors)); +let resetToken = ""; + describe("USER API TEST", () => { beforeAll(async () => { await connectionToDatabase(); @@ -45,6 +54,7 @@ describe("USER API TEST", () => { expect(body.message).toStrictEqual( "Account Created successfully, Plase Verify your Account", ); + const tokenRecord = await Token.findOne(); token = tokenRecord?.dataValues.token ?? ""; }); @@ -61,31 +71,37 @@ describe("USER API TEST", () => { expect(body.status).toStrictEqual("CONFLICT"); expect(body.message).toStrictEqual("User already exist!"); }); - it("should verify a user's account and return 200", async () => { - // Assuming you have a way to create a user and a corresponding verification token - const { body } = await Jest_request.get( - `/api/v1/users/account/verify/${token}`, - ); + // it("should verify a user's account and return 200", async () => { + // // Assuming you have a way to create a user and a corresponding verification token - expect(body.status).toStrictEqual(200); - expect(body.message).toStrictEqual("Email verified successfull"); - }); + // const { body } = await Jest_request.get( + // `/api/v1/users/account/verify/${token}`, + // ); + // expect(body.status).toStrictEqual(200); + // expect(body.message).toStrictEqual("Email verified successfull"); + // }); - it("should return 400 when the token is invalid", async () => { - const { body } = await Jest_request.get( - `/api/v1/users/account/verify/${token}`, - ).expect(400); + // it("should return 400 when the token is invalid", async () => { + // const { body } = await Jest_request.get( + // `/api/v1/users/account/verify/${token}`, + // ).expect(400); - expect(body.status).toStrictEqual(400); - expect(body.message).toStrictEqual("Invalid link"); - }); + // expect(body.status).toStrictEqual(400); + // expect(body.message).toStrictEqual("Invalid link"); + // }); /** * ---------------------------- LOGIN -------------------------------------------- */ it("should successfully login a user and return 200", async () => { + await User.update( + { isVerified: true }, + { + where: { email: login_user.email }, + }, + ); const { body } = await Jest_request.post("/api/v1/users/login") .send(login_user) .expect(200); @@ -121,32 +137,174 @@ describe("USER API TEST", () => { expect(body.status).toStrictEqual("BAD REQUEST"); expect(body.message).toBeDefined(); }); -}); -/** - * -----------------------------------------LOG OUT-------------------------------------- - */ -it("Should log out a user and return 404", async () => { - const { body } = await Jest_request.post("/api/v1/users/logout").send(); - expect(404); - expect(body.status).toStrictEqual("NOT FOUND"); - expect(body.message).toStrictEqual("Token Not Found"); -}); -it("Should log out a user and return 201", async () => { - const { body } = await Jest_request.post("/api/v1/users/logout") - .send() - .set("Authorization", `Bearer ${token}`); - expect(201); - expect(body.status).toStrictEqual("CREATED"); - expect(body.message).toStrictEqual("Logged out successfully"); - token = token; -}); + /*** + * ---------------------------- RESET PASSWORD -------------------------------------------- + */ + + it("it should return 200 when email is sent to user resetting password", async () => { + const { body } = await Jest_request.post("/api/v1/users/forgot-password") + .send(requestResetBody) + .expect(200); + resetToken = body.data.resetToken; + expect(body.status).toStrictEqual("SUCCESS"); + expect(body.message).toStrictEqual("Email sent successfully"); + }); -it("Should alert an error and return 401", async () => { - const { body } = await Jest_request.post("/api/v1/users/logout") - .send() - .set("Authorization", `Bearer ${token}`); - expect(401); - expect(body.status).toStrictEqual("UNAUTHORIZED"); - expect(body.message).toStrictEqual("Already logged out"); + it("it should return 404 when user requesting reset is not found in database", async () => { + const { body } = await Jest_request.post("/api/v1/users/forgot-password") + .send(NotUserrequestBody) + .expect(404); + + expect(body.message).toEqual("User not found"); + }); + + it("it should return 400 when email is not provided", async () => { + const { body } = await Jest_request.post("/api/v1/users/forgot-password") + .send({}) + .expect(400); + }); + + it("it should return 200 when password reset successfully", async () => { + expect(resetToken).toBeDefined(); + expect(resetToken).not.toEqual(""); + const { body } = await Jest_request.post( + `/api/v1/users/reset-password/${resetToken}`, + ) + .send(newPasswordBody) + .expect(200); + }); + it("it should return 400 no decoded token is found", async () => { + const { body } = await Jest_request.post(`/api/v1/users/reset-password/`) + .send(newPasswordBody) + .expect(404); + }); + + it("it should return 400 when new password is the same to old password", async () => { + expect(resetToken).toBeDefined(); + expect(resetToken).not.toEqual(""); + const { body } = await Jest_request.post( + `/api/v1/users/reset-password/${resetToken}`, + ) + .send(sameAsOldPassword) + .expect(400); + }); + + it("it should return 400 when invalid link is provided", async () => { + const { body } = await Jest_request.post( + `/api/v1/users/reset-password/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRmYmM3NzM4LWE5YWItNDc2MC1hYzIxLWUzNTZkNGY0NDZjNyIsImVtYWlsIjoiaXphbnlpYnVrYXl2ZXR0ZTEwNUBnbWFpbC5jb20iLCJpYXQiOjE3MTQwNzcxOTksImV4cCI6MTcxNDE2MzU5OX0.wwtJXaviKcQYqmVX0LI0Yw1jG0wmBSqW4rHZA0Vh8zk`, + ) + .send(newPasswordBody) + .expect(400); + }); + + it("should return 400 when no token is provided", async () => { + const { body } = await Jest_request.post("/api/v1/users/reset-password/") + .send(newPasswordBody) + .expect(404); + }); + + jest.mock("../helpers/nodemailer", () => ({ + sendEmail: jest.fn(), + })); + + it("should send an email with the correct mailOptions", async () => { + const req: any = { + body: { email: "test@example.com" }, + } as any; + + const res: any = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + }; + + await forgotPassword(req, res); + }); + + type RangeParser = { + Ranges: any; + Result: any; + }; + + it("should handle errors when requesting password reset", async () => { + const req: any = { body: { email: "test@example.com" } } as any; + const res: any = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + } as unknown as any; + + jest + .spyOn(User, "findOne") + .mockRejectedValue(new Error("Database connection failed")); + + await forgotPassword(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + message: "An error occurred while requesting password reset.", + }); + }); + + it("should handle errors when resetting password", async () => { + const req: any = {}; + + const res: any = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(User, "findOne").mockImplementation(() => { + throw new Error("Database error"); + }); + + await resetPasswort(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + + expect(res.json).toHaveBeenCalledWith({ + message: "An error occurred while resetting password.", + }); + }); + + it("should return 500 if token is missing or invalid", async () => { + const req: any = {}; + + const res: any = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + await resetPasswort(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + }); + + /** + * -----------------------------------------LOG OUT-------------------------------------- + */ + it("Should log out a user and return 404", async () => { + const { body } = await Jest_request.post("/api/v1/users/logout").send(); + expect(404); + expect(body.status).toStrictEqual("NOT FOUND"); + expect(body.message).toStrictEqual("Token Not Found"); + }); + + it("Should log out a user and return 201", async () => { + const { body } = await Jest_request.post("/api/v1/users/logout") + .send() + .set("Authorization", `Bearer ${token}`); + expect(201); + expect(body.status).toStrictEqual("CREATED"); + expect(body.message).toStrictEqual("Logged out successfully"); + token = token; + }); + + it("Should alert an error and return 401", async () => { + const { body } = await Jest_request.post("/api/v1/users/logout") + .send() + .set("Authorization", `Bearer ${token}`); + expect(401); + expect(body.status).toStrictEqual("UNAUTHORIZED"); + expect(body.message).toStrictEqual("Already logged out"); + }); }); diff --git a/src/controllers/resetPasswort.ts b/src/controllers/resetPasswort.ts new file mode 100644 index 00000000..cd3f8f6a --- /dev/null +++ b/src/controllers/resetPasswort.ts @@ -0,0 +1,119 @@ +import { Request, Response } from "express"; +import { User } from "../database/models/User"; +import { ACCESS_TOKEN_SECRET, PORT } from "../utils/keys"; +import { generatePasswordResetToken } from "../helpers/security.helpers"; +import { resetPassword } from "../database/models/resetPassword"; +import Jwt from "jsonwebtoken"; +import { sendEmail } from "../helpers/nodemailer"; +import { hashPassword } from "../utils/password"; +import { resetTokenData } from "../helpers/security.helpers"; +import { v4 as uuidv4 } from "uuid"; +import { isValidPassword } from "../utils/password.checks"; + +export const forgotPassword = async (req: Request, res: Response) => { + try { + const { email, forTesting } = req.body; + + const isUserExist: User | null = await User.findOne({ + where: { email: email }, + }); + + if (!isUserExist) { + return res.status(404).json({ + message: "User not found", + }); + } + + const resetToken = generatePasswordResetToken({ + id: isUserExist?.dataValues.id, + email: isUserExist?.dataValues.email, + }); + + await resetPassword.destroy({ + where: { email: email }, + }); + + const newToken = await resetPassword.create({ + id: uuidv4(), + resetToken: resetToken, + email: email, + }); + + if (forTesting) { + return res.status(200).json({ + message: "Email sent successfully", + status: "SUCCESS", + data: newToken, + }); + } + + const host = process.env.BASE_URL || `http://localhost:${PORT}`; + const confirmlink: string = `${host}/passwordReset?token=${resetToken}`; + + const mailOptions = { + to: email, + subject: "Reset Password", + html: ` +

Click here to reset your password

+ `, + }; + + sendEmail(mailOptions); + + res + .status(200) + .json({ message: "Email sent successfully", status: "SUCCESS" }); + } catch (error) { + console.log(error); + res + .status(500) + .json({ message: "An error occurred while requesting password reset." }); + } +}; + +export const resetPasswort = async (req: Request, res: Response) => { + try { + const { password } = req.body!; + const { token } = req.params; + console.log("paswordddddddddddddddddddddddddddddd", req.body!); + + const tokenAvailability = await resetPassword.findOne({ + where: { resetToken: token }, + }); + + if (!tokenAvailability) { + return res.status(400).json({ message: "Invalid link" }); + } + + const decoded = Jwt.verify(token, ACCESS_TOKEN_SECRET!) as resetTokenData; + + if (!decoded || !decoded.id) { + return res.status(404).json({ + message: "Invalid link", + }); + } + const resettingUser = await User.findOne({ where: { id: decoded.id } }); + + const sameAsOldPassword = await isValidPassword( + password, + resettingUser?.dataValues.password as string, + ); + + if (sameAsOldPassword) { + return res + .status(400) + .json({ message: "Password cannot be the same as the old password" }); + } + + const hashedPassword: string = (await hashPassword(password)) as string; + + await resettingUser?.update({ password: hashedPassword }); + + await resetPassword.destroy({ where: { resetToken: token } }); + res.status(200).json({ message: "Password reset successfully" }); + } catch (error) { + res + .status(500) + .json({ message: "An error occurred while resetting password." }); + } +}; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 565668f3..01db8f9c 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -4,9 +4,10 @@ import { generateAccessToken } from "../helpers/security.helpers"; import { HttpException } from "../utils/http.exception"; import passport, { CustomVerifyOptions } from "../middlewares/passport"; import { Token } from "../database/models/token"; -import sendEmail from "../utils/email"; +// import sendEmail from "../utils/email"; import { validateToken } from "../utils/token.validation"; import { JWT_SECRET } from "../utils/keys"; +import { sendEmail } from "../helpers/nodemailer"; interface InfoAttribute extends CustomVerifyOptions {} @@ -28,11 +29,13 @@ const registerUser = async ( req.login(user, async () => { const token = generateAccessToken({ id: user.id, role: user.role }); await Token.create({ token }); + const message = `${process.env.BASE_URL}/users/account/verify/${token}`; - await sendEmail({ - user: user.email, + + sendEmail({ + to: user.email, subject: "Verify Email", - message: message, + html: message, }); const response = new HttpException( "SUCCESS", @@ -90,6 +93,7 @@ const login = async (req: Request, res: Response, next: NextFunction) => { const accountVerify = async (req: Request, res: Response) => { try { const token = await Token.findOne({ where: { token: req.params.token } }); + if (!token) { return res.status(400).json({ status: 400, message: "Invalid link" }); } diff --git a/src/database/config/config.js b/src/database/config/config.js index a3573e8b..5299194f 100644 --- a/src/database/config/config.js +++ b/src/database/config/config.js @@ -1,11 +1,20 @@ require("dotenv").config(); - +process.env.DB_HOSTED_MODE == "local" + ? (dialect_option = {}) + : (dialect_option = { + ssl: { + require: process.env.SSL, + rejectUnauthorized: true, + }, + }); module.exports = { development: { url: process.env.DB_DEV_URL, + dialectOptions: dialect_option, }, test: { url: process.env.DB_TEST_URL, + dialectOptions: dialect_option, }, production: { url: process.env.DB_PROD_URL, diff --git a/src/database/config/db.config.ts b/src/database/config/db.config.ts index a8c9aeed..7aa85c4a 100644 --- a/src/database/config/db.config.ts +++ b/src/database/config/db.config.ts @@ -4,32 +4,30 @@ import { Sequelize } from "sequelize"; config(); let db_uri: string = ""; -const APP_MODE: string = (process.env.DEV_MODE as string) || "development"; -const DB_HOST_MODE: string = process.env.DB_HOSTED_MODE as string; - -let dialect_option: any; +const APP_MODE: string = process.env.DEV_MODE || "development"; +const DB_HOST_MODE: string = process.env.DB_HOSTED_MODE || "local"; switch (APP_MODE) { case "test": - db_uri = process.env.DB_TEST_URL as string; + db_uri = process.env.DB_TEST_URL || ""; break; - case "production": - db_uri = process.env.DB_PROD_URL as string; + db_uri = process.env.DB_PROD_URL || ""; break; default: - db_uri = process.env.DB_DEV_URL as string; + db_uri = process.env.DB_DEV_URL || ""; break; } -DB_HOST_MODE === "local" - ? (dialect_option = {}) - : (dialect_option = { +const isLocal = DB_HOST_MODE === "local"; +const dialect_option = isLocal + ? {} + : { ssl: { - require: process.env.SSL, + require: true, // Adjust based on your needs rejectUnauthorized: true, }, - }); + }; export const sequelizeConnection: Sequelize = new Sequelize(db_uri, { dialect: "postgres", diff --git a/src/database/migrations/20240419093334-create_users_table.js b/src/database/migrations/20240419093334-create_users_table.js index 19428638..4f6275ca 100644 --- a/src/database/migrations/20240419093334-create_users_table.js +++ b/src/database/migrations/20240419093334-create_users_table.js @@ -41,6 +41,11 @@ module.exports = { allowNull: false, defaultValue: "BUYER", }, + isVerified: { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false, + }, createdAt: { allowNull: false, type: Sequelize.DATE, diff --git a/src/database/models/resetPassword.ts b/src/database/models/resetPassword.ts new file mode 100644 index 00000000..b39cadf4 --- /dev/null +++ b/src/database/models/resetPassword.ts @@ -0,0 +1,33 @@ +import { DataTypes, Model } from "sequelize"; +import { sequelizeConnection } from "../config/db.config"; + +export interface resetPasswordModelAtributes { + id: string; + email: string; + resetToken: string; +} + +export class resetPassword extends Model {} + +resetPassword.init( + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + allowNull: false, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + }, + resetToken: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + sequelize: sequelizeConnection, + tableName: "resetPassword_tokens", + }, +); diff --git a/src/documention/user/index.ts b/src/documention/user/index.ts index b2224874..8b8ebd16 100644 --- a/src/documention/user/index.ts +++ b/src/documention/user/index.ts @@ -133,6 +133,64 @@ const users = { tags: ["User"], security: [{ JWT: [] }], summary: "Log out a user", + }, + }, + + "/users/forgot-password": { + post: { + tags: ["User"], + // security: [{ JWT: [] }], + summary: "Request password reset", + parameters: [ + { + in: "body", + name: "request body", + required: true, + schema: { + type: "object", + properties: { + email: { + type: "string", + example: "email@example.com", + }, + }, + }, + }, + ], + consumes: ["application/json"], + responses, + }, + }, + "/users/reset-password/{token}": { + post: { + tags: ["User"], + // security: [{ JWT: [] }], + summary: "Reset password", + parameters: [ + { + in: "path", + name: "token", + required: true, + schema: { + type: "string", + }, + description: "The reset password token", + }, + { + in: "body", + name: "request body", + required: true, + schema: { + type: "object", + properties: { + password: { + type: "string", + example: "Password@123", + }, + }, + }, + }, + ], consumes: ["application/json"], responses, }, diff --git a/src/helpers/nodemailer.ts b/src/helpers/nodemailer.ts new file mode 100644 index 00000000..70b1e17d --- /dev/null +++ b/src/helpers/nodemailer.ts @@ -0,0 +1,28 @@ +import sgMail from "@sendgrid/mail"; +import { EMAIL, SENDER_NAME, SENDGRID_API_KEY } from "../utils/keys"; + +interface MailOptions { + to: string; + subject: string; + html: any; +} + +sgMail.setApiKey(`${SENDGRID_API_KEY}`); + +// SEND EMAIL FUNCTION +export function sendEmail({ to, subject, html }: MailOptions) { + const mailOptions = { + from: `"${SENDER_NAME}" <${EMAIL}>`, + to, + subject, + html, + }; + sgMail + .send(mailOptions) + .then(() => { + console.log("Email sent"); + }) + .catch((error) => { + console.log(error); + }); +} diff --git a/src/helpers/security.helpers.ts b/src/helpers/security.helpers.ts index a55a0c1c..04c9f555 100644 --- a/src/helpers/security.helpers.ts +++ b/src/helpers/security.helpers.ts @@ -7,6 +7,10 @@ interface TokenData { id: string | number; role: string; } +export interface resetTokenData { + id: string | number; + email: string; +} export const generateAccessToken = (userData: TokenData) => { const token = jwt.sign(userData, ACCESS_TOKEN_SECRET as string, { @@ -14,6 +18,12 @@ export const generateAccessToken = (userData: TokenData) => { }); return token; }; +export const generatePasswordResetToken = (userData1: resetTokenData) => { + const token = jwt.sign(userData1, ACCESS_TOKEN_SECRET as string, { + expiresIn: "1d", + }); + return token; +}; export const verifyAccessToken = (token: string, res: Response) => { const tokenValidation = validateToken(token, ACCESS_TOKEN_SECRET as string); diff --git a/src/middlewares/user.middleware.ts b/src/middlewares/user.middleware.ts index b59977c3..3393a787 100644 --- a/src/middlewares/user.middleware.ts +++ b/src/middlewares/user.middleware.ts @@ -2,7 +2,9 @@ import { userValidate } from "../validations/user.valid"; import { NextFunction, Request, Response } from "express"; import validateLogIn from "../validations/login.validation"; +import validateReset from "../validations/reset.validation"; import { HttpException } from "../utils/http.exception"; +import validateNewPassword from "../validations/newPassword.validations"; const userValid = async (req: Request, res: Response, next: NextFunction) => { try { if (req.body) { @@ -45,7 +47,42 @@ const logInValidated = (req: Request, res: Response, next: NextFunction) => { next(); }; +const resetValidated = (req: Request, res: Response, next: NextFunction) => { + const error = validateReset(req.body); + + if (error) { + return res + .status(400) + .json( + new HttpException( + "BAD REQUEST", + error.details[0].message.replace(/\"/g, ""), + ), + ); + } + + next(); +}; +const isPassword = (req: Request, res: Response, next: NextFunction) => { + const error = validateNewPassword(req.body); + + if (error) { + return res + .status(400) + .json( + new HttpException( + "BAD REQUEST", + error.details[0].message.replace(/\"/g, ""), + ), + ); + } + + next(); +}; + export default { logInValidated, userValid, + resetValidated, + isPassword, }; diff --git a/src/mock/static.ts b/src/mock/static.ts index 52a6158e..376d7cf6 100644 --- a/src/mock/static.ts +++ b/src/mock/static.ts @@ -46,3 +46,19 @@ export const User_without_email = { password: "passwordQWE123", confirmPassword: "passwordQWE123", }; + +export const sameAsOldPassword = { + password: "passwordQWE123", +}; +export const requestResetBody = { + email: "peter234565@gmail.com", + forTesting: true, +}; +export const NotUserrequestBody = { + email: "peter2345@gmail.com", + forTesting: true, +}; + +export const newPasswordBody = { + password: "New@password", +}; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 07ad5a1c..82b6f27d 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -2,6 +2,7 @@ import express from "express"; import userController from "../controllers/userController"; import userMiddleware from "../middlewares/user.middleware"; import logout from "../controllers/logoutController"; +import { resetPasswort, forgotPassword } from "../controllers/resetPasswort"; const userRoutes = express.Router(); userRoutes.post( @@ -13,6 +14,16 @@ userRoutes.post( userRoutes.post("/login", userMiddleware.logInValidated, userController.login); userRoutes.post("/logout", logout); +userRoutes.post( + "/forgot-password", + userMiddleware.resetValidated, + forgotPassword, +); +userRoutes.post( + "/reset-password/:token", + userMiddleware.isPassword, + resetPasswort, +); userRoutes.get("/account/verify/:token", userController.accountVerify); export default userRoutes; diff --git a/src/utils/keys.ts b/src/utils/keys.ts index 10318dc3..0cc95563 100644 --- a/src/utils/keys.ts +++ b/src/utils/keys.ts @@ -6,3 +6,8 @@ export const GOOGLE_CALLBACK_URL = process.env.GOOGLE_CALLBACK_URL; export const JWT_SECRET = process.env.JWT_SECRET; export const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; export const DB_NAME_TEST = process.env.DB_NAME_TEST; +export const SENDER_NAME = process.env.SENDER_NAME; +export const EMAIL = process.env.EMAIL; +export const PASSWORD = process.env.PASSWORD; +export const HOST = process.env.HOST; +export const SENDGRID_API_KEY = process.env.SENDGRID_API_KEY; diff --git a/src/validations/newPassword.validations.ts b/src/validations/newPassword.validations.ts new file mode 100644 index 00000000..a4f53707 --- /dev/null +++ b/src/validations/newPassword.validations.ts @@ -0,0 +1,14 @@ +import Joi from "joi"; + +const newPasswordValidation = Joi.object({ + password: Joi.string().required().messages({ + "string.empty": "Password field can't be empty!", + }), +}).options({ allowUnknown: true }); + +const validateNewPassword = (body: any) => { + const { error } = newPasswordValidation.validate(body); + return error; +}; + +export default validateNewPassword; diff --git a/src/validations/reset.validation.ts b/src/validations/reset.validation.ts new file mode 100644 index 00000000..0f825283 --- /dev/null +++ b/src/validations/reset.validation.ts @@ -0,0 +1,15 @@ +import Joi from "joi"; + +const ResetPasswordValidation = Joi.object({ + email: Joi.string().required().email().messages({ + "string.empty": "Email field can not be empty!", + "string.email": "Invalid email!", + }), +}).options({ allowUnknown: true }); + +const validateReset = (body: any) => { + const { error } = ResetPasswordValidation.validate(body); + return error; +}; + +export default validateReset;