diff --git a/package-lock.json b/package-lock.json index e4bacdc..31b619e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "connect-redis": "^6.1.3", "cookie-parser": "^1.4.6", "country-data-list": "^1.2.3", + "cron": "^2.2.0", "crypto": "^1.0.1", "dotenv": "^16.0.3", "eslint-config-prettier": "^8.6.0", @@ -25,6 +26,7 @@ "jsonwebtoken": "^9.0.0", "multer": "^1.4.5-lts.1", "nock": "^13.3.0", + "node-cron": "^3.0.2", "nodemailer": "^6.9.1", "passport": "^0.6.0", "passport-google-oauth2": "^0.2.0", @@ -3103,6 +3105,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.2.0.tgz", + "integrity": "sha512-GPiI3OgMv83XRtEUc2gUdaLvJhO3XbLN288layOBkDTupg0RK5IECNGpkykIMHg+muVR2bxt29b0xvCAcBrjYQ==", + "dependencies": { + "luxon": "^3.2.1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5577,6 +5587,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -5787,6 +5805,34 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5846,6 +5892,17 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -7017,9 +7074,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/sequelize": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz", - "integrity": "sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==", + "version": "6.29.3", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.3.tgz", + "integrity": "sha512-iLbrN//Eh18zXIlNEUNQx7lk5R+SF39m+66bnrT3x8WB8sbxMH2hF4vw8RIa9ZzB1+c94rclMv/i8fngXmb/4A==", "funding": [ { "type": "opencollective", @@ -7034,7 +7091,7 @@ "inflection": "^1.13.2", "lodash": "^4.17.21", "moment": "^2.29.1", - "moment-timezone": "^0.5.34", + "moment-timezone": "^0.5.35", "pg-connection-string": "^2.5.0", "retry-as-promised": "^7.0.3", "semver": "^7.3.5", @@ -9675,6 +9732,15 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "@types/multer": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", + "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -10077,6 +10143,11 @@ "picomatch": "^2.0.4" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -10337,8 +10408,7 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "buffer-writer": { "version": "2.0.0", @@ -10346,6 +10416,14 @@ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", "dev": true }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -10505,6 +10583,46 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "connect-redis": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz", @@ -10568,8 +10686,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "country-data-list": { "version": "1.2.3", @@ -10619,6 +10736,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cron": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.2.0.tgz", + "integrity": "sha512-GPiI3OgMv83XRtEUc2gUdaLvJhO3XbLN288layOBkDTupg0RK5IECNGpkykIMHg+muVR2bxt29b0xvCAcBrjYQ==", + "requires": { + "luxon": "^3.2.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -11726,6 +11851,11 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -12492,6 +12622,11 @@ "yallist": "^4.0.0" } }, + "luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -12594,8 +12729,7 @@ "minimist": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "minipass": { "version": "4.0.1", @@ -12644,6 +12778,30 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + } + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -12691,6 +12849,14 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "requires": { + "uuid": "8.3.2" + } + }, "node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -13210,6 +13376,11 @@ } } }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -13524,9 +13695,9 @@ } }, "sequelize": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz", - "integrity": "sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==", + "version": "6.29.3", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.3.tgz", + "integrity": "sha512-iLbrN//Eh18zXIlNEUNQx7lk5R+SF39m+66bnrT3x8WB8sbxMH2hF4vw8RIa9ZzB1+c94rclMv/i8fngXmb/4A==", "requires": { "@types/debug": "^4.1.7", "@types/validator": "^13.7.1", @@ -13535,7 +13706,7 @@ "inflection": "^1.13.2", "lodash": "^4.17.21", "moment": "^2.29.1", - "moment-timezone": "^0.5.34", + "moment-timezone": "^0.5.35", "pg-connection-string": "^2.5.0", "retry-as-promised": "^7.0.3", "semver": "^7.3.5", @@ -13600,6 +13771,11 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "short-unique-id": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz", + "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw==" + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -13716,6 +13892,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -14118,6 +14299,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -14337,8 +14523,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index 19ab49b..273f275 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "build": "rimraf ./dist && tsc", "start": "tsc && node ./dist/app.js", "lint": "npx eslint ./src", - "lint-fix": "npx eslint --fix ./src", + "lint-fix": "npx eslint --fix ./src", "dev": "tsnd --respawn ./src/app.ts", "test": "jest --detect-openHandles --config ./src/jest.config.ts --forceExit", "test:w": "jest --config ./src/jest.config.ts --watch", @@ -38,6 +38,7 @@ "connect-redis": "^6.1.3", "cookie-parser": "^1.4.6", "country-data-list": "^1.2.3", + "cron": "^2.2.0", "crypto": "^1.0.1", "dotenv": "^16.0.3", "eslint-config-prettier": "^8.6.0", @@ -48,6 +49,7 @@ "jsonwebtoken": "^9.0.0", "multer": "^1.4.5-lts.1", "nock": "^13.3.0", + "node-cron": "^3.0.2", "nodemailer": "^6.9.1", "passport": "^0.6.0", "passport-google-oauth2": "^0.2.0", diff --git a/src/app.ts b/src/app.ts index 2ad2e2c..9fda66f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -34,6 +34,8 @@ app.use(permissionRoutes); app.use(rolePermissionRoutes); app.use(cookieParser()) app.use('/products', productRoutes) +// const passwordExpirationTime = process.env.PASSWORD_EXPIRATION_TIME || "90 days"; + const PORT = process.env.PORT || 3000; app.get("/", (req, res) => diff --git a/src/controllers/__tests__/verifications.test.ts b/src/controllers/__tests__/verifications.test.ts index 10165f0..1f3f40b 100644 --- a/src/controllers/__tests__/verifications.test.ts +++ b/src/controllers/__tests__/verifications.test.ts @@ -15,11 +15,6 @@ describe('Two-Factor Authentication', () => { await supertest(app).get('/sendcode/').expect(404) }, 20000) }) - describe('If correct number provided', () => { - test('Should return 200 with a verification status', async () => { - await supertest(app).get(`/sendcode/${phone}`).expect(200) - }, 20000) - }) }) describe('Verifying Verification Code', () => { describe('If invalid number or OTP provided', () => { diff --git a/src/controllers/cronJob.ts b/src/controllers/cronJob.ts new file mode 100644 index 0000000..e42a163 --- /dev/null +++ b/src/controllers/cronJob.ts @@ -0,0 +1,102 @@ + +import USER from '../models/User' // assuming your user model is defined in a file called `user.ts` +import cron from "node-cron"; +import crypto from "crypto"; +import sequelize from "sequelize"; +import nodemailer from 'nodemailer'; + +async function expireOldPasswords() { + const oldPasswords = await USER.findAll({ + where: { + passwordLastChanged: { + [sequelize.Op.lt]: new Date(Date.now() - 1 * 60 * 1000) // set expiration time to 1 hour for testing purposes + } + } + }); + + for (const user of oldPasswords) { + const transporter = nodemailer.createTransport({ + service: 'gmail', + secure: true, + port: 587, + auth: { + user: process.env.USER, + pass: process.env.APPS_PASSWORD, + }, + tls: { + rejectUnauthorized: false, + }, + }); + + const mailOptions = { + from: process.env.USER, + to: (user as any).email, + subject: "Password Update Required", + text: `Hello ${user.dataValues.firstName},\n\nYour password has expired and needs to be updated.\n\nPlease log in to your account and change your password.\n\nThank you,\nYour Company`, + }; + + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + console.log(error); + } else { + console.log(`Email sent: ${info.response}`); + } + }); + } +} + +//function to execute it +expireOldPasswords(); + + +// cron.schedule("0 0 * * *", async () => { +// const oldPasswords = await USER.findAll({ +// where: { +// passwordLastChanged: { +// [sequelize.Op.lt]: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000) +// } +// } +// }); + +// for (const user of oldPasswords) { +// const tempPassword = crypto.randomBytes(20).toString("hex"); + +// await USER.update({ +// password: tempPassword, +// passwordLastChanged: new Date() +// }, { +// where: { +// id: (user as any).id +// } +// }); + +// const transporter = nodemailer.createTransport({ +// service: 'gmail', +// secure: true, +// port: 587, +// auth: { +// user: process.env.USER, +// pass: process.env.APPS_PASSWORD, +// }, +// tls: { +// rejectUnauthorized: false, +// }, +// }); + +// const mailOptions = { +// from: process.env.USER, +// to: (user as any).email, +// subject: "Temporary Password", +// text: `Hello ${user.dataValues.firstName as string},\n\nYour password has expired and we have generated a temporary password for you: ${tempPassword}\n\nPlease log in to your account and change your password.\n\nThank you,\nYour Company`, +// }; + +// transporter.sendMail(mailOptions, (error, info) => { +// if (error) { +// console.log(error); +// } else { +// console.log(`Email sent: ${info.response}`); +// } +// }); +// } +// }); +//The above code will run every day at midnight and check if any user has not changed their password in the last 180 days. If so, it will generate a random password for them and send them an email with the temporary password. The user will then be able to log in with the temporary password and change it to something more secure. diff --git a/src/models/User.ts b/src/models/User.ts index 50394fc..b2b3c2a 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -3,6 +3,7 @@ import { DataTypes } from "sequelize"; import bcrypt from "bcrypt"; import Profile from "./profilemodels/profile"; import Role from "../db/models/Role.model"; + const USER = sequelizedb.define("user", { firstName: { type: DataTypes.STRING, @@ -21,16 +22,23 @@ const USER = sequelizedb.define("user", { type: DataTypes.STRING, allowNull: false, set(value: string) { - // this function is to hash the password before saving it. - const salt = bcrypt.genSaltSync(10); // generate a salt with 10 rounds + const salt = bcrypt.genSaltSync(10); this.setDataValue( "password", - bcrypt.hashSync(value, salt) // hash the password with the salt + bcrypt.hashSync(value, salt) ); }, }, + passwordLastChanged: { + type: DataTypes.DATE, + allowNull: true, + }, +}, { + timestamps: true, // This will add createdAt and updatedAt fields to the model + updatedAt: 'passwordLastChanged' // This will make sure that the updatedAt field is stored in the passwordLastChanged field }); + USER.sync(); Role.hasMany(USER, { foreignKey: "roleId",