diff --git a/.env-example b/.env-example index 6b66471f..4e86385d 100644 --- a/.env-example +++ b/.env-example @@ -12,11 +12,14 @@ ACCESS_TOKEN_SECRET= < Your ACCESS_TOKEN_SECRET > GOOGLE_CLIENT_ID= GOOGLE_SECRET_ID= GOOGLE_CALLBACK_URL= -SESSION_SECRET= -ACCESS_TOKEN_SECRET= SENDER_NAME= -EMAIL= -PASSWORD= -ACCESS_TOKEN_SECRET= -SENDGRID_API_KEY= \ No newline at end of file +SENDGRID_API_KEY= + +BASE_URL = < your BASE_URL> +HOST = < your Host > +SERVICE = < your SERVICE > + +EMAIL= +PASSWORD= + diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 6fe2265e..d6504e01 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -33,6 +33,12 @@ jobs: DB_DEV_URL: ${{ secrets.DB_DEV_URL }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + DEPLOYED_URL: ${{ secrets.DEPLOYED_URL }} + CLOUDINARY_CLOUD_NAME: ${{ secrets.CLOUDINARY_CLOUD_NAME }} + CLOUDINARY_API_KEY: ${{ secrets.CLOUDINARY_API_KEY }} + CLOUDINARY_API_SECRET: ${{ secrets.CLOUDINARY_API_SECRET }} + CLOUDINARY_FOLDER_NAME: ${{ secrets.CLOUDINARY_FOLDER_NAME }} + strategy: matrix: node-version: ["20.x"] diff --git a/package-lock.json b/package-lock.json index 949bb66b..003d591e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@types/jsonwebtoken": "^9.0.6", "@types/nodemailer": "^6.4.14", "bcrypt": "^5.1.1", + "cors": "^2.8.5", "cross-env": "^7.0.3", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -22,6 +23,7 @@ "nodemailer": "^6.9.13", "npm-run-all": "^4.1.5", "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", "passport-local": "^1.0.0", "passport-stub": "^1.1.1", "pg": "^8.11.5", @@ -29,7 +31,7 @@ "randomatic": "^3.1.1", "reflect-metadata": "^0.2.2", "sequelize": "^6.37.3", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "devDependencies": { "@eslint/js": "^9.0.0", @@ -42,6 +44,7 @@ "@types/node": "^20.12.7", "@types/nodemailer": "^6.4.14", "@types/passport": "^1.0.16", + "@types/passport-google-oauth20": "^2.0.14", "@types/passport-local": "^1.0.38", "@types/pg": "^8.11.5", "@types/randomatic": "^3.1.5", @@ -51,6 +54,7 @@ "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", + "cross-env": "^7.0.3", "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", @@ -58,6 +62,7 @@ "husky": "^9.0.11", "jest": "^29.7.0", "lint-staged": "^15.2.2", + "npm-run-all": "^4.1.5", "passport-stub": "^1.1.1", "prettier": "3.2.5", "sequelize-cli": "^6.6.2", @@ -1846,6 +1851,15 @@ "@types/node": "*" } }, + "node_modules/@types/oauth": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.4.tgz", + "integrity": "sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/passport": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz", @@ -1855,6 +1869,17 @@ "@types/express": "*" } }, + "node_modules/@types/passport-google-oauth20": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.14.tgz", + "integrity": "sha512-ZaZpRUAeMl3vy298ulKO1wGLn9SQtj/CyIfZL/Px5xU9pybMiQU3mhXDCBiWSbg0EK9uXT4ZoWC3ktuWY+5fwQ==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, "node_modules/@types/passport-local": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", @@ -1866,6 +1891,17 @@ "@types/passport-strategy": "*" } }, + "node_modules/@types/passport-oauth2": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.15.tgz", + "integrity": "sha512-9cUTP/HStNSZmhxXGuRrBJfEWzIEJRub2eyJu3CvkA+8HAMc9W3aKdFhVq+Qz1hi42qn+GvSAnz3zwacDSYWpw==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "node_modules/@types/passport-strategy": { "version": "0.2.38", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", @@ -2507,6 +2543,7 @@ "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" @@ -2612,6 +2649,7 @@ "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", @@ -2653,6 +2691,7 @@ "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" }, @@ -2794,6 +2833,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -3339,6 +3386,18 @@ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -3370,6 +3429,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, "dependencies": { "cross-spawn": "^7.0.1" }, @@ -3387,6 +3447,7 @@ "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", @@ -3413,6 +3474,7 @@ "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", @@ -3429,6 +3491,7 @@ "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", @@ -3445,6 +3508,7 @@ "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", @@ -3513,6 +3577,7 @@ "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", @@ -3746,6 +3811,7 @@ "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" } @@ -3754,6 +3820,7 @@ "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", @@ -3832,6 +3899,7 @@ "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" }, @@ -3843,6 +3911,7 @@ "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", @@ -3865,6 +3934,7 @@ "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", @@ -4722,6 +4792,7 @@ "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" } @@ -4857,6 +4928,7 @@ "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", @@ -4874,6 +4946,7 @@ "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" } @@ -4970,6 +5043,7 @@ "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", @@ -5026,6 +5100,7 @@ "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" }, @@ -5070,7 +5145,8 @@ "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==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -5082,6 +5158,7 @@ "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" } @@ -5132,6 +5209,7 @@ "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" }, @@ -5170,7 +5248,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==" + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true }, "node_modules/html-escaper": { "version": "2.0.2", @@ -5355,6 +5434,7 @@ "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", @@ -5376,6 +5456,7 @@ "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" @@ -5390,12 +5471,14 @@ "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==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "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" }, @@ -5419,6 +5502,7 @@ "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" @@ -5434,6 +5518,7 @@ "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" }, @@ -5445,6 +5530,7 @@ "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" }, @@ -5456,6 +5542,7 @@ "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" }, @@ -5470,6 +5557,7 @@ "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" }, @@ -5522,6 +5610,7 @@ "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" }, @@ -5542,6 +5631,7 @@ "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" }, @@ -5571,6 +5661,7 @@ "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" @@ -5586,6 +5677,7 @@ "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" }, @@ -5612,6 +5704,7 @@ "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" }, @@ -5626,6 +5719,7 @@ "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" }, @@ -5640,6 +5734,7 @@ "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" }, @@ -5654,6 +5749,7 @@ "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" }, @@ -5664,12 +5760,14 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -6509,7 +6607,8 @@ "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==" + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -6975,6 +7074,7 @@ "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==", + "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -6989,6 +7089,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -7001,6 +7102,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "engines": { "node": ">=4" } @@ -7316,6 +7418,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, "engines": { "node": ">= 0.10.0" } @@ -7514,7 +7617,8 @@ "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "node_modules/node-addon-api": { "version": "5.1.0", @@ -7578,6 +7682,7 @@ "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==", + "dev": true, "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -7589,6 +7694,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, "bin": { "semver": "bin/semver" } @@ -7606,6 +7712,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", @@ -7630,6 +7737,7 @@ "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==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -7641,6 +7749,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -7654,6 +7763,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -7661,12 +7771,14 @@ "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==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "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==", + "dev": true, "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -7682,6 +7794,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -7690,6 +7803,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -7698,6 +7812,7 @@ "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==", + "dev": true, "engines": { "node": ">=4" } @@ -7706,6 +7821,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, "bin": { "pidtree": "bin/pidtree.js" }, @@ -7717,6 +7833,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, "bin": { "semver": "bin/semver" } @@ -7725,6 +7842,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -7736,6 +7854,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7744,6 +7863,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -7755,6 +7875,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -7785,6 +7906,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/oauth": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz", + "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7805,6 +7931,7 @@ "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" } @@ -7813,6 +7940,7 @@ "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", @@ -8067,6 +8195,17 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", @@ -8078,6 +8217,25 @@ "node": ">= 0.4.0" } }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -8113,6 +8271,7 @@ "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" } @@ -8120,7 +8279,8 @@ "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==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-scurry": { "version": "1.10.2", @@ -8354,6 +8514,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, "engines": { "node": ">=4" } @@ -8383,6 +8544,7 @@ "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" } @@ -8638,6 +8800,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -8651,6 +8814,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, "dependencies": { "pify": "^3.0.0" }, @@ -8692,6 +8856,7 @@ "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", @@ -8718,6 +8883,7 @@ "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", @@ -8838,6 +9004,7 @@ "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", @@ -8874,6 +9041,7 @@ "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", @@ -9146,6 +9314,7 @@ "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", @@ -9165,6 +9334,7 @@ "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" }, @@ -9176,6 +9346,7 @@ "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" } @@ -9184,6 +9355,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9288,6 +9460,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -9296,12 +9469,14 @@ "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==" + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true }, "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==", + "dev": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -9310,7 +9485,8 @@ "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==" + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true }, "node_modules/split2": { "version": "4.2.0", @@ -9408,6 +9584,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -9425,6 +9602,7 @@ "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", @@ -9442,6 +9620,7 @@ "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", @@ -9455,6 +9634,7 @@ "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", @@ -9606,6 +9786,7 @@ "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" }, @@ -10062,6 +10243,7 @@ "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", @@ -10075,6 +10257,7 @@ "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", @@ -10093,6 +10276,7 @@ "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", @@ -10112,6 +10296,7 @@ "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", @@ -10177,6 +10362,11 @@ "node": ">= 0.8" } }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/umzug": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", @@ -10193,6 +10383,7 @@ "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", @@ -10318,6 +10509,7 @@ "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==", + "dev": true, "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -10366,6 +10558,7 @@ "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" }, @@ -10380,6 +10573,7 @@ "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", @@ -10395,6 +10589,7 @@ "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 64f7c549..0004ff2e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "tsc", "start": "node --require dotenv/config dist/index.js", "migrate": "npx sequelize-cli db:migrate", - "migrate:reset": "npx sequelize-cli db:migrate:undo:all && npm run migrate", + "migrate:reset": "npx sequelize-cli db:migrate:undo:all && npm run migrate && npm run seed", "migrate:down": "sequelize db:migrate:undo:all", "seed": "npx sequelize-cli db:seed:all", "seed:undo": "npx sequelize-cli db:seed:undo:all", @@ -23,7 +23,6 @@ "test": "npm-run-all run-tests run-after-tests --continue-on-error" }, "keywords": [], - "author": "", "license": "ISC", "dependencies": { "@sendgrid/mail": "^8.1.3", @@ -31,6 +30,7 @@ "@types/nodemailer": "^6.4.14", "bcrypt": "^5.1.1", "cross-env": "^7.0.3", + "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", "express-session": "^1.18.0", @@ -39,6 +39,7 @@ "nodemailer": "^6.9.13", "npm-run-all": "^4.1.5", "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", "passport-local": "^1.0.0", "passport-stub": "^1.1.1", "pg": "^8.11.5", @@ -46,7 +47,7 @@ "randomatic": "^3.1.1", "reflect-metadata": "^0.2.2", "sequelize": "^6.37.3", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "jest": { "preset": "ts-jest", @@ -60,13 +61,15 @@ "lcov" ], "coveragePathIgnorePatterns": [ - "/src/utils/token.validation.ts", "/src/services/mailService.ts", "/src/middlewares/passport.ts", + "/src/middlewares/auth.ts", + "/src/database/config/config.js", "/src/database/config/db.config.ts", "/src/utils", "src/helpers", - "src/documention/index.ts" + "src/documention/index.ts", + "/src/helpers/security.helpers.ts" ] }, "devDependencies": { @@ -80,6 +83,7 @@ "@types/node": "^20.12.7", "@types/nodemailer": "^6.4.14", "@types/passport": "^1.0.16", + "@types/passport-google-oauth20": "^2.0.14", "@types/passport-local": "^1.0.38", "@types/pg": "^8.11.5", "@types/randomatic": "^3.1.5", @@ -89,6 +93,7 @@ "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", + "cross-env": "^7.0.3", "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", @@ -96,6 +101,7 @@ "husky": "^9.0.11", "jest": "^29.7.0", "lint-staged": "^15.2.2", + "npm-run-all": "^4.1.5", "passport-stub": "^1.1.1", "prettier": "3.2.5", "sequelize-cli": "^6.6.2", diff --git a/src/__test__/google.auth.test.ts b/src/__test__/google.auth.test.ts new file mode 100644 index 00000000..573daa79 --- /dev/null +++ b/src/__test__/google.auth.test.ts @@ -0,0 +1,49 @@ +import app from "../app"; +import request from "supertest"; +import { google_profile } from "../mock/static"; +jest.setTimeout(30000); + +function logErrors( + err: { stack: any }, + _req: any, + _res: any, + next: (arg0: any) => void, +) { + console.log(err.stack); + next(err); +} + +const Jest_request = request(app.use(logErrors)); + +describe("GOOGLE API TEST", () => { + it("should simulate Google oAuth flow and return 302", async () => { + const response = await Jest_request.get("/api/v1/users/auth/google").expect( + 302, + ); + expect(response.header.location).toStrictEqual( + "/api/v1/users/auth/google/callback", + ); + expect(response.redirect).toBeTruthy(); + expect(response.header["set-cookie"]).toBeDefined(); + }); + + it("should handle google oauth callback and display direct link", async () => { + const response = await Jest_request.get( + "/api/v1/users/auth/google/callback", + ).query(google_profile); + expect(response.status).toBe(302); + + const google_url = response.header.location; + + expect(google_url).toBeDefined(); + }); + + it("Welcome to Hacker's e-commerce backend and return 200", async () => { + const { body } = await Jest_request.get("/").expect(200); + }); + + it("should display login home page and return 200", async () => { + const { body } = await Jest_request.get("/api/v1/").expect(200); + expect(body.message).toBe("Welcome to Hacker's e-commerce backend!"); + }); +}); diff --git a/src/app.ts b/src/app.ts index 2ae3beb4..6dc8c69f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,11 +3,22 @@ import session from "express-session"; import passport from "./middlewares/passport"; import { SESSION_SECRET } from "./utils/keys"; import router from "./routes"; -import config from "./documention"; -import swaggerJsdoc from "swagger-jsdoc"; import swaggerUi from "swagger-ui-express"; import { root_home_page } from "./utils/html.utils"; +import docs from "./documention"; +import { GOOGLE_CLIENT_ID, GOOGLE_SECRET_ID } from "./utils/keys"; + const app = express(); + +const swaggerOptions = { + validatorUrl: null, + oauth: { + clientId: GOOGLE_CLIENT_ID, + clientSecret: GOOGLE_SECRET_ID, + appName: "E-Commerce", + }, +}; + app.use( session({ secret: SESSION_SECRET, @@ -19,16 +30,12 @@ app.use(passport.initialize()); app.use(passport.session()); app.use(express.json()); -const options = { - swaggerDefinition: { - ...config, - }, - apis: ["./src/documentation/*.ts"], -}; - -const swaggerSpec = swaggerJsdoc(options); +app.use( + "/api/v1/docs", + swaggerUi.serve, + swaggerUi.setup(docs, { swaggerOptions: swaggerOptions }), +); -app.use("/api/v1/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); app.use("/api/v1", router); app.get("/api/v1", (_req, res) => { res.status(200).json({ @@ -38,5 +45,4 @@ app.get("/api/v1", (_req, res) => { app.get("/", (_req, res) => { res.send(root_home_page); }); - export default app; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index a68639cb..e22dabe9 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -162,6 +162,57 @@ const accountVerify = async (req: Request, res: Response) => { } }; +export const googleAuthInit = async (req: Request, res: Response) => { + passport.authenticate("google", { scope: ["profile", "email"] }); + res.redirect("/api/v1/users/auth/google/callback"); +}; + +export const handleGoogleAuth = async ( + req: Request, + res: Response, + next: NextFunction, +) => { + try { + passport.authenticate( + "google", + async (err: Error, user: UserModelAttributes) => { + const userData = user; + const userExist = await User.findOne({ + where: { email: userData.email }, + }); + + if (userExist) { + const token = generateAccessToken({ + id: userExist.dataValues.id, + role: userExist.dataValues.role, + }); + const response = new HttpException( + "SUCCESS", + "Logged in to you account successfully!", + ).response(); + return res.status(200).json({ ...response, token }); + } + + const newUser = await User.create({ ...userData }); + await newUser.save(); + const token = generateAccessToken({ + id: newUser.dataValues.id, + role: newUser.dataValues.role, + }); + const response = new HttpException( + "SUCCESS", + "Account Created successfully!", + ).response(); + res.status(201).json({ ...response, token }); + }, + )(req, res, next); + } catch (error) { + res + .status(500) + .json(new HttpException("SERVER ERROR", "Something went wrong!")); + } +}; + const two_factor_authentication = async (req: Request, res: Response) => { try { const { otp } = req.body; @@ -201,4 +252,6 @@ export default { login, accountVerify, two_factor_authentication, + googleAuthInit, + handleGoogleAuth, }; diff --git a/src/database/config/config.js b/src/database/config/config.js index 8f5e9a37..4319732f 100644 --- a/src/database/config/config.js +++ b/src/database/config/config.js @@ -1,6 +1,5 @@ require("dotenv").config(); -//add ssl in this config process.env.DB_HOSTED_MODE == "local" ? (dialect_option = {}) : (dialect_option = { diff --git a/src/database/config/db.config.ts b/src/database/config/db.config.ts index 7aa85c4a..613ad0bf 100644 --- a/src/database/config/db.config.ts +++ b/src/database/config/db.config.ts @@ -24,7 +24,7 @@ const dialect_option = isLocal ? {} : { ssl: { - require: true, // Adjust based on your needs + require: process.env.SSL, rejectUnauthorized: true, }, }; diff --git a/src/documention/basicInfo.ts b/src/documention/basicInfo.ts new file mode 100644 index 00000000..94e26586 --- /dev/null +++ b/src/documention/basicInfo.ts @@ -0,0 +1,47 @@ +import { GOOGLE_CALLBACK_URL, PORT } from "../utils/keys"; + +const basicInfo = { + openapi: "3.0.0", + info: { + title: "E-commerce API", + description: "Ecommerce api docs", + version: "1.0.0", + }, + + servers: [ + { + url: `http://localhost:${PORT}`, + description: "Development server", + }, + { + url: "https://hackers-ec-be.onrender.com", + description: "Production server (HTTPS)", + }, + ], + security: [ + { + google_auth: [], + }, + ], + + components: { + securitySchemes: { + google_auth: { + type: "oauth2", + flows: { + authorizationCode: { + authorizationUrl: `${GOOGLE_CALLBACK_URL}`, + }, + }, + }, + + bearerAuth: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + }, + }, + }, +}; + +export default basicInfo; diff --git a/src/documention/index.ts b/src/documention/index.ts index 86f2e884..8edc9ebc 100644 --- a/src/documention/index.ts +++ b/src/documention/index.ts @@ -1,49 +1,9 @@ -import dotenv from "dotenv"; -import swaggerDoc from "./swagger.json"; -import { PORT } from "../utils/keys"; -import users from "./user"; +import basicInfo from "./basicInfo"; +import { users } from "./user"; -const defaults = swaggerDoc.paths; - -dotenv.config(); - -const SERVER_HOST = process.env.HOST || `http://localhost:${PORT}`; - -const host = - process.env.DEV_MODE === "production" - ? (SERVER_HOST as string).split("https://")[1] - : (SERVER_HOST as string).split("http://")[1]; - -const paths = { - ...defaults, - ...users, -}; - -const config = { - swagger: "2.0", - info: { - title: "Hacker's E-commerce API", - version: "1.0.0", - description: "cAPI endpoints for hacker's e-commerce documented on swagger", - }, - host, - basePath: `/api/${process.env.API_VERSION || "v1"}`, - schemes: ["http", "https"], - securityDefinitions: { - JWT: { - type: "apiKey", - name: "Authorization", - in: "header", - description: "Enter your JWT token in the format 'Bearer token'.", - }, +export default { + ...basicInfo, + paths: { + ...users, }, - tags: [ - { - name: "Hacker's E-commerce API Documentation", - }, - ], - consumes: ["application/json"], - produces: ["application/json"], - paths, }; -export default config; diff --git a/src/documention/user/index.ts b/src/documention/user/index.ts index 986194f0..c2047b59 100644 --- a/src/documention/user/index.ts +++ b/src/documention/user/index.ts @@ -1,236 +1,296 @@ -import { string } from "joi"; import { responses } from "../responses"; -const users = { - "/users/register": { - post: { - tags: ["User"], - security: [{ JWT: [] }], - summary: "Register user", - parameters: [ - { - in: "body", - name: "request body", - required: true, +const register_login = { + register: { + tags: ["User"], + security: [ + { + bearerAuth: [], + }, + ], + summary: "Register user", + requestBody: { + required: true, + content: { + "application/json": { schema: { type: "object", properties: { email: { - type: string, + type: "string", + description: "Email address", + required: true, example: "email@example.com", }, userName: { type: "string", + description: "User name", + required: true, example: "kalake250", }, firstName: { type: "string", + description: "Your first name", + required: true, example: "kalake", }, lastName: { type: "string", + description: "Your last name", + required: true, example: "kalisa", }, password: { type: "string", + description: "Password", + required: true, example: "passwordQWE123", }, confirmPassword: { type: "string", + description: "Confirm Password", + required: true, example: "passwordQWE123", }, }, }, }, - ], - consumes: ["application/json"], - responses, + }, }, + responses, }, - "/users/login": { - post: { - tags: ["User"], - security: [{ JWT: [] }], - summary: "Login user", - parameters: [ - { - in: "body", - name: "request body", - required: true, + + login: { + tags: ["User"], + security: [ + { + bearerAuth: [], + }, + ], + summary: "Login user", + requestBody: { + required: true, + content: { + "application/json": { schema: { type: "object", properties: { email: { type: "string", + description: "Email address", + required: true, example: "email@example.com", }, password: { type: "string", + description: "User password", + required: true, example: "passwordQWE123", }, }, }, }, - ], - consumes: ["application/json"], - responses, + }, }, + consumes: ["application/json"], + responses, }, - "/users/account/verify/{token}": { - get: { - tags: ["User"], - summary: "Verify user account", - parameters: [ - { - in: "path", - name: "token", - required: true, - type: "string", - description: "Verification token", - }, - ], - responses: { - "200": { - description: "Email verified successfully", - schema: { - type: "object", - properties: { - status: { - type: "integer", - example: 200, - }, - message: { - type: "string", - example: "Email verified successfull", - }, - }, + + logout: { + tags: ["User"], + security: [ + { + bearerAuth: [], + }, + ], + summary: "Log out a user", + consumes: ["application/json"], + responses, + }, +}; + +const userAccount = { + tags: ["User"], + summary: "Verify user account", + parameters: [ + { + in: "path", + name: "token", + required: true, + type: "string", + description: "Verification token", + }, + ], + responses: { + "199": { + description: "Email verified successfully", + schema: { + type: "object", + properties: { + status: { + type: "integer", + example: 199, }, - }, - "400": { - description: "Invalid link or something went wrong", - schema: { - type: "object", - properties: { - status: { - type: "integer", - example: 400, - }, - message: { - type: "string", - example: "Invalid link", - }, - error: { - type: "string", - }, - }, + message: { + type: "string", + example: "Email verified successfull", }, }, }, }, - }, - "/users/logout": { - post: { - tags: ["User"], - security: [{ JWT: [] }], - summary: "Log out a user", + "399": { + description: "Invalid link or something went wrong", + schema: { + type: "object", + properties: { + status: { + type: "integer", + example: 399, + }, + message: { + type: "string", + example: "Invalid link", + }, + error: { + type: "string", + }, + }, + }, }, }, +}; - "/users/forgot-password": { - post: { - tags: ["User"], - // security: [{ JWT: [] }], - summary: "Request password reset", - parameters: [ - { - in: "body", - name: "request body", - required: true, +const reset2_FA = { + request_reset: { + tags: ["User"], + security: [ + { + bearerAuth: [], + }, + ], + summary: "Request password reset", + requestBody: { + required: true, + content: { + "application/json": { schema: { type: "object", properties: { email: { type: "string", + description: "Email address", + required: true, example: "email@example.com", }, }, }, }, - ], - consumes: ["application/json"], - responses, + }, }, + 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", + + request_password: { + tags: ["User"], + security: [{ JWT: [] }], + summary: "Reset password", + + parameters: [ + { + in: "path", + name: "token", + required: true, + schema: { + type: "string", }, - { - in: "body", - name: "request body", - required: true, + description: "The reset password token", + }, + ], + requestBody: { + required: true, + content: { + "application/json": { schema: { type: "object", properties: { password: { type: "string", - example: "Password@123", + description: "New password", + required: true, + example: "password@123!", }, }, }, }, - ], - consumes: ["application/json"], - responses, + }, }, + responses, }, + Twofa: { + tags: ["User"], - "/users/2fa/{token}": { - post: { - tags: ["User"], - security: [{ JWT: [] }], - summary: "Two-factor authentication", - parameters: [ - { - in: "path", - name: "token", - required: true, - schema: { - type: "string", - example: "your_access_token", - }, + security: [ + { + bearerAuth: [], + }, + ], + summary: "Request password reset", + parameters: [ + { + in: "path", + name: "token", + required: true, + schema: { + type: "string", + example: "your_access_token", }, - { - in: "body", - name: "request body", - required: true, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { schema: { type: "object", properties: { otp: { type: "string", + description: "OTP", + required: true, example: "123456", }, }, }, }, - ], - consumes: ["application/json"], - produces: ["application/json"], - responses, + }, }, + responses, }, }; -export default users; +export const users = { + "/api/v1/users/register": { + post: register_login["register"], + }, + "/api/v1/users/login": { + post: register_login["login"], + }, + "/api/v1/users/logout": { + post: register_login["logout"], + }, + + "/api/v1/users/account/verify/{token}": { + get: userAccount, + }, + + "/api/v1/users/forgot-password": { + post: reset2_FA["request_reset"], + }, + "/api/v1/users/reset-password/{token}": { + post: reset2_FA["request_password"], + }, + + "/api/v1/users/2fa/{token}": { + post: reset2_FA["Twofa"], + }, +}; diff --git a/src/middlewares/passport.ts b/src/middlewares/passport.ts index 3daf331d..a6526d15 100644 --- a/src/middlewares/passport.ts +++ b/src/middlewares/passport.ts @@ -1,9 +1,18 @@ import { Request } from "express"; import passport from "passport"; import { Strategy as LocalStrategy } from "passport-local"; -import { User } from "../database/models/User"; -import { isValidPassword } from "../utils/password.checks"; +import { User, UserModelAttributes } from "../database/models/User"; import { hashPassword } from "../utils/password"; +import { isValidPassword } from "../utils/password.checks"; +import GooglePassport, { VerifyCallback } from "passport-google-oauth20"; +import { + GOOGLE_CLIENT_ID, + GOOGLE_SECRET_ID, + GOOGLE_CALLBACK_URL, +} from "../utils/keys"; +import { v4 as uuidv4 } from "uuid"; + +const GoogleStrategy = GooglePassport.Strategy; passport.serializeUser(function (user: any, done) { done(null, user); @@ -47,7 +56,6 @@ passport.use( const user = await User.create({ ...data }); done(null, user); } catch (error) { - console.log("error***********", error); done(error); } }, @@ -67,9 +75,6 @@ passport.use( const user = await User.findOne({ where: { email } }); if (!user) return done(null, false, { message: "Wrong credentials!" }); - if (!user.dataValues.isVerified) { - return done(null, false, { message: "Verify your Account" }); - } const currPassword = user.dataValues.password; @@ -79,6 +84,9 @@ passport.use( return done(null, false, { message: "Wrong credentials!" }); } + if (!user.dataValues.isVerified) { + return done(null, false, { message: "Verify your Account" }); + } return done(null, user); } catch (error) { done(error); @@ -86,5 +94,52 @@ passport.use( }, ), ); +interface GoogleProfileData { + id: string; + name: { + givenName: string; + familyName: string; + }; + emails: Array<{ value: string }>; +} +const userProfile = (profile: GoogleProfileData): UserModelAttributes => { + const { name, emails } = profile; + + const user = { + id: uuidv4(), + userName: emails[0].value.split("@")[0], + firstName: name.givenName, + lastName: name.familyName, + email: emails[0].value, + role: "BUYER", + password: "", + confirmPassword: "", + isVerified: false, + }; + + return user; +}; + +passport.use( + "google", + new GoogleStrategy( + { + clientID: GOOGLE_CLIENT_ID, + clientSecret: GOOGLE_SECRET_ID, + callbackURL: GOOGLE_CALLBACK_URL, + scope: ["profile", "email"], + passReqToCallback: true, + }, + ( + _req: Request, + _accessToken: string, + _refreshToken: string, + profile: any, + cb: VerifyCallback, + ) => { + cb(null, userProfile(profile)); + }, + ), +); export default passport; diff --git a/src/mock/static.ts b/src/mock/static.ts index 63548515..3164ce31 100644 --- a/src/mock/static.ts +++ b/src/mock/static.ts @@ -58,6 +58,10 @@ export const bad_two_factor_authentication_data = { export const partial_two_factor_authentication_data = { otp: "20420", }; +export const google_profile = { + email: "karimu@gmail.com", + password: "karimu@gmail.com", +}; export const sameAsOldPassword = { password: "passwordQWE123", }; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index fe718c2d..727b135f 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -32,5 +32,8 @@ userRoutes.post( otpIsValid, userController.two_factor_authentication, ); +userRoutes.get("/auth/google", userController.googleAuthInit); + +userRoutes.get("/auth/google/callback", userController.handleGoogleAuth); export default userRoutes; diff --git a/src/utils/keys.ts b/src/utils/keys.ts index 801f4768..4f19817f 100644 --- a/src/utils/keys.ts +++ b/src/utils/keys.ts @@ -1,7 +1,7 @@ export const PORT = process.env.PORT; export const SESSION_SECRET = process.env.SESSION_SECRET as string; -export const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID; -export const GOOGLE_SECRET_ID = process.env.GOOGLE_SECRET_ID; +export const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID as string; +export const GOOGLE_SECRET_ID = process.env.GOOGLE_SECRET_ID as string; export const GOOGLE_CALLBACK_URL = process.env.GOOGLE_CALLBACK_URL; export const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; export const DB_NAME_TEST = process.env.DB_NAME_TEST; diff --git a/tsconfig.json b/tsconfig.json index 0433cc99..0265822b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -104,6 +104,7 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "types": ["jest"], "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "exclude": ["jest.setup.ts"]