diff --git a/.gitignore b/.gitignore index b2ec942c..9cf55262 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Ignore all node_modules folders node_modules /.vscode/launch.json +dump.rdb diff --git a/backend/package-lock.json b/backend/package-lock.json index 64a53a87..c3aba683 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,26 +11,30 @@ "dependencies": { "@typegoose/typegoose": "^9.8.1", "@types/bcrypt": "^5.0.0", + "axios": "^1.2.1", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "crypto-js": "^4.1.1", "dotenv": "^16.0.0", "express": "^4.17.3", - "express-jwt": "^7.7.0", "jsonwebtoken": "^8.5.1", "mongo-dot-notation": "^3.1.0", "mongoose": "^6.3.2", "nodemon": "^2.0.16", "prettier": "^2.6.2", + "redis": "^4.2.0", "socket.io": "^4.4.1", "uuid": "^8.3.2" }, "devDependencies": { + "@types/cookie-parser": "^1.4.3", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.13", "@types/jsonwebtoken": "^8.5.8", "@types/node": "^17.0.31", + "@types/redis": "^4.0.11", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/parser": "^5.22.0", @@ -178,6 +182,25 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -220,6 +243,25 @@ "node": ">=10" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -255,6 +297,59 @@ "node": ">= 8" } }, + "node_modules/@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz", + "integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==", + "dependencies": { + "cluster-key-slot": "1.1.0", + "generic-pool": "3.8.2", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", + "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -367,6 +462,15 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, + "node_modules/@types/cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -439,6 +543,16 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "node_modules/@types/redis": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz", + "integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==", + "deprecated": "This is a stub types definition. redis provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "redis": "*" + } + }, "node_modules/@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -967,6 +1081,21 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1186,7 +1315,7 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -1365,6 +1494,14 @@ "mimic-response": "^1.0.0" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1389,6 +1526,17 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -1447,6 +1595,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -1535,6 +1703,14 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -2035,23 +2211,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express-jwt": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-7.7.0.tgz", - "integrity": "sha512-4W1N/wD1buyg5F1/IFi4hWMRwerHd1wl17PhfBrNnzMAb1MbAVDJiSoNByVrz2+NGThChIC+vuNaDwVLprkQzQ==", - "dependencies": { - "express-unless": "^1.0.0", - "jsonwebtoken": "^8.5.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/express-unless": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-1.0.0.tgz", - "integrity": "sha512-zXSSClWBPfcSYjg0hcQNompkFN/MxQQ53eyrzm9BYgik2ut2I7PxAf2foVqBRMYCwWaZx/aWodi+uk76npdSAw==" - }, "node_modules/express/node_modules/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", @@ -2203,6 +2362,38 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "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/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2278,6 +2469,14 @@ "node": ">=10" } }, + "node_modules/generic-pool": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", + "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -2860,32 +3059,32 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -2896,7 +3095,7 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/loglevel": { "version": "1.8.0", @@ -3215,44 +3414,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/nodemon": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", @@ -3528,6 +3689,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/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3688,6 +3854,19 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz", + "integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==", + "dependencies": { + "@redis/bloom": "1.0.2", + "@redis/client": "1.2.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.0.6", + "@redis/time-series": "1.0.3" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -4836,6 +5015,14 @@ "tar": "^6.1.11" }, "dependencies": { + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -4859,6 +5046,25 @@ "requires": { "lru-cache": "^6.0.0" } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } } } }, @@ -4888,6 +5094,46 @@ "fastq": "^1.6.0" } }, + "@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", + "requires": {} + }, + "@redis/client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz", + "integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==", + "requires": { + "cluster-key-slot": "1.1.0", + "generic-pool": "3.8.2", + "yallist": "4.0.0" + } + }, + "@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", + "requires": {} + }, + "@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", + "requires": {} + }, + "@redis/search": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", + "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", + "requires": {} + }, + "@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", + "requires": {} + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -4984,6 +5230,15 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, + "@types/cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -5056,6 +5311,15 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "@types/redis": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz", + "integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==", + "dev": true, + "requires": { + "redis": "*" + } + }, "@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -5414,6 +5678,21 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5559,7 +5838,7 @@ "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "buffer-from": { "version": "1.1.2", @@ -5683,6 +5962,11 @@ "mimic-response": "^1.0.0" } }, + "cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5701,6 +5985,14 @@ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5747,6 +6039,22 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -5820,6 +6128,11 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -6233,20 +6546,6 @@ } } }, - "express-jwt": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-7.7.0.tgz", - "integrity": "sha512-4W1N/wD1buyg5F1/IFi4hWMRwerHd1wl17PhfBrNnzMAb1MbAVDJiSoNByVrz2+NGThChIC+vuNaDwVLprkQzQ==", - "requires": { - "express-unless": "^1.0.0", - "jsonwebtoken": "^8.5.1" - } - }, - "express-unless": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-1.0.0.tgz", - "integrity": "sha512-zXSSClWBPfcSYjg0hcQNompkFN/MxQQ53eyrzm9BYgik2ut2I7PxAf2foVqBRMYCwWaZx/aWodi+uk76npdSAw==" - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6345,6 +6644,21 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "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==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6401,6 +6715,11 @@ "wide-align": "^1.1.2" } }, + "generic-pool": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", + "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" + }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -6832,32 +7151,32 @@ "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" }, "lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" }, "lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" }, "lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "lodash.merge": { "version": "4.6.2", @@ -6868,7 +7187,7 @@ "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "loglevel": { "version": "1.8.0", @@ -7100,35 +7419,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, "nodemon": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", @@ -7325,6 +7615,11 @@ "ipaddr.js": "1.9.1" } }, + "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==" + }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -7439,6 +7734,19 @@ "picomatch": "^2.2.1" } }, + "redis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz", + "integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==", + "requires": { + "@redis/bloom": "1.0.2", + "@redis/client": "1.2.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.0.6", + "@redis/time-series": "1.0.3" + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", diff --git a/backend/package.json b/backend/package.json index d05fc599..31fe688f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,18 +2,20 @@ "dependencies": { "@typegoose/typegoose": "^9.8.1", "@types/bcrypt": "^5.0.0", + "axios": "^1.2.1", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "crypto-js": "^4.1.1", "dotenv": "^16.0.0", "express": "^4.17.3", - "express-jwt": "^7.7.0", "jsonwebtoken": "^8.5.1", "mongo-dot-notation": "^3.1.0", "mongoose": "^6.3.2", "nodemon": "^2.0.16", "prettier": "^2.6.2", + "redis": "^4.2.0", "socket.io": "^4.4.1", "uuid": "^8.3.2" }, @@ -28,10 +30,12 @@ "version": "1.0.0", "main": "server.js", "devDependencies": { + "@types/cookie-parser": "^1.4.3", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.13", "@types/jsonwebtoken": "^8.5.8", "@types/node": "^17.0.31", + "@types/redis": "^4.0.11", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/parser": "^5.22.0", diff --git a/backend/src/api/auth.ts b/backend/src/api/auth.ts index 7af086db..8c957f64 100644 --- a/backend/src/api/auth.ts +++ b/backend/src/api/auth.ts @@ -1,21 +1,21 @@ import { Router } from 'express'; -import { sign } from 'jsonwebtoken'; import bcrypt from 'bcrypt'; import dalUser from '../repository/dalUser'; import { addHours, generateHashedSsoPayload, generateSsoPayload, - getJWTSecret, getParamMap, isAuthenticated, isCorrectHashedSsoPayload, isSsoEnabled, isValidNonce, + logoutSCORE, signInUserWithSso, userToToken, } from '../utils/auth'; import { UserModel } from '../models/User'; +import { addToken, destroyToken, sign } from '../utils/jwt'; const router = Router(); @@ -37,9 +37,17 @@ router.post('/login', async (req, res) => { } const user = userToToken(foundUser); - const token = sign(user, getJWTSecret(), { expiresIn: '2h' }); + const token = sign(user); const expiresAt = addHours(2); + await addToken(foundUser.userID, token); + + res.cookie('CK_SESSION', token, { + httpOnly: true, + domain: process.env.APP_DOMAIN || 'localhost', + expires: expiresAt, + secure: true, + }); res.status(200).send({ token, user, expiresAt }); }); @@ -52,12 +60,35 @@ router.post('/register', async (req, res) => { const savedUser = await dalUser.create(body); const user = userToToken(savedUser); - const token = sign(user, getJWTSecret(), { expiresIn: '2h' }); + const token = sign(user); const expiresAt = addHours(2); + await addToken(savedUser.userID, token); + + res.cookie('CK_SESSION', token, { + httpOnly: true, + domain: process.env.APP_DOMAIN || 'localhost', + expires: expiresAt, + secure: true, + }); res.status(200).send({ token, user, expiresAt }); }); +router.post('/logout', isAuthenticated, async (req, res) => { + if (!req.headers.authorization) { + return res.status(400).end('No authorization header found!'); + } + + const token = req.headers.authorization.replace('Bearer ', ''); + await destroyToken(res.locals.user.userID, token); + + if (req.query.score) { + await logoutSCORE(req); + } + + res.status(200).end(); +}); + router.post('/multiple', async (req, res) => { const ids = req.body; const users = await dalUser.findByUserIDs(ids); @@ -71,12 +102,14 @@ router.get('/is-sso-enabled', async (req, res) => { }); router.get('/sso/handshake', async (req, res) => { - const scoreSsoEndpoint = process.env.SCORE_SSO_ENDPOINT; - const payload = await generateSsoPayload(); - const hashedPayload = generateHashedSsoPayload(payload); - if (!scoreSsoEndpoint) { + const ssoEndpoint = process.env.SCORE_SSO_ENDPOINT; + const scoreAddress = process.env.SCORE_SERVER_ADDRESS || 'http://localhost'; + if (!ssoEndpoint) { throw new Error('No SCORE SSO endpoint environment variable defined!'); } + const scoreSsoEndpoint = `${scoreAddress + ssoEndpoint}`; + const payload = await generateSsoPayload(); + const hashedPayload = generateHashedSsoPayload(payload); res.status(200).send({ scoreSsoEndpoint: scoreSsoEndpoint, sig: hashedPayload, diff --git a/backend/src/api/projects.ts b/backend/src/api/projects.ts index 5a3b55c8..50a69a6d 100644 --- a/backend/src/api/projects.ts +++ b/backend/src/api/projects.ts @@ -2,7 +2,7 @@ import { Router } from 'express'; import { mongo } from 'mongoose'; import { BoardScope } from '../models/Board'; import { ProjectModel } from '../models/Project'; -import { UserModel } from '../models/User'; +import { Role, UserModel } from '../models/User'; import dalBoard from '../repository/dalBoard'; import dalProject from '../repository/dalProject'; import { @@ -12,6 +12,14 @@ import { import { BoardType } from '../models/Board'; import { ApplicationError } from '../errors/base.errors'; import { addUserToProject } from '../utils/project.helpers'; +import { + createUserIfNecessary, + getOrCreateUser, + getParamMap, + getRole, +} from '../utils/auth'; +import dalUser from '../repository/dalUser'; +import { SCORE_DOMAIN } from '../constants'; const router = Router(); @@ -78,6 +86,74 @@ router.post('/:id', async (req, res) => { res.status(200).json(updatedProject); }); +router.post('/score/link', async (req, res) => { + const { runId, code } = req.body; + const result: any = { + message: 'Code does not exist or has already been used', + }; + const linkedRunId = Number(runId); + + let project = await dalProject.getByConnectCode(code); + if (project) { + if (!isNaN(linkedRunId) && project.linkedRunId == 0) { + project = await dalProject.update(project.projectID, { linkedRunId }); + result.code = project?.scoreJoinCode; + result.message = + 'Successfully linked Run to an available CK Board project'; + } + } + + res.status(200).json(result); +}); + +router.post('/score/unlink', async (req, res) => { + const { runId, code } = req.body; + const result: any = { + message: 'Could not unlink due to invalid code or run id', + }; + const linkedRunId = Number(runId); + + let project = await dalProject.getByConnectCode(code); + if (project) { + if (!isNaN(linkedRunId) && project.linkedRunId == linkedRunId) { + project = await dalProject.update(project.projectID, { linkedRunId: 0 }); + result.code = project?.scoreJoinCode; + result.message = 'Successfully unlinked Run from CK Board project'; + } + } + + res.status(200).json(result); +}); + +router.post('/score/addMember', async (req, res) => { + const { code, username, role } = req.body; + let project = await dalProject.getByConnectCode(code); + if (project && !project.membershipDisabled) { + const email = `${username}@${SCORE_DOMAIN}`; + const user = await createUserIfNecessary(email, username, getRole(role)); + if (user?.role == Role.STUDENT) { + dalProject.addStudent(project.studentJoinCode, user.userID); + } else if (user?.role == Role.TEACHER) { + dalProject.addTeacher(project.teacherJoinCode, user.userID); + } + } + res.status(200).send(); +}); + +router.post('/score/removeMember', async (req, res) => { + const { username, code } = req.body; + let project = await dalProject.getByConnectCode(code); + if (project) { + const user = await dalUser.findByUsername(username); + if (user?.role == Role.STUDENT) { + dalProject.removeStudent(project.studentJoinCode, user.userID); + } else if (user?.role == Role.TEACHER) { + dalProject.removeTeacher(project.teacherJoinCode, user.userID); + } + } + res.status(200).send(); +}); + router.get('/:id', async (req, res) => { const id = req.params.id; diff --git a/backend/src/constants.ts b/backend/src/constants.ts index 0abaeda4..0ee4badd 100644 --- a/backend/src/constants.ts +++ b/backend/src/constants.ts @@ -79,3 +79,5 @@ export const DEFAULT_TAGS: Partial[] = [ QUESTION_TAG, NEEDS_ATTENTION_TAG, ]; + +export const SCORE_DOMAIN = 'score.oise.utoronto.ca'; diff --git a/backend/src/models/Project.ts b/backend/src/models/Project.ts index 2e7b53ac..b8ae3605 100644 --- a/backend/src/models/Project.ts +++ b/backend/src/models/Project.ts @@ -35,6 +35,12 @@ export class ProjectModel { @prop({ required: true }) public teacherJoinCode!: string; + @prop({ required: false }) + public scoreJoinCode!: string; + + @prop({ required: false }) + public linkedRunId!: number; + @prop({ required: true, type: () => PersonalBoardSetting }) public personalBoardSetting!: PersonalBoardSetting; diff --git a/backend/src/repository/dalProject.ts b/backend/src/repository/dalProject.ts index f5454758..f04ec852 100644 --- a/backend/src/repository/dalProject.ts +++ b/backend/src/repository/dalProject.ts @@ -34,14 +34,37 @@ export const getByJoinCode = async (code: string, role: Role) => { } }; +export const getByConnectCode = async (code: string) => { + try { + return await Project.findOne({ scoreJoinCode: code }); + } catch (err) { + throw new Error(JSON.stringify(err, null, ' ')); + } +}; + export const addStudent = async (code: string, userID: string) => { const project = await Project.findOne({ studentJoinCode: code }); if (!project) { throw new UnauthorizedError('Invalid Join Code!'); } - await project.updateOne({ $push: { members: userID } }); + if (!project.members.includes(userID)) { + await project.updateOne({ $push: { members: userID } }); + } + + return project; +}; + +export const removeStudent = async (code: string, userID: string) => { + const project = await Project.findOne({ studentJoinCode: code }); + if (!project) { + throw new UnauthorizedError('Invalid Join Code!'); + } + if (project.members.includes(userID)) { + await project.updateOne({ $pull: { members: userID } }); + } return project; + ``; }; export const addTeacher = async (code: string, userID: string) => { @@ -49,7 +72,21 @@ export const addTeacher = async (code: string, userID: string) => { if (!project) { throw new UnauthorizedError('Invalid Join Code!'); } - await project.updateOne({ $push: { teacherIDs: userID, members: userID } }); + if (!project.members.includes(userID)) { + await project.updateOne({ $push: { teacherIDs: userID, members: userID } }); + } + + return project; +}; + +export const removeTeacher = async (code: string, userID: string) => { + const project = await Project.findOne({ teacherJoinCode: code }); + if (!project) { + throw new UnauthorizedError('Invalid Join Code!'); + } + if (project.members.includes(userID)) { + await project.updateOne({ $pull: { teacherIDs: userID, members: userID } }); + } return project; }; @@ -112,9 +149,12 @@ const dalProject = { getById, getByUserId, getByJoinCode, + getByConnectCode, create, addStudent, + removeStudent, addTeacher, + removeTeacher, update, removeBoard, remove, diff --git a/backend/src/server.ts b/backend/src/server.ts index 2744b6a7..8d41464f 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,6 +1,7 @@ import express from 'express'; import http from 'http'; import bodyParser from 'body-parser'; +import cookieParser from 'cookie-parser'; import cors from 'cors'; import mongoose from 'mongoose'; import dotenv from 'dotenv'; @@ -18,9 +19,12 @@ import trace from './api/trace'; import groups from './api/groups'; import todoItems from './api/todoItem'; import { isAuthenticated } from './utils/auth'; +import redis from './utils/redis'; dotenv.config(); const port = process.env.PORT || 8001; +const ckAddr = process.env.CKBOARD_SERVER_ADDRESS || 'http://localhost:4201'; +const scoreAddr = process.env.SCORE_SERVER_ADDRESS || 'http://localhost'; const dbUsername = process.env.DB_USER; const dbPassword = process.env.DB_PASSWORD; const dbUrl = process.env.DB_URL; @@ -28,10 +32,30 @@ const dbName = process.env.DB_NAME; const dbURI = `mongodb+srv://${dbUsername}:${dbPassword}@${dbUrl}.mongodb.net/${dbName}?retryWrites=true&w=majority`; const app = express(); -app.use(cors()); +app.use(cookieParser()); +app.use( + cors({ + credentials: true, + origin: (origin, callback) => { + if (!origin) return callback(null, true); + + if (origin != ckAddr && origin != scoreAddr) { + const msg = `This site ${origin} does not have an access. Only specific domains are allowed to access it.`; + return callback(new Error(msg), false); + } + + return callback(null, true); + }, + }) +); app.use(bodyParser.json()); const server = http.createServer(app); +(async () => { + await redis.connect(); + return redis; +})(); + const socket = Socket.Instance; socket.init(); diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 53c4756c..ff01b8f3 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, Response } from 'express'; -import { sign, verify } from 'jsonwebtoken'; +import axios from 'axios'; import { Role, UserModel } from '../models/User'; import { v4 as uuidv4 } from 'uuid'; import hmacSHA256 from 'crypto-js/hmac-sha256'; @@ -11,6 +11,8 @@ import { ProjectModel } from '../models/Project'; import { NotFoundError } from '../errors/client.errors'; import { addUserToProject } from './project.helpers'; import { ApplicationError } from '../errors/base.errors'; +import { addToken, checkToken, sign, verify } from './jwt'; +import { SCORE_DOMAIN } from '../constants'; export interface Token { email: string; @@ -19,11 +21,6 @@ export interface Token { role: string; } -export const addHours = (numOfHours: number, date = new Date()) => { - date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000); - return date; -}; - export const getJWTSecret = (): string => { const secret = process.env.JWT_SECRET; @@ -34,6 +31,11 @@ export const getJWTSecret = (): string => { return secret; }; +export const addHours = (numOfHours: number, date = new Date()) => { + date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000); + return date; +}; + export const userToToken = (user: UserModel): Token => { return { email: user.email, @@ -54,11 +56,15 @@ export const isAuthenticated = async ( } const token = req.headers.authorization.replace('Bearer ', ''); - res.locals.user = verify(token, getJWTSecret()) as Token; + res.locals.user = verify(token); + + const cachedToken = await checkToken(res.locals.user.userID, token); + if (cachedToken == null || cachedToken == 'invalid' || cachedToken == 'nil') + return res.status(401).end('Invalid token!'); next(); } catch (e) { - return res.status(403).end('Unable to authenticate!'); + return res.status(401).end('Unable to authenticate!'); } }; @@ -121,7 +127,7 @@ export const getOrCreateUser = async ( paramMap: Map ): Promise => { const username = paramMap.get('username') ?? ''; - const email = `${username}@score.oise.utoronto.ca`; + const email = `${username}@${SCORE_DOMAIN}`; const role = getRole(paramMap.get('role')); try { return await createUserIfNecessary(email, username, role); @@ -218,14 +224,32 @@ export const signInUserWithSso = async ( return res.status(500).end('Internal Server Error'); } } - const sessionToken = generateSessionToken(userModel); + const sessionToken = await generateSessionToken(userModel); sessionToken.redirectUrl = redirectUrl; + await addToken(sessionToken.user.userID, sessionToken.token); + + res.cookie('CK_SESSION', sessionToken.token, { + httpOnly: true, + domain: process.env.APP_DOMAIN || 'localhost', + expires: sessionToken.expiresAt, + secure: true, + }); return res.status(200).send(sessionToken); }; export const generateSessionToken = (userModel: UserModel): any => { const user = userToToken(userModel); - const token = sign(user, getJWTSecret(), { expiresIn: '2h' }); + const token = sign(user); const expiresAt = addHours(2); return { token, user, expiresAt }; }; + +export const logoutSCORE = async (req: Request) => { + const scoreAddress = process.env.SCORE_SERVER_ADDRESS || 'http://localhost'; + return await axios.get( + `${scoreAddress + process.env.SCORE_LOGOUT_ENDPOINT}`, + { + headers: { Cookie: `SESSION=${req.cookies['SESSION']};` }, + } + ); +}; diff --git a/backend/src/utils/jwt.ts b/backend/src/utils/jwt.ts new file mode 100644 index 00000000..1665d3d2 --- /dev/null +++ b/backend/src/utils/jwt.ts @@ -0,0 +1,39 @@ +import redis from './redis'; +import jwt from 'jsonwebtoken'; +import { Token } from './auth'; + +export const sign = (payload: Token, date = new Date()) => { + return jwt.sign(payload, 'secret', { + expiresIn: date.setTime(date.getTime() + 2 * 60 * 60 * 1000), + }); +}; + +export const verify = (token: string): Token => { + return jwt.verify(token, 'secret') as Token; +}; + +export const addToken = async ( + id: string, + token: string, + date = new Date() +) => { + const key = `${id}_${token}`; + const check = await redis.EXISTS(key); // check if key exists in cache + if (check == 1) return; + + await redis.SET(key, 'valid'); // set key value to be 'valid' + await redis.EXPIREAT(key, date.setTime(date.getTime() + 2 * 60 * 60 * 1000)); // set expiry date for the key in the cache + return; +}; + +export const checkToken = async (id: string, token: string) => { + const key = `${id}_${token}`; + const status = redis.GET(key); // get the token from the cache and return its value + return status; +}; + +export const destroyToken = async (id: string, token: string) => { + const key = `${id}_${token}`; + await redis.DEL(key); // deletes token from cache + return; +}; diff --git a/backend/src/utils/redis.ts b/backend/src/utils/redis.ts new file mode 100644 index 00000000..045809ce --- /dev/null +++ b/backend/src/utils/redis.ts @@ -0,0 +1,35 @@ +import { createClient } from 'redis'; + +class Redis { + client: any; + connected: boolean; + + constructor() { + this.client = null; + this.connected = false; + } + + getConnection() { + if (this.connected) return this.client; + + this.client = createClient(); + console.log(this.client); + + this.client.on('connect', () => { + console.log('Client connected to Redis...'); + }); + this.client.on('ready', () => { + console.log('Redis ready to use'); + }); + this.client.on('error', (err: string) => { + console.error('Redis Client', err); + }); + this.client.on('end', () => { + console.log('Redis disconnected successfully'); + }); + + return this.client; + } +} + +export default new Redis().getConnection(); diff --git a/frontend/src/app/components/add-project-modal/add-project-modal.component.html b/frontend/src/app/components/add-project-modal/add-project-modal.component.html index a6a4b943..34dcef06 100644 --- a/frontend/src/app/components/add-project-modal/add-project-modal.component.html +++ b/frontend/src/app/components/add-project-modal/add-project-modal.component.html @@ -45,6 +45,14 @@
Membership Settings:
+ +
+
SCORE Linking
+ + Connect to a SCORE Run (i.e., to add SCORE members and activities) + +
+
diff --git a/frontend/src/app/components/add-project-modal/add-project-modal.component.ts b/frontend/src/app/components/add-project-modal/add-project-modal.component.ts index e1e6785b..088ecea8 100644 --- a/frontend/src/app/components/add-project-modal/add-project-modal.component.ts +++ b/frontend/src/app/components/add-project-modal/add-project-modal.component.ts @@ -4,7 +4,7 @@ import { fabric } from 'fabric'; import { PersonalBoardSetting } from 'src/app/models/project'; import { FileUploadService } from 'src/app/services/fileUpload.service'; import { UserService } from 'src/app/services/user.service'; -import { FabricUtils, ImageSettings } from 'src/app/utils/FabricUtils'; +import { FabricUtils } from 'src/app/utils/FabricUtils'; import { generateCode, generateUniqueID } from 'src/app/utils/Utils'; @Component({ @@ -20,6 +20,7 @@ export class AddProjectModalComponent implements OnInit { bgImage: null, }; membershipDisabledEditable = false; + linkToScore = false; constructor( public dialogRef: MatDialogRef, @@ -54,6 +55,10 @@ export class AddProjectModalComponent implements OnInit { boards: [], studentJoinCode: generateCode(5).toString(), teacherJoinCode: generateCode(5).toString(), + ...(this.linkToScore && { + scoreJoinCode: generateCode(5).toString(), + linkedRunId: 0, + }), personalBoardSetting: this.personalBoardSetting, membershipDisabled: this.membershipDisabledEditable, }); diff --git a/frontend/src/app/components/dashboard/dashboard.component.html b/frontend/src/app/components/dashboard/dashboard.component.html index 7dbf8153..ba707469 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.html +++ b/frontend/src/app/components/dashboard/dashboard.component.html @@ -5,7 +5,7 @@ diff --git a/frontend/src/app/components/login/login.component.ts b/frontend/src/app/components/login/login.component.ts index 90a4e738..40401f98 100644 --- a/frontend/src/app/components/login/login.component.ts +++ b/frontend/src/app/components/login/login.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { UserService } from 'src/app/services/user.service'; import { MyErrorStateMatcher } from 'src/app/utils/ErrorStateMatcher'; @@ -8,7 +8,7 @@ import { MyErrorStateMatcher } from 'src/app/utils/ErrorStateMatcher'; templateUrl: './login.component.html', styleUrls: ['./login.component.scss'], }) -export class LoginComponent { +export class LoginComponent implements OnInit { email: string; password: string; @@ -16,13 +16,13 @@ export class LoginComponent { invalidCredentials = false; - constructor(private userService: UserService, private router: Router) { - if (this.userService.loggedIn) { - this.router.navigate(['/dashboard']); - } + constructor(private userService: UserService, private router: Router) {} + + ngOnInit(): void { + if (this.userService.loggedIn) this.router.navigate(['/dashboard']); } - onLogin() { + onLogin(): void { this.userService .login(this.email, this.password) .then(async () => { diff --git a/frontend/src/app/components/project-configuration-modal/project-configuration-modal.component.html b/frontend/src/app/components/project-configuration-modal/project-configuration-modal.component.html index 48a20357..c5fe0025 100644 --- a/frontend/src/app/components/project-configuration-modal/project-configuration-modal.component.html +++ b/frontend/src/app/components/project-configuration-modal/project-configuration-modal.component.html @@ -17,17 +17,24 @@

Project Configuration

matTooltip="When activated, no users can join this project."> Restrict users from joining this project -
Invite Codes
+
{{ !!project.scoreJoinCode ? 'SCORE-CK Connect Code' : 'Invite Codes' }}
- + + + -
Current Members ({{members.length}})
diff --git a/frontend/src/app/components/toolbar/toolbar.component.ts b/frontend/src/app/components/toolbar/toolbar.component.ts index bd79a709..1b926d68 100644 --- a/frontend/src/app/components/toolbar/toolbar.component.ts +++ b/frontend/src/app/components/toolbar/toolbar.component.ts @@ -22,8 +22,8 @@ export class ToolbarComponent implements OnInit { ngOnInit(): void {} - signOut(): void { - this.userService.logout(); + async signOut(): Promise { + await this.userService.logout(); this.router.navigate(['login']); } } diff --git a/frontend/src/app/models/project.ts b/frontend/src/app/models/project.ts index 7cf371fd..d8bc723a 100644 --- a/frontend/src/app/models/project.ts +++ b/frontend/src/app/models/project.ts @@ -13,6 +13,8 @@ export class Project { members: string[]; studentJoinCode: string; teacherJoinCode: string; + scoreJoinCode: string; + linkedRunId: number; personalBoardSetting: PersonalBoardSetting; membershipDisabled: boolean; } diff --git a/frontend/src/app/services/user.service.ts b/frontend/src/app/services/user.service.ts index 873bcd86..f2efc1cd 100644 --- a/frontend/src/app/services/user.service.ts +++ b/frontend/src/app/services/user.service.ts @@ -20,7 +20,7 @@ export class UserService { async register(user: User) { return this.http - .post('auth/register', user) + .post('auth/register', user, { withCredentials: true }) .toPromise() .then((result) => { localStorage.setItem('user', JSON.stringify(result.user)); @@ -32,10 +32,14 @@ export class UserService { async login(email: string, password: string): Promise { return this.http - .post('auth/login', { - email: email, - password: password, - }) + .post( + 'auth/login', + { + email: email, + password: password, + }, + { withCredentials: true } + ) .toPromise() .then((result) => { localStorage.setItem('user', JSON.stringify(result.user)); @@ -45,6 +49,20 @@ export class UserService { }); } + async logout(): Promise { + return this.http + .post( + 'auth/logout?score=true', + {}, + { withCredentials: true } + ) + .toPromise() + .then(() => { + this.clearLocalStorage(); + return true; + }); + } + async isSsoEnabled(): Promise { return this.http .get('auth/is-sso-enabled') @@ -66,7 +84,7 @@ export class UserService { async ssoLogin(sso: string | null, sig: string | null): Promise { return this.http - .get(`auth/sso/login/${sso}/${sig}`) + .get(`auth/sso/login/${sso}/${sig}`, { withCredentials: true }) .toPromise() .then((result: any) => { localStorage.setItem('user', JSON.stringify(result.user)); @@ -77,12 +95,6 @@ export class UserService { }); } - logout() { - localStorage.removeItem('user'); - localStorage.removeItem('access_token'); - localStorage.removeItem('expires_at'); - } - update(id: string, user: Partial) { return this.http.post('auth/' + id, user).toPromise(); } @@ -91,6 +103,12 @@ export class UserService { return this.http.delete('auth/' + id).toPromise(); } + clearLocalStorage(): void { + localStorage.removeItem('user'); + localStorage.removeItem('access_token'); + localStorage.removeItem('expires_at'); + } + public get loggedIn(): boolean { const token = localStorage.getItem('access_token'); const expiry = localStorage.getItem('expires_at'); diff --git a/frontend/src/app/utils/interceptor.ts b/frontend/src/app/utils/interceptor.ts index 7f48195e..faeaf3f5 100644 --- a/frontend/src/app/utils/interceptor.ts +++ b/frontend/src/app/utils/interceptor.ts @@ -4,16 +4,19 @@ import { HttpInterceptor, HttpHandler, HttpRequest, + HttpErrorResponse, + HttpResponse, } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { timeout } from 'rxjs/operators'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { UserService } from '../services/user.service'; +import { Router } from '@angular/router'; export const DEFAULT_TIMEOUT = 30000; @Injectable() export class APIInterceptor implements HttpInterceptor { - constructor(public auth: UserService) {} + constructor(public auth: UserService, private router: Router) {} intercept( req: HttpRequest, @@ -27,6 +30,14 @@ export class APIInterceptor implements HttpInterceptor { Authorization: `Bearer ${this.auth.token}`, }, }); - return next.handle(apiReq).pipe(timeout(timeoutValue)); + return next.handle(apiReq).pipe( + catchError(async (err) => { + if (err instanceof HttpErrorResponse && err.status == 401) { + this.auth.clearLocalStorage(); + this.router.navigate(['login']); + } + return err; + }) + ); } }