From 93f0804fc2e431a95b4d2131e0f300d612d042be Mon Sep 17 00:00:00 2001 From: Rui Silva Date: Tue, 21 Feb 2023 16:25:42 +0000 Subject: [PATCH] feat(auth-azure): upload and set user photo on login --- backend/package-lock.json | 639 ++++++++++++++++-- backend/package.json | 5 +- .../infrastructure/config/config.module.ts | 3 +- .../infrastructure/config/configuration.ts | 6 +- backend/src/libs/constants/azure.ts | 2 + backend/src/libs/constants/storage.ts | 1 + backend/src/modules/auth/auth.providers.ts | 6 - backend/src/modules/auth/shared/login.auth.ts | 5 +- backend/src/modules/azure/azure.module.ts | 7 +- backend/src/modules/azure/azure.providers.ts | 6 - .../services/cron.azure.service.interface.ts | 3 - .../azure/services/auth.azure.service.ts | 154 +++-- .../azure/services/cron.azure.service.ts | 34 - .../interfaces/services/storage.service.ts | 10 + .../src/modules/storage/interfaces/types.ts | 11 + .../storage/services/storage.service.ts | 54 ++ backend/src/modules/storage/storage.module.ts | 8 + .../src/modules/storage/storage.providers.ts | 7 + backend/src/modules/users/dto/user.dto.ts | 4 + .../src/modules/users/entities/user.schema.ts | 3 + .../services/update.user.service.interface.ts | 2 + .../repository/user.repository.interface.ts | 1 + .../users/repository/user.repository.ts | 8 + .../users/services/update.user.service.ts | 19 +- docker-compose.yaml | 31 + frontend/package-lock.json | 4 +- frontend/src/pages/api/auth/[...nextauth].tsx | 3 + frontend/src/types/next-auth.d.ts | 3 + frontend/src/types/user/create-login.user.ts | 1 + package-lock.json | 4 +- 30 files changed, 871 insertions(+), 173 deletions(-) create mode 100644 backend/src/libs/constants/storage.ts delete mode 100644 backend/src/modules/azure/interfaces/services/cron.azure.service.interface.ts delete mode 100644 backend/src/modules/azure/services/cron.azure.service.ts create mode 100644 backend/src/modules/storage/interfaces/services/storage.service.ts create mode 100644 backend/src/modules/storage/interfaces/types.ts create mode 100644 backend/src/modules/storage/services/storage.service.ts create mode 100644 backend/src/modules/storage/storage.module.ts create mode 100644 backend/src/modules/storage/storage.providers.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 1342f94c6..a7ae23fa4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,15 +1,18 @@ { "name": "backend", - "version": "v0.1.12", + "version": "v0.1.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "backend", - "version": "v0.1.12", + "version": "v0.1.14", "license": "UNLICENSED", "dependencies": { + "@azure/msal-node": "^1.15.0", + "@azure/storage-blob": "^12.12.0", "@faker-js/faker": "^6.1.2", + "@microsoft/microsoft-graph-client": "^3.0.5", "@nestjs-modules/mailer": "^1.6.1", "@nestjs/bull": "^0.6.0", "@nestjs/common": "^9.0.0", @@ -1341,6 +1344,165 @@ "node": ">= 12.0.0" } }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", + "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-http": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.3.1.tgz", + "integrity": "sha512-cur03BUwV0Tbv81bQBOLafFB02B6G++K6F2O3IMl8pSE2QlXm3cu11bfyBNlDUKi5U+xnB3GC63ae3athhkx6Q==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/core-util": "^1.1.1", + "@azure/logger": "^1.0.0", + "@types/node-fetch": "^2.5.0", + "@types/tunnel": "^0.0.3", + "form-data": "^4.0.0", + "node-fetch": "^2.6.7", + "process": "^0.11.10", + "tough-cookie": "^4.0.0", + "tslib": "^2.2.0", + "tunnel": "^0.0.6", + "uuid": "^8.3.0", + "xml2js": "^0.4.19" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-http/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/@azure/core-lro": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.1.tgz", + "integrity": "sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.0.0-preview.13", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", + "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "dependencies": { + "@opentelemetry/api": "^1.0.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", + "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", + "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-10.0.0.tgz", + "integrity": "sha512-/LghpT93jsZLy55QzTsRZWMx6R1Mjc1Aktwps8sKSGE3WbrGwbSsh2uhDlpl6FMcKChYjJ0ochThWwwOodrQNg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.15.0.tgz", + "integrity": "sha512-fwC5M0c8pxOAzmScPbpx7j28YVTDebUaizlVF7bR0xvlU0r3VWW5OobCcr9ybqKS6wGyO7u4EhXJS9rjRWAuwA==", + "dependencies": { + "@azure/msal-common": "^10.0.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": "10 || 12 || 14 || 16 || 18" + } + }, + "node_modules/@azure/storage-blob": { + "version": "12.12.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.12.0.tgz", + "integrity": "sha512-o/Mf6lkyYG/eBW4/hXB9864RxVNmAkcKHjsGR6Inlp5hupa3exjSyH2KjO3tLO//YGA+tS+17hM2bxRl9Sn16g==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-http": "^2.0.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/logger": "^1.0.0", + "events": "^3.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -2878,6 +3040,32 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@microsoft/microsoft-graph-client": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.5.tgz", + "integrity": "sha512-xQADFNLUhE78RzYadFZtOmy/5wBZenSZhVK193m40MTDC5hl1aYMQO1QOJApnKga8WcvMCDCU10taRhuXTOz5w==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependenciesMeta": { + "@azure/identity": { + "optional": true + }, + "@azure/msal-browser": { + "optional": true + }, + "buffer": { + "optional": true + }, + "stream-browserify": { + "optional": true + } + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.1.2.tgz", @@ -3357,39 +3545,28 @@ } }, "node_modules/@nestjs/swagger": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.0.4.tgz", - "integrity": "sha512-YKf9RvCYHAgqkMvUG3xRr0zAcnuN8JsWPfvi7h0hzEXLRhNxByj2FT8Rzj+0fPAe6VZioWXBDpLzshO2PjE48Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.2.1.tgz", + "integrity": "sha512-9M2vkfJHIzLqDZwvM5TEZO0MxRCvIb0xVy0LsmWwxH1lrb0z/4MhU+r2CWDhBtTccVJrKxVPiU2s3T3b9uUJbg==", "dependencies": { - "@nestjs/mapped-types": "1.1.0", + "@nestjs/mapped-types": "1.2.2", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "4.12.0" + "swagger-ui-dist": "4.15.5" }, "peerDependencies": { "@fastify/static": "^6.0.0", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", + "class-transformer": "*", + "class-validator": "*", "reflect-metadata": "^0.1.12" }, "peerDependenciesMeta": { "@fastify/static": { "optional": true - } - } - }, - "node_modules/@nestjs/swagger/node_modules/@nestjs/mapped-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.1.0.tgz", - "integrity": "sha512-+2kSly4P1QI+9eGt+/uGyPdEG1hVz7nbpqPHWZVYgoqz8eOHljpXPag+UCVRw9zo2XCu4sgNUIGe8Uk0+OvUQg==", - "peerDependencies": { - "@nestjs/common": "^7.0.8 || ^8.0.0 || ^9.0.0", - "class-transformer": "^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0", - "class-validator": "^0.11.1 || ^0.12.0 || ^0.13.0", - "reflect-metadata": "^0.1.12" - }, - "peerDependenciesMeta": { + }, "class-transformer": { "optional": true }, @@ -3398,17 +3575,6 @@ } } }, - "node_modules/@nestjs/swagger/node_modules/class-validator": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", - "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", - "optional": true, - "peer": true, - "dependencies": { - "libphonenumber-js": "^1.9.43", - "validator": "^13.7.0" - } - }, "node_modules/@nestjs/testing": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.0.7.tgz", @@ -3525,6 +3691,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@opentelemetry/api": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.0.tgz", + "integrity": "sha512-IgMK9i3sFGNUqPMbjABm0G26g0QCKCUBfglhQ7rQq6WcxbKfEHRcmwsoER4hZcuYqJgkYn2OeuoJIv7Jsftp7g==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgr/utils": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", @@ -3951,6 +4125,28 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, + "node_modules/@types/node-fetch": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4077,6 +4273,14 @@ "@types/superagent": "*" } }, + "node_modules/@types/tunnel": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", + "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/validator": { "version": "13.7.11", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.11.tgz", @@ -7293,7 +7497,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -13537,6 +13740,14 @@ "node": ">=10" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -13640,6 +13851,11 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/pug": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", @@ -13784,6 +14000,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -13997,6 +14218,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -14204,6 +14430,11 @@ "node": ">=6" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -15004,9 +15235,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.12.0.tgz", - "integrity": "sha512-B0Iy2ueXtbByE6OOyHTi3lFQkpPi/L7kFOKFeKTr44za7dJIELa9kzaca6GkndCgpK1QTjArnoXG+aUy0XQp1w==" + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz", + "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA==" }, "node_modules/swagger-ui-express": { "version": "4.5.0", @@ -15288,6 +15519,28 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -15508,6 +15761,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -15658,6 +15919,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16169,6 +16439,26 @@ } } }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", @@ -17310,6 +17600,131 @@ "tslib": "^2.3.1" } }, + "@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", + "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-http": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.3.1.tgz", + "integrity": "sha512-cur03BUwV0Tbv81bQBOLafFB02B6G++K6F2O3IMl8pSE2QlXm3cu11bfyBNlDUKi5U+xnB3GC63ae3athhkx6Q==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/core-util": "^1.1.1", + "@azure/logger": "^1.0.0", + "@types/node-fetch": "^2.5.0", + "@types/tunnel": "^0.0.3", + "form-data": "^4.0.0", + "node-fetch": "^2.6.7", + "process": "^0.11.10", + "tough-cookie": "^4.0.0", + "tslib": "^2.2.0", + "tunnel": "^0.0.6", + "uuid": "^8.3.0", + "xml2js": "^0.4.19" + }, + "dependencies": { + "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" + } + } + } + }, + "@azure/core-lro": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.1.tgz", + "integrity": "sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-tracing": { + "version": "1.0.0-preview.13", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", + "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "requires": { + "@opentelemetry/api": "^1.0.1", + "tslib": "^2.2.0" + } + }, + "@azure/core-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", + "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/logger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", + "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/msal-common": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-10.0.0.tgz", + "integrity": "sha512-/LghpT93jsZLy55QzTsRZWMx6R1Mjc1Aktwps8sKSGE3WbrGwbSsh2uhDlpl6FMcKChYjJ0ochThWwwOodrQNg==" + }, + "@azure/msal-node": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.15.0.tgz", + "integrity": "sha512-fwC5M0c8pxOAzmScPbpx7j28YVTDebUaizlVF7bR0xvlU0r3VWW5OobCcr9ybqKS6wGyO7u4EhXJS9rjRWAuwA==", + "requires": { + "@azure/msal-common": "^10.0.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + } + }, + "@azure/storage-blob": { + "version": "12.12.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.12.0.tgz", + "integrity": "sha512-o/Mf6lkyYG/eBW4/hXB9864RxVNmAkcKHjsGR6Inlp5hupa3exjSyH2KjO3tLO//YGA+tS+17hM2bxRl9Sn16g==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-http": "^2.0.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/logger": "^1.0.0", + "events": "^3.0.0", + "tslib": "^2.2.0" + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -18506,6 +18921,15 @@ "tar": "^6.1.11" } }, + "@microsoft/microsoft-graph-client": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.5.tgz", + "integrity": "sha512-xQADFNLUhE78RzYadFZtOmy/5wBZenSZhVK193m40MTDC5hl1aYMQO1QOJApnKga8WcvMCDCU10taRhuXTOz5w==", + "requires": { + "@babel/runtime": "^7.12.5", + "tslib": "^2.2.0" + } + }, "@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.1.2.tgz", @@ -18793,34 +19217,15 @@ } }, "@nestjs/swagger": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.0.4.tgz", - "integrity": "sha512-YKf9RvCYHAgqkMvUG3xRr0zAcnuN8JsWPfvi7h0hzEXLRhNxByj2FT8Rzj+0fPAe6VZioWXBDpLzshO2PjE48Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.2.1.tgz", + "integrity": "sha512-9M2vkfJHIzLqDZwvM5TEZO0MxRCvIb0xVy0LsmWwxH1lrb0z/4MhU+r2CWDhBtTccVJrKxVPiU2s3T3b9uUJbg==", "requires": { - "@nestjs/mapped-types": "1.1.0", + "@nestjs/mapped-types": "1.2.2", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "4.12.0" - }, - "dependencies": { - "@nestjs/mapped-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.1.0.tgz", - "integrity": "sha512-+2kSly4P1QI+9eGt+/uGyPdEG1hVz7nbpqPHWZVYgoqz8eOHljpXPag+UCVRw9zo2XCu4sgNUIGe8Uk0+OvUQg==", - "requires": {} - }, - "class-validator": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", - "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", - "optional": true, - "peer": true, - "requires": { - "libphonenumber-js": "^1.9.43", - "validator": "^13.7.0" - } - } + "swagger-ui-dist": "4.15.5" } }, "@nestjs/testing": { @@ -18889,6 +19294,11 @@ } } }, + "@opentelemetry/api": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.0.tgz", + "integrity": "sha512-IgMK9i3sFGNUqPMbjABm0G26g0QCKCUBfglhQ7rQq6WcxbKfEHRcmwsoER4hZcuYqJgkYn2OeuoJIv7Jsftp7g==" + }, "@pkgr/utils": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", @@ -19283,6 +19693,27 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, + "@types/node-fetch": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -19409,6 +19840,14 @@ "@types/superagent": "*" } }, + "@types/tunnel": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", + "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", + "requires": { + "@types/node": "*" + } + }, "@types/validator": { "version": "13.7.11", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.11.tgz", @@ -21813,8 +22252,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "execa": { "version": "5.1.1", @@ -26585,6 +27023,11 @@ "uuid": "^8.3.2" } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -26683,6 +27126,11 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "pug": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", @@ -26818,6 +27266,11 @@ "side-channel": "^1.0.4" } }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -26977,6 +27430,11 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -27111,6 +27569,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -27742,9 +28205,9 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "swagger-ui-dist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.12.0.tgz", - "integrity": "sha512-B0Iy2ueXtbByE6OOyHTi3lFQkpPi/L7kFOKFeKTr44za7dJIELa9kzaca6GkndCgpK1QTjArnoXG+aUy0XQp1w==" + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz", + "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA==" }, "swagger-ui-express": { "version": "4.5.0", @@ -27947,6 +28410,24 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + } + } + }, "tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -28086,6 +28567,11 @@ } } }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -28183,6 +28669,15 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -28544,6 +29039,20 @@ "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "requires": {} }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index ece989419..707045c9b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,7 +23,10 @@ "pre-commit": "lint-staged" }, "dependencies": { + "@azure/msal-node": "^1.15.0", + "@azure/storage-blob": "^12.12.0", "@faker-js/faker": "^6.1.2", + "@microsoft/microsoft-graph-client": "^3.0.5", "@nestjs-modules/mailer": "^1.6.1", "@nestjs/bull": "^0.6.0", "@nestjs/common": "^9.0.0", @@ -108,4 +111,4 @@ "prettier --write" ] } -} \ No newline at end of file +} diff --git a/backend/src/infrastructure/config/config.module.ts b/backend/src/infrastructure/config/config.module.ts index 6dd52b78e..c9710ee24 100644 --- a/backend/src/infrastructure/config/config.module.ts +++ b/backend/src/infrastructure/config/config.module.ts @@ -80,7 +80,8 @@ import { configuration } from './configuration'; REDIS_USER: Joi.string(), REDIS_PASSWORD: Joi.string(), REDIS_HOST: Joi.string().required(), - REDIS_PORT: Joi.number().required() + REDIS_PORT: Joi.number().required(), + AZURE_STORAGE_CONNECTION_STRING: Joi.string().required() }) }) ], diff --git a/backend/src/infrastructure/config/configuration.ts b/backend/src/infrastructure/config/configuration.ts index e5ec98ec1..307804249 100644 --- a/backend/src/infrastructure/config/configuration.ts +++ b/backend/src/infrastructure/config/configuration.ts @@ -31,7 +31,8 @@ export const configuration = (): Configuration => { clientId: process.env.AZURE_CLIENT_ID as string, clientSecret: process.env.AZURE_CLIENT_SECRET as string, tenantId: process.env.AZURE_TENANT_ID as string, - enabled: process.env.AZURE_ENABLE === 'true' + enabled: process.env.AZURE_ENABLE === 'true', + authority: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}` }, smtp: { host: process.env.SMTP_HOST as string, @@ -51,6 +52,9 @@ export const configuration = (): Configuration => { password: process.env.REDIS_PASSWORD as string, host: process.env.REDIS_HOST as string, port: parseInt(process.env.REDIS_PORT as string, 10) + }, + storage: { + connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING as string } }; diff --git a/backend/src/libs/constants/azure.ts b/backend/src/libs/constants/azure.ts index 72f342952..f0fc392d7 100644 --- a/backend/src/libs/constants/azure.ts +++ b/backend/src/libs/constants/azure.ts @@ -5,3 +5,5 @@ export const AZURE_CLIENT_ID = 'azure.clientId'; export const AZURE_CLIENT_SECRET = 'azure.clientSecret'; export const AZURE_TENANT_ID = 'azure.tenantId'; + +export const AZURE_AUTHORITY = 'azure.authority'; diff --git a/backend/src/libs/constants/storage.ts b/backend/src/libs/constants/storage.ts new file mode 100644 index 000000000..5a10e590b --- /dev/null +++ b/backend/src/libs/constants/storage.ts @@ -0,0 +1 @@ +export const CONNECTION_STRING = 'storage.connectionString'; diff --git a/backend/src/modules/auth/auth.providers.ts b/backend/src/modules/auth/auth.providers.ts index a293af5d8..68ccd261a 100644 --- a/backend/src/modules/auth/auth.providers.ts +++ b/backend/src/modules/auth/auth.providers.ts @@ -1,4 +1,3 @@ -import AzureAuthServiceImpl from '../azure/services/cron.azure.service'; import { CreateResetTokenAuthApplicationImpl } from './applications/create-reset-token.auth.application'; import { GetTokenAuthApplicationImpl } from './applications/get-token.auth.application'; import { RegisterAuthApplicationImpl } from './applications/register.auth.application'; @@ -28,11 +27,6 @@ export const createResetTokenAuthService = { useClass: CreateResetTokenAuthServiceImpl }; -export const azureAuthService = { - provide: TYPES.services.AzureAuthService, - useClass: AzureAuthServiceImpl -}; - export const getTokenAuthApplication = { provide: TYPES.applications.GetTokenAuthApplication, useClass: GetTokenAuthApplicationImpl diff --git a/backend/src/modules/auth/shared/login.auth.ts b/backend/src/modules/auth/shared/login.auth.ts index 49dabd77b..a391d2266 100644 --- a/backend/src/modules/auth/shared/login.auth.ts +++ b/backend/src/modules/auth/shared/login.auth.ts @@ -8,7 +8,7 @@ export const signIn = async ( getTokenService: GetTokenAuthService | GetTokenAuthApplication, strategy: string ) => { - const { email, firstName, lastName, _id, isSAdmin, providerAccountCreatedAt } = user; + const { email, firstName, lastName, _id, isSAdmin, providerAccountCreatedAt, avatar } = user; const jwt = await getTokenService.getTokens(_id); if (!jwt) return null; @@ -22,6 +22,7 @@ export const signIn = async ( id: _id, isSAdmin, providerAccountCreatedAt, - _id + _id, + avatar }; }; diff --git a/backend/src/modules/azure/azure.module.ts b/backend/src/modules/azure/azure.module.ts index 2a274cb63..eea30fa60 100644 --- a/backend/src/modules/azure/azure.module.ts +++ b/backend/src/modules/azure/azure.module.ts @@ -1,13 +1,14 @@ import { Module } from '@nestjs/common'; import AuthModule from '../auth/auth.module'; import { CommunicationModule } from '../communication/communication.module'; +import { StorageModule } from '../storage/storage.module'; import UsersModule from '../users/users.module'; -import { authAzureApplication, authAzureService, cronAzureService } from './azure.providers'; +import { authAzureApplication, authAzureService } from './azure.providers'; import AzureController from './controller/azure.controller'; @Module({ - imports: [UsersModule, AuthModule, CommunicationModule], + imports: [UsersModule, AuthModule, CommunicationModule, StorageModule], controllers: [AzureController], - providers: [cronAzureService, authAzureService, authAzureApplication] + providers: [authAzureService, authAzureApplication] }) export default class AzureModule {} diff --git a/backend/src/modules/azure/azure.providers.ts b/backend/src/modules/azure/azure.providers.ts index d97b004a9..c55377dd2 100644 --- a/backend/src/modules/azure/azure.providers.ts +++ b/backend/src/modules/azure/azure.providers.ts @@ -1,12 +1,6 @@ import { AuthAzureApplicationImpl } from './applications/auth.azure.application'; import { TYPES } from './interfaces/types'; import AuthAzureServiceImpl from './services/auth.azure.service'; -import CronAzureServiceImpl from './services/cron.azure.service'; - -export const cronAzureService = { - provide: TYPES.services.CronAzureService, - useClass: CronAzureServiceImpl -}; export const authAzureService = { provide: TYPES.services.AuthAzureService, diff --git a/backend/src/modules/azure/interfaces/services/cron.azure.service.interface.ts b/backend/src/modules/azure/interfaces/services/cron.azure.service.interface.ts deleted file mode 100644 index 8f16a7c9d..000000000 --- a/backend/src/modules/azure/interfaces/services/cron.azure.service.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface CronAzureService { - getAzureAccessToken: () => Promise; -} diff --git a/backend/src/modules/azure/services/auth.azure.service.ts b/backend/src/modules/azure/services/auth.azure.service.ts index 48594c270..3f1f441d1 100644 --- a/backend/src/modules/azure/services/auth.azure.service.ts +++ b/backend/src/modules/azure/services/auth.azure.service.ts @@ -1,23 +1,29 @@ import { Inject, Injectable } from '@nestjs/common'; -import axios from 'axios'; import jwt_decode from 'jwt-decode'; import isEmpty from 'src/libs/utils/isEmpty'; import { GetTokenAuthService } from 'src/modules/auth/interfaces/services/get-token.auth.service.interface'; import * as AuthType from 'src/modules/auth/interfaces/types'; +import * as StorageType from 'src/modules/storage/interfaces/types'; import { signIn } from 'src/modules/auth/shared/login.auth'; import { CreateUserService } from 'src/modules/users/interfaces/services/create.user.service.interface'; import { GetUserService } from 'src/modules/users/interfaces/services/get.user.service.interface'; import * as UserType from 'src/modules/users/interfaces/types'; import { AuthAzureService } from '../interfaces/services/auth.azure.service.interface'; -import { CronAzureService } from '../interfaces/services/cron.azure.service.interface'; -import { TYPES } from '../interfaces/types'; -import * as CommunicationsType from 'src/modules/communication/interfaces/types'; -import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; +import { ConfidentialClientApplication } from '@azure/msal-node'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { ConfigService } from '@nestjs/config'; +import { AZURE_AUTHORITY, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET } from 'src/libs/constants/azure'; +import { createHash } from 'node:crypto'; +import User from 'src/modules/users/entities/user.schema'; +import { StorageServiceInterface } from 'src/modules/storage/interfaces/services/storage.service'; +import { UpdateUserService } from 'src/modules/users/interfaces/services/update.user.service.interface'; type AzureUserFound = { - mail?: string; - displayName?: string; - userPrincipalName?: string; + id: string; + mail: string; + displayName: string; + userPrincipalName: string; + createdDateTime: Date; }; type AzureDecodedUser = { @@ -30,18 +36,48 @@ type AzureDecodedUser = { @Injectable() export default class AuthAzureServiceImpl implements AuthAzureService { + private graphClient: Client; + private authCredentials: { accessToken: string; expiresOn: Date }; + constructor( @Inject(UserType.TYPES.services.CreateUserService) private readonly createUserService: CreateUserService, @Inject(UserType.TYPES.services.GetUserService) private readonly getUserService: GetUserService, + @Inject(AuthType.TYPES.services.UpdateUserService) + private readonly updateUserService: UpdateUserService, @Inject(AuthType.TYPES.services.GetTokenAuthService) private readonly getTokenService: GetTokenAuthService, - @Inject(TYPES.services.CronAzureService) - private readonly cronAzureService: CronAzureService, - @Inject(CommunicationsType.TYPES.services.SlackCommunicationService) - private slackCommunicationService: CommunicationServiceInterface - ) {} + private readonly configService: ConfigService, + @Inject(StorageType.TYPES.services.StorageService) + private readonly storageService: StorageServiceInterface + ) { + const confidentialClient = new ConfidentialClientApplication({ + auth: { + clientId: configService.get(AZURE_CLIENT_ID), + clientSecret: configService.get(AZURE_CLIENT_SECRET), + authority: configService.get(AZURE_AUTHORITY) + } + }); + + this.graphClient = Client.init({ + fetchOptions: { + headers: { ConsistencyLevel: 'eventual' } + }, + authProvider: async (done) => { + if (this.authCredentials?.expiresOn <= new Date()) { + return done(null, this.authCredentials.accessToken); + } + + const { accessToken, expiresOn } = await confidentialClient.acquireTokenByClientCredential({ + scopes: ['https://graph.microsoft.com/.default'] + }); + + this.authCredentials = { accessToken, expiresOn }; + done(null, accessToken); + } + }); + } async loginOrRegisterAzureToken(azureToken: string) { const { unique_name, email, name, given_name, family_name } = ( @@ -54,56 +90,84 @@ export default class AuthAzureServiceImpl implements AuthAzureService { const emailOrUniqueName = email ?? unique_name; - const userExists = await this.checkUserExistsInActiveDirectory(emailOrUniqueName); const userFromAzure = await this.getUserFromAzure(emailOrUniqueName); - if (!userExists || isEmpty(userFromAzure)) return null; + if (!userFromAzure) return null; const user = await this.getUserService.getByEmail(emailOrUniqueName); - if (user) return signIn(user, this.getTokenService, 'azure'); + let userToAuthenticate: User; - const createdUser = await this.createUserService.create({ - email: emailOrUniqueName, - firstName, - lastName, - providerAccountCreatedAt: userFromAzure.value[0].createdDateTime - }); + if (user) { + userToAuthenticate = user; + } else { + const createdUser = await this.createUserService.create({ + email: emailOrUniqueName, + firstName, + lastName, + providerAccountCreatedAt: userFromAzure.createdDateTime + }); - //this.slackCommunicationService.executeAddUserMainChannel({ email: emailOrUniqueName }); + if (!createdUser) return null; - if (!createdUser) return null; + userToAuthenticate = createdUser; + } - return signIn(createdUser, this.getTokenService, 'azure'); - } + const avatarUrl = await this.getUserPhoto(userToAuthenticate); - getGraphQueryUrl(email: string) { - return `https://graph.microsoft.com/v1.0/users?$search="mail:${email}" OR "displayName:${email}" OR "userPrincipalName:${email}"&$orderbydisplayName&$select=displayName,mail,userPrincipalName,createdDateTime`; - } + if (avatarUrl) { + await this.updateUserService.updateUserAvatar(userToAuthenticate._id, avatarUrl); - async getUserFromAzure(email: string) { - const queryUrl = this.getGraphQueryUrl(email); + userToAuthenticate.avatar = avatarUrl; + } - const { data } = await axios.get(queryUrl, { - headers: { - Authorization: `Bearer ${await this.cronAzureService.getAzureAccessToken()}`, - ConsistencyLevel: 'eventual' - } - }); + return signIn(userToAuthenticate, this.getTokenService, 'azure'); + } + + async getUserFromAzure(email: string): Promise { + const { value } = await this.graphClient + .api('/users') + .select(['id', 'displayName', 'mail', 'userPrincipalName', 'createdDateTime']) + .search(`"mail:${email}" OR "displayName:${email}" OR "userPrincipalName:${email}"`) + .orderby('displayName') + .get(); - return data; + return value[0]; } async checkUserExistsInActiveDirectory(email: string) { const data = await this.getUserFromAzure(email); - const user = data.value.find( - (userFound: AzureUserFound) => - userFound.mail?.toLowerCase() === email.toLowerCase() || - userFound.displayName?.toLowerCase() === email.toLowerCase() || - userFound.userPrincipalName?.toLowerCase() === email.toLowerCase() - ); + return !isEmpty(data); + } + + private async getUserPhoto(user: User) { + const { email, avatar } = user; + const azureUser = await this.getUserFromAzure(email); + + if (!azureUser) return ''; + + try { + const blob = await this.graphClient.api(`/users/${azureUser.id}/photo/$value`).get(); + + const buffer = Buffer.from(await blob.arrayBuffer()); + const hash = createHash('md5').update(buffer).digest('hex'); + + if (avatar) { + const avatarHash = avatar.split('/').pop().split('.').shift(); + + if (avatarHash === hash) return; + + await this.storageService.deleteFile(avatar); + } - return !isEmpty(user); + return this.storageService.uploadFile(hash, { + buffer, + mimetype: blob.type, + originalname: `${hash}.${blob.type.split('/').pop()}` + }); + } catch (ex) { + return ''; + } } } diff --git a/backend/src/modules/azure/services/cron.azure.service.ts b/backend/src/modules/azure/services/cron.azure.service.ts deleted file mode 100644 index dc722da0f..000000000 --- a/backend/src/modules/azure/services/cron.azure.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import axios from 'axios'; -import { AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID } from 'src/libs/constants/azure'; -import { CronAzureService } from '../interfaces/services/cron.azure.service.interface'; - -@Injectable() -export default class CronAzureServiceImpl implements CronAzureService { - constructor(private readonly configService: ConfigService) {} - - getOAuthUrl(tenantId: string) { - return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; - } - - async getAzureAccessToken(): Promise { - const params = new URLSearchParams(); - params.append('grant_type', 'client_credentials'); - params.append('client_id', this.configService.get(AZURE_CLIENT_ID)!); - params.append('client_secret', this.configService.get(AZURE_CLIENT_SECRET)!); - params.append('scope', 'https://graph.microsoft.com/.default'); - - const config = { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }; - - const authUrl = this.getOAuthUrl(this.configService.get(AZURE_TENANT_ID)!); - - const { data } = await axios.post(authUrl, params, config); - - return data.access_token; - } -} diff --git a/backend/src/modules/storage/interfaces/services/storage.service.ts b/backend/src/modules/storage/interfaces/services/storage.service.ts new file mode 100644 index 000000000..faea7328e --- /dev/null +++ b/backend/src/modules/storage/interfaces/services/storage.service.ts @@ -0,0 +1,10 @@ +import { BlobDeleteResponse } from '@azure/storage-blob'; + +export interface StorageServiceInterface { + uploadFile( + fileName: string, + file: { originalname: string; buffer: Buffer; mimetype: string }, + containerName?: string + ): Promise; + deleteFile(fileUrl: string, containerName?: string): Promise; +} diff --git a/backend/src/modules/storage/interfaces/types.ts b/backend/src/modules/storage/interfaces/types.ts new file mode 100644 index 000000000..180ffcac3 --- /dev/null +++ b/backend/src/modules/storage/interfaces/types.ts @@ -0,0 +1,11 @@ +export const TYPES = { + services: { + StorageService: 'StorageService' + } +}; + +export enum ContainerNameEnum { + SPLIT_IMAGES = 'split-images' +} + +export type ContainerName = `${ContainerNameEnum}`; diff --git a/backend/src/modules/storage/services/storage.service.ts b/backend/src/modules/storage/services/storage.service.ts new file mode 100644 index 000000000..6ceb3361b --- /dev/null +++ b/backend/src/modules/storage/services/storage.service.ts @@ -0,0 +1,54 @@ +import { BlobDeleteResponse, BlobServiceClient } from '@azure/storage-blob'; +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { CONNECTION_STRING } from 'src/libs/constants/storage'; +import { StorageServiceInterface } from '../interfaces/services/storage.service'; +import { ContainerNameEnum } from '../interfaces/types'; + +@Injectable() +export class StorageService implements StorageServiceInterface { + private blobServiceClient: BlobServiceClient; + + constructor(private readonly configService: ConfigService, private readonly logger: Logger) { + const connectionString = this.configService.get(CONNECTION_STRING); + + this.blobServiceClient = BlobServiceClient.fromConnectionString(connectionString); + } + + async uploadFile( + fileName: string, + file: { originalname: string; buffer: Buffer; mimetype: string }, + containerName = ContainerNameEnum.SPLIT_IMAGES + ): Promise { + const containerClient = this.blobServiceClient.getContainerClient(containerName); + + const fileExtension = file.originalname.split('.').pop(); + const blobClient = containerClient.getBlockBlobClient(`${fileName}.${fileExtension}`); + + await blobClient + .upload(file.buffer, file.buffer.length, { + blobHTTPHeaders: { + blobContentType: file.mimetype + } + }) + .catch((err) => { + this.logger.error(err); + }); + + return blobClient.url; + } + + deleteFile( + fileUrl: string, + containerName = ContainerNameEnum.SPLIT_IMAGES + ): Promise { + const containerClient = this.blobServiceClient.getContainerClient(containerName); + + const fileName = fileUrl.split('/').pop(); + const blobClient = containerClient.getBlockBlobClient(fileName); + + return blobClient.delete().catch((err) => { + this.logger.error(err); + }); + } +} diff --git a/backend/src/modules/storage/storage.module.ts b/backend/src/modules/storage/storage.module.ts new file mode 100644 index 000000000..00014567f --- /dev/null +++ b/backend/src/modules/storage/storage.module.ts @@ -0,0 +1,8 @@ +import { Logger, Module } from '@nestjs/common'; +import { storageService } from './storage.providers'; + +@Module({ + providers: [storageService, Logger], + exports: [storageService] +}) +export class StorageModule {} diff --git a/backend/src/modules/storage/storage.providers.ts b/backend/src/modules/storage/storage.providers.ts new file mode 100644 index 000000000..6402aa297 --- /dev/null +++ b/backend/src/modules/storage/storage.providers.ts @@ -0,0 +1,7 @@ +import { TYPES } from './interfaces/types'; +import { StorageService } from './services/storage.service'; + +export const storageService = { + provide: TYPES.services.StorageService, + useClass: StorageService +}; diff --git a/backend/src/modules/users/dto/user.dto.ts b/backend/src/modules/users/dto/user.dto.ts index 08a945a78..b180d6d36 100644 --- a/backend/src/modules/users/dto/user.dto.ts +++ b/backend/src/modules/users/dto/user.dto.ts @@ -37,4 +37,8 @@ export default class UserDto { @IsOptional() @IsDateString() providerAccountCreatedAt?: Date; + + @ApiPropertyOptional({ default: '' }) + @IsOptional() + avatar?: string; } diff --git a/backend/src/modules/users/entities/user.schema.ts b/backend/src/modules/users/entities/user.schema.ts index a6dc75f1d..e4897982d 100644 --- a/backend/src/modules/users/entities/user.schema.ts +++ b/backend/src/modules/users/entities/user.schema.ts @@ -40,6 +40,9 @@ export default class User extends BaseModel { @Prop({ default: false }) isAnonymous: boolean; + + @Prop({ default: '' }) + avatar?: string; } export const UserSchema = SchemaFactory.createForClass(User); diff --git a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts index 3780a18a3..31415e3a2 100644 --- a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts @@ -15,4 +15,6 @@ export interface UpdateUserService { checkEmail(token: string): Promise; updateSuperAdmin(user: UpdateUserDto, requestUser: UserDto): Promise>; + + updateUserAvatar(avatar: string, userId: string): Promise>; } diff --git a/backend/src/modules/users/repository/user.repository.interface.ts b/backend/src/modules/users/repository/user.repository.interface.ts index 0910924ab..2dd1bb06e 100644 --- a/backend/src/modules/users/repository/user.repository.interface.ts +++ b/backend/src/modules/users/repository/user.repository.interface.ts @@ -6,6 +6,7 @@ export interface UserRepositoryInterface extends BaseInterfaceRepository { updateUserWithRefreshToken(refreshToken: string, userId: string): Promise; updateUserPassword(email: string, password: string): Promise; updateSuperAdmin(userId: string, isSAdmin: boolean): Promise; + updateUserAvatar(userId: string, avatarUrl: string): Promise; deleteUser(userId: string, withSession: boolean); getAllWithPagination(page: number, size: number, searchUser?: string): Promise; } diff --git a/backend/src/modules/users/repository/user.repository.ts b/backend/src/modules/users/repository/user.repository.ts index 5f05f96c7..76ba59f54 100644 --- a/backend/src/modules/users/repository/user.repository.ts +++ b/backend/src/modules/users/repository/user.repository.ts @@ -41,6 +41,14 @@ export class UserRepository return this.findOneByFieldAndUpdate({ _id: userId }, { $set: { isSAdmin } }, { new: true }); } + updateUserAvatar(userId: string, avatarUrl: string): Promise { + return this.findOneByFieldAndUpdate( + { _id: userId }, + { $set: { avatar: avatarUrl } }, + { new: true } + ); + } + deleteUser(userId: string, withSession: boolean) { return this.findOneAndRemove(userId, withSession); } diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index 6a01d20a5..186a68f11 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -35,9 +35,11 @@ export default class updateUserServiceImpl implements UpdateUserService { async setPassword(userEmail: string, newPassword: string, newPasswordConf: string) { const password = await encrypt(newPassword); - if (newPassword !== newPasswordConf) + if (newPassword !== newPasswordConf) { throw new HttpException('PASSWORDS_DO_NOT_MATCH', HttpStatus.BAD_REQUEST); - const user = this.userRepository.updateUserPassword(userEmail, password); + } + + const user = await this.userRepository.updateUserPassword(userEmail, password); if (!user) throw new HttpException('USER_NOT_FOUND', HttpStatus.NOT_FOUND); @@ -48,7 +50,9 @@ export default class updateUserServiceImpl implements UpdateUserService { const userFromDb = await this.resetModel.findOne({ token }); if (!userFromDb) throw new HttpException('USER_FROM_TOKEN_NOT_FOUND', HttpStatus.NOT_FOUND); + this.tokenValidator(userFromDb.updatedAt); + const user = await this.userRepository.findOneByField({ email: userFromDb.emailAddress }); if (!user) throw new HttpException('USER_NOT_FOUND', HttpStatus.NOT_FOUND); @@ -68,6 +72,7 @@ export default class updateUserServiceImpl implements UpdateUserService { if (requestUser._id.toString() === user._id) { throw new BadRequestException(UPDATE_FAILED); } + const userUpdated = await this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); if (!userUpdated) { @@ -76,4 +81,14 @@ export default class updateUserServiceImpl implements UpdateUserService { return userUpdated; } + + async updateUserAvatar(userId: string, avatarUrl: string) { + const user = await this.userRepository.updateUserAvatar(userId, avatarUrl); + + if (!user) { + throw new BadRequestException(UPDATE_FAILED); + } + + return user; + } } diff --git a/docker-compose.yaml b/docker-compose.yaml index 60c8f7e93..340dc0aac 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -79,6 +79,37 @@ services: env_file: - ./database/redis/.env + azurite: + container_name: azurite + image: mcr.microsoft.com/azure-storage/azurite + ports: + - '${FORWARD_AZURITE_BLOB_PORT:-10000}:10000' + command: azurite-blob --blobHost 0.0.0.0 --disableProductStyleUrl + volumes: + - azurite:/data + networks: + - split-network + depends_on: + - azurite-init + + azurite-init: + image: curlimages/curl:7.87.0 + entrypoint: + - /bin/sh + - -c + - | + echo "Waiting for Azurite to start..." + while ! nc -z azurite 10000; do sleep 1; done + curl --request PUT \ + --url 'http://azurite:10000/devstoreaccount1/split-images?restype=container&sv=2021-10-04&ss=btqf&srt=sco&st=2022-12-14T16%3A22%3A03Z&se=2119-01-15T16%3A22%3A00Z&sp=rwdxftlacup&sig=uXt%2FXqfK1iFnNeDP60UD1CwJueWVoADE9gOLqkkI3NM%3D' \ + --header 'x-ms-blob-public-access: blob' + networks: + - split-network + networks: split-network: driver: bridge + +volumes: + azurite: + driver: local \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 446124221..eb94664d5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "v0.1.12", + "version": "v0.1.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "frontend", - "version": "v0.1.12", + "version": "v0.1.14", "dependencies": { "@hello-pangea/dnd": "^16.2.0", "@hookform/resolvers": "^2.8.8", diff --git a/frontend/src/pages/api/auth/[...nextauth].tsx b/frontend/src/pages/api/auth/[...nextauth].tsx index 17ceed95b..f79d26ea8 100644 --- a/frontend/src/pages/api/auth/[...nextauth].tsx +++ b/frontend/src/pages/api/auth/[...nextauth].tsx @@ -107,6 +107,7 @@ export default NextAuth({ _id, isSAdmin, providerAccountCreatedAt, + avatar, } = data; user.firstName = firstName; user.lastName = lastName; @@ -117,6 +118,7 @@ export default NextAuth({ user.id = _id; user.isSAdmin = isSAdmin; user.providerAccountCreatedAt = providerAccountCreatedAt; + user.avatar = avatar; } return true; @@ -135,6 +137,7 @@ export default NextAuth({ email: user.email ?? '', isSAdmin: user.isSAdmin, refreshToken: user.refreshToken, + avatar: user.avatar, }, strategy: user.strategy ?? 'local', error: '', diff --git a/frontend/src/types/next-auth.d.ts b/frontend/src/types/next-auth.d.ts index 98035d6df..0a5c608cd 100644 --- a/frontend/src/types/next-auth.d.ts +++ b/frontend/src/types/next-auth.d.ts @@ -12,6 +12,7 @@ declare module 'next-auth' { isSAdmin: boolean; accessToken: Token; refreshToken?: Token; + avatar?: string; } & DefaultSession['user']; strategy: string; error: string; @@ -26,6 +27,7 @@ declare module 'next-auth' { lastName: string; isSAdmin: boolean; providerAccountCreatedAt?: string; + avatar?: string; } } @@ -39,6 +41,7 @@ declare module 'next-auth/jwt' { email: string; id: string; isSAdmin: boolean; + avatar?: string; }; error: string; strategy: string; diff --git a/frontend/src/types/user/create-login.user.ts b/frontend/src/types/user/create-login.user.ts index fb74ce065..590e127cb 100644 --- a/frontend/src/types/user/create-login.user.ts +++ b/frontend/src/types/user/create-login.user.ts @@ -10,4 +10,5 @@ export interface CreateOrLogin { lastName: string; isSAdmin: boolean; providerAccountCreatedAt?: string; + avatar?: string; } diff --git a/package-lock.json b/package-lock.json index 8a846f39a..6dbbe07cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "split", - "version": "v0.1.12", + "version": "v0.1.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "split", - "version": "v0.1.12", + "version": "v0.1.14", "devDependencies": { "@commitlint/cli": "^17.4.0", "@commitlint/config-conventional": "^17.4.0",