From 1f8f1b1273826689f3d6c886ee79af57965ba420 Mon Sep 17 00:00:00 2001 From: Jhonathan Date: Thu, 11 Jul 2024 21:39:02 -0300 Subject: [PATCH] feat: jwt strategy implementation and jwt update strategy --- .husky/pre-commit | 1 + package.json | 4 + pnpm-lock.yaml | 264 +++++++++++++++++++++++++++ src/app/app.controller.ts | 12 -- src/app/app.module.ts | 9 +- src/app/app.service.ts | 8 - src/app/auth/auth.controller.ts | 17 ++ src/app/auth/auth.decorator.ts | 3 + src/app/auth/auth.dto.ts | 17 ++ src/{ => app}/auth/auth.guard.ts | 2 +- src/app/auth/auth.module.ts | 28 +++ src/app/auth/auth.service.ts | 42 +++++ src/app/auth/jwt.strategy.ts | 39 ++++ src/app/auth/refresh.strategy.ts | 49 +++++ src/app/tests/app.controller.spec.ts | 18 +- src/auth/auth.controller.ts | 0 src/auth/auth.decorator.ts | 3 - src/auth/auth.module.ts | 0 src/auth/auth.service.ts | 0 src/auth/jwt.strategy.ts | 29 --- src/auth/refresh.strategy.ts | 0 src/constants/index.ts | 3 + src/database/schemas/user.schema.ts | 0 src/filters/http-exception.filter.ts | 25 +++ src/main.ts | 13 +- src/schemas/session.schema.ts | 23 +++ src/schemas/user.schema.ts | 34 ++++ src/types/role.enum.ts | 4 + 28 files changed, 572 insertions(+), 75 deletions(-) delete mode 100644 src/app/app.controller.ts delete mode 100644 src/app/app.service.ts create mode 100644 src/app/auth/auth.controller.ts create mode 100644 src/app/auth/auth.decorator.ts create mode 100644 src/app/auth/auth.dto.ts rename src/{ => app}/auth/auth.guard.ts (99%) create mode 100644 src/app/auth/auth.module.ts create mode 100644 src/app/auth/auth.service.ts create mode 100644 src/app/auth/jwt.strategy.ts create mode 100644 src/app/auth/refresh.strategy.ts delete mode 100644 src/auth/auth.controller.ts delete mode 100644 src/auth/auth.decorator.ts delete mode 100644 src/auth/auth.module.ts delete mode 100644 src/auth/auth.service.ts delete mode 100644 src/auth/jwt.strategy.ts delete mode 100644 src/auth/refresh.strategy.ts create mode 100644 src/constants/index.ts delete mode 100644 src/database/schemas/user.schema.ts create mode 100644 src/filters/http-exception.filter.ts create mode 100644 src/schemas/session.schema.ts create mode 100644 src/schemas/user.schema.ts create mode 100644 src/types/role.enum.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index ffdb54f..f0edc62 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,4 @@ pnpm format pnpm lint pnpm test +git add . diff --git a/package.json b/package.json index 35c3fed..c2643e7 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ "@nestjs/terminus": "^10.2.3", "@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/parser": "^7.16.0", + "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "commitizen": "^4.3.0", + "cookie-parser": "^1.4.6", "dotenv": "^16.4.5", "husky": "^9.0.11", "lint-staged": "^15.2.7", @@ -53,6 +55,8 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/swagger": "^7.4.0", "@nestjs/testing": "^10.0.0", + "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2480fc5..fcf9416 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: '@typescript-eslint/parser': specifier: ^7.16.0 version: 7.16.0(eslint@8.57.0)(typescript@5.5.3) + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -50,6 +53,9 @@ importers: commitizen: specifier: ^4.3.0 version: 4.3.0(@types/node@20.14.10)(typescript@5.5.3) + cookie-parser: + specifier: ^1.4.6 + version: 1.4.6 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -96,6 +102,12 @@ importers: '@nestjs/testing': specifier: ^10.0.0 version: 10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)) + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 + '@types/cookie-parser': + specifier: ^1.4.7 + version: 1.4.7 '@types/express': specifier: ^4.17.17 version: 4.17.21 @@ -567,6 +579,10 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + '@microsoft/tsdoc@0.15.0': resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} @@ -795,6 +811,9 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} @@ -804,6 +823,9 @@ packages: '@types/conventional-commits-parser@5.0.0': resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} + '@types/cookie-parser@1.4.7': + resolution: {integrity: sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==} + '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} @@ -1019,6 +1041,9 @@ packages: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1042,6 +1067,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1107,6 +1136,14 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1173,6 +1210,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1284,6 +1325,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -1364,6 +1409,10 @@ packages: color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + color@3.2.1: resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} @@ -1413,6 +1462,9 @@ packages: consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1440,9 +1492,17 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-parser@1.4.6: + resolution: {integrity: sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==} + engines: {node: '>= 0.8.0'} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie@0.4.1: + resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} + engines: {node: '>= 0.6'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -1549,6 +1609,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1565,6 +1628,10 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -1892,6 +1959,10 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -1906,6 +1977,11 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2015,6 +2091,9 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -2034,6 +2113,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -2556,6 +2639,10 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -2645,14 +2732,31 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} @@ -2732,6 +2836,9 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} @@ -2750,6 +2857,11 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2762,6 +2874,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3107,6 +3223,9 @@ packages: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3287,6 +3406,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -3555,6 +3678,9 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -3605,6 +3731,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@2.4.5: resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} engines: {node: '>= 14'} @@ -4259,6 +4388,21 @@ snapshots: '@lukeed/csprng@1.1.0': {} + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.6.2 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + '@microsoft/tsdoc@0.15.0': {} '@mongodb-js/saslprep@1.1.7': @@ -4477,6 +4621,10 @@ snapshots: dependencies: '@babel/types': 7.24.7 + '@types/bcrypt@5.0.2': + dependencies: + '@types/node': 20.14.10 + '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 @@ -4490,6 +4638,10 @@ snapshots: dependencies: '@types/node': 20.14.10 + '@types/cookie-parser@1.4.7': + dependencies: + '@types/express': 4.17.21 + '@types/cookiejar@2.1.5': {} '@types/eslint-scope@3.7.7': @@ -4779,6 +4931,8 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + abbrev@1.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -4798,6 +4952,12 @@ snapshots: acorn@8.12.1: {} + agent-base@6.0.2: + dependencies: + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + ajv-formats@2.1.1(ajv@8.12.0): optionalDependencies: ajv: 8.12.0 @@ -4855,6 +5015,13 @@ snapshots: append-field@1.0.0: {} + aproba@2.0.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + arg@4.1.3: {} argparse@1.0.10: @@ -4935,6 +5102,14 @@ snapshots: base64-js@1.5.1: {} + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + binary-extensions@2.3.0: {} bl@4.1.0: @@ -5067,6 +5242,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@2.0.0: {} + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} @@ -5137,6 +5314,8 @@ snapshots: color-name: 1.1.4 simple-swizzle: 0.2.2 + color-support@1.1.3: {} + color@3.2.1: dependencies: color-convert: 1.9.3 @@ -5205,6 +5384,8 @@ snapshots: consola@2.15.3: {} + console-control-strings@1.1.0: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -5230,8 +5411,15 @@ snapshots: convert-source-map@2.0.0: {} + cookie-parser@1.4.6: + dependencies: + cookie: 0.4.1 + cookie-signature: 1.0.6 + cookie-signature@1.0.6: {} + cookie@0.4.1: {} + cookie@0.6.0: {} cookiejar@2.1.4: {} @@ -5335,6 +5523,8 @@ snapshots: delayed-stream@1.0.0: {} + delegates@1.0.0: {} + depd@2.0.0: {} destroy@1.2.0: {} @@ -5343,6 +5533,8 @@ snapshots: detect-indent@6.1.0: {} + detect-libc@2.0.3: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -5748,6 +5940,10 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs-monkey@1.0.6: {} fs.realpath@1.0.0: {} @@ -5757,6 +5953,18 @@ snapshots: function-bind@1.1.2: {} + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -5866,6 +6074,8 @@ snapshots: has-symbols@1.0.3: {} + has-unicode@2.0.1: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -5886,6 +6096,13 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -6600,6 +6817,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + make-dir@4.0.0: dependencies: semver: 7.6.2 @@ -6665,12 +6886,25 @@ snapshots: minimist@1.2.8: {} + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + mkdirp@0.5.6: dependencies: minimist: 1.2.8 + mkdirp@1.0.4: {} + mongodb-connection-string-url@3.0.1: dependencies: '@types/whatwg-url': 11.0.5 @@ -6741,6 +6975,8 @@ snapshots: node-abort-controller@3.1.1: {} + node-addon-api@5.1.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.17.21 @@ -6753,6 +6989,10 @@ snapshots: node-releases@2.0.14: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + normalize-path@3.0.0: {} npm-run-path@4.0.1: @@ -6763,6 +7003,13 @@ snapshots: dependencies: path-key: 4.0.0 + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + object-assign@4.1.1: {} object-inspect@1.13.2: {} @@ -7090,6 +7337,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -7268,6 +7517,15 @@ snapshots: tapable@2.2.1: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + terser-webpack-plugin@5.3.10(webpack@5.92.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -7516,6 +7774,10 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + widest-line@3.1.0: dependencies: string-width: 4.2.3 @@ -7579,6 +7841,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yaml@2.4.5: {} yargs-parser@21.1.1: {} diff --git a/src/app/app.controller.ts b/src/app/app.controller.ts deleted file mode 100644 index cce879e..0000000 --- a/src/app/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } -} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a5d3b81..fcd1011 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { AuthModule } from '~/app/auth/auth.module'; + import { env } from '~/env'; @Module({ @@ -14,8 +14,9 @@ import { env } from '~/env'; return connection; }, }), + AuthModule, ], - controllers: [AppController], - providers: [AppService], + controllers: [], + providers: [], }) export class AppModule {} diff --git a/src/app/app.service.ts b/src/app/app.service.ts deleted file mode 100644 index 927d7cc..0000000 --- a/src/app/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/src/app/auth/auth.controller.ts b/src/app/auth/auth.controller.ts new file mode 100644 index 0000000..5a35beb --- /dev/null +++ b/src/app/auth/auth.controller.ts @@ -0,0 +1,17 @@ +import { Body, Controller, HttpStatus, Post, Res } from '@nestjs/common'; +import { Response } from 'express'; + +import { AuthService } from '~/app/auth/auth.service'; +import { SignUpDto } from '~/app/auth/auth.dto'; + +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Post('signup') + async signUp(@Body() body: SignUpDto, @Res() res: Response) { + const data = await this.authService.signUp(body); + + return res.status(HttpStatus.CREATED).json(data); + } +} diff --git a/src/app/auth/auth.decorator.ts b/src/app/auth/auth.decorator.ts new file mode 100644 index 0000000..b69b373 --- /dev/null +++ b/src/app/auth/auth.decorator.ts @@ -0,0 +1,3 @@ +import { Reflector } from '@nestjs/core'; + +export const Roles = Reflector.createDecorator(); diff --git a/src/app/auth/auth.dto.ts b/src/app/auth/auth.dto.ts new file mode 100644 index 0000000..f1fef91 --- /dev/null +++ b/src/app/auth/auth.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'; + +export class SignUpDto { + @ApiProperty() + @IsEmail() + @IsNotEmpty() + email: string; + + @ApiProperty() + @IsNotEmpty() + name: string; + + @ApiProperty() + @MinLength(6) + password: string; +} diff --git a/src/auth/auth.guard.ts b/src/app/auth/auth.guard.ts similarity index 99% rename from src/auth/auth.guard.ts rename to src/app/auth/auth.guard.ts index 1418bf0..47382ab 100644 --- a/src/auth/auth.guard.ts +++ b/src/app/auth/auth.guard.ts @@ -16,4 +16,4 @@ export class JwtAuthGuard extends AuthGuard('jwt') { return user; } -} \ No newline at end of file +} diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts new file mode 100644 index 0000000..9760b94 --- /dev/null +++ b/src/app/auth/auth.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { JwtModule, JwtService } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; + +import { AuthService } from '~/app/auth/auth.service'; +import { AuthController } from '~/app/auth/auth.controller'; +import { User, UserSchema } from '~/schemas/user.schema'; +import { JwtStrategy } from '~/app/auth/jwt.strategy'; +import { JwtRefreshStrategy } from '~/app/auth/refresh.strategy'; +import { ACCESS_TOKEN_EXPIRES_IN } from '~/constants'; +import { env } from '~/env'; + +@Module({ + imports: [ + MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), + JwtModule.register({ + global: true, + secret: env.SECRET_KEY, + signOptions: { expiresIn: ACCESS_TOKEN_EXPIRES_IN }, + }), + PassportModule, + ], + controllers: [AuthController], + providers: [AuthService, JwtService, JwtStrategy, JwtRefreshStrategy], + exports: [AuthService], +}) +export class AuthModule {} diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts new file mode 100644 index 0000000..f445cfb --- /dev/null +++ b/src/app/auth/auth.service.ts @@ -0,0 +1,42 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { JwtService } from '@nestjs/jwt'; + +import { ACCESS_TOKEN_EXPIRES_IN, REFRESH_TOKEN_EXPIRES_IN } from '~/constants'; +import { SignUpDto } from '~/app/auth/auth.dto'; +import { User } from '~/schemas/user.schema'; +import { env } from '~/env'; + +@Injectable() +export class AuthService { + constructor( + @InjectModel(User.name) private readonly userModel: Model, + private readonly jwtService: JwtService, + ) {} + + async signUp(data: SignUpDto) { + const emailIsAlreadyInUse = await this.userModel.findOne({ + email: data.email, + }); + if (!!emailIsAlreadyInUse) + throw new HttpException('Email is already in use', HttpStatus.CONFLICT); + + const user = await this.userModel.create(data); + + const accessToken = await this.jwtService.signAsync( + { user: user._id }, + { secret: env.SECRET_KEY, expiresIn: ACCESS_TOKEN_EXPIRES_IN }, + ); + + const refreshToken = await this.jwtService.signAsync( + { user: user._id }, + { + secret: env.SECRET_KEY, + expiresIn: REFRESH_TOKEN_EXPIRES_IN, + }, + ); + + return { user, refreshToken, accessToken }; + } +} diff --git a/src/app/auth/jwt.strategy.ts b/src/app/auth/jwt.strategy.ts new file mode 100644 index 0000000..238199f --- /dev/null +++ b/src/app/auth/jwt.strategy.ts @@ -0,0 +1,39 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { Request } from 'express'; +import { InjectModel } from '@nestjs/mongoose'; + +import { env } from '~/env'; +import { AUTH_COOKIE } from '~/constants'; +import { User } from '~/schemas/user.schema'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor(@InjectModel(User.name) private readonly userModel: Model) { + super({ + ignoreExpiration: false, + jwtFromRequest: ExtractJwt.fromExtractors([ + (req: Request) => { + if (req) { + const data = req.cookies[AUTH_COOKIE]; + + if (data && data.accessToken) return data.accessToken; + } + + return null; + }, + ]), + secretOrKey: env.SECRET_KEY, + }); + } + + async validate(payload: Record) { + const user = this.userModel.findById(payload.user); + + if (user) return user; + + throw new UnauthorizedException(); + } +} diff --git a/src/app/auth/refresh.strategy.ts b/src/app/auth/refresh.strategy.ts new file mode 100644 index 0000000..1d0bbfe --- /dev/null +++ b/src/app/auth/refresh.strategy.ts @@ -0,0 +1,49 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { Request } from 'express'; +import { Model } from 'mongoose'; +import { InjectModel } from '@nestjs/mongoose'; +import { JwtService } from '@nestjs/jwt'; + +import { env } from '~/env'; +import { ACCESS_TOKEN_EXPIRES_IN, AUTH_COOKIE } from '~/constants'; +import { User } from '~/schemas/user.schema'; + +@Injectable() +export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'refresh') { + constructor( + @InjectModel(User.name) private readonly userModel: Model, + private readonly jwtService: JwtService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromExtractors([ + (req: Request) => { + if (req) { + const data = req.cookies[AUTH_COOKIE]; + + if (data && data.refreshToken) return data.refreshToken; + } + + return null; + }, + ]), + ignoreExpiration: false, + secretOrKey: env.SECRET_KEY, + }); + } + + async validate(payload: Record) { + const user = await this.userModel.findById(payload.user); + + if (user) { + const accessToken = await this.jwtService.signAsync( + { user: user._id }, + { secret: env.SECRET_KEY, expiresIn: ACCESS_TOKEN_EXPIRES_IN }, + ); + + return accessToken; + } + throw new UnauthorizedException(); + } +} diff --git a/src/app/tests/app.controller.spec.ts b/src/app/tests/app.controller.spec.ts index f893739..9dfea66 100644 --- a/src/app/tests/app.controller.spec.ts +++ b/src/app/tests/app.controller.spec.ts @@ -1,23 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { AppController } from '../app.controller'; -import { AppService } from '../app.service'; - describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); + beforeEach(async () => {}); describe('root', () => { it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); + expect(true).toBe(true); }); }); }); diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/auth/auth.decorator.ts b/src/auth/auth.decorator.ts deleted file mode 100644 index 93726db..0000000 --- a/src/auth/auth.decorator.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Reflector } from '@nestjs/core'; - -export const Roles = Reflector.createDecorator(); \ No newline at end of file diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts deleted file mode 100644 index 5402ade..0000000 --- a/src/auth/jwt.strategy.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { PassportStrategy } from '@nestjs/passport'; -import { Injectable, UnauthorizedException } from '@nestjs/common'; - -import { env } from '~/env'; - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor() { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: env.SECRET_KEY, - }); - } - - async validate(payload: Record) { - // const user = await this.prisma.user.findUnique({ - // where: { id: payload.user }, - // select: { - // id: true, - // }, - // }); - - // if (user) return user; - - throw new UnauthorizedException(); - } -} diff --git a/src/auth/refresh.strategy.ts b/src/auth/refresh.strategy.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..f9f0734 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,3 @@ +export const AUTH_COOKIE = 'auth'; +export const ACCESS_TOKEN_EXPIRES_IN = '3d'; +export const REFRESH_TOKEN_EXPIRES_IN = '30d'; diff --git a/src/database/schemas/user.schema.ts b/src/database/schemas/user.schema.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/filters/http-exception.filter.ts b/src/filters/http-exception.filter.ts new file mode 100644 index 0000000..62bea92 --- /dev/null +++ b/src/filters/http-exception.filter.ts @@ -0,0 +1,25 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, +} from '@nestjs/common'; +import { Request, Response } from 'express'; + +@Catch(HttpException) +export class HttpExceptionFilter implements ExceptionFilter { + catch(exception: HttpException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception.getStatus(); + const data = exception.getResponse(); + + response.status(status).json({ + statusCode: status, + timestamp: new Date().toISOString(), + path: request.url, + data, + }); + } +} diff --git a/src/main.ts b/src/main.ts index 9627bff..cc41695 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,23 +1,32 @@ +import * as cookieParser from 'cookie-parser'; + import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { env } from '~/env'; +import { HttpExceptionFilter } from '~/filters/http-exception.filter'; import { AppModule } from '~/app/app.module'; -import 'reflect-metadata'; import { logger } from '~/logger'; +import { AUTH_COOKIE } from '~/constants'; + +import 'reflect-metadata'; (async () => { - const app = await NestFactory.create(AppModule, { }); + const app = await NestFactory.create(AppModule, {}); + + app.use(cookieParser()); app.useGlobalPipes( new ValidationPipe({ transform: true, }), ); + app.useGlobalFilters(new HttpExceptionFilter()); const config = new DocumentBuilder() + .addCookieAuth(AUTH_COOKIE) .setTitle('Morgoth') .setDescription('Docs') .setVersion('1.0') diff --git a/src/schemas/session.schema.ts b/src/schemas/session.schema.ts new file mode 100644 index 0000000..004dc3f --- /dev/null +++ b/src/schemas/session.schema.ts @@ -0,0 +1,23 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import * as mongoose from 'mongoose'; + +import { User } from './user.schema'; + +export type SessionDocument = mongoose.HydratedDocument; + +@Schema() +export class Session { + @Prop({ required: true }) + accessToken: string; + + @Prop({ required: true }) + refreshToken: string; + + @Prop({ required: false, default: false }) + isExpired: boolean; + + @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' }) + user: User; +} + +export const SessionSchema = SchemaFactory.createForClass(Session); diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts new file mode 100644 index 0000000..bdebcae --- /dev/null +++ b/src/schemas/user.schema.ts @@ -0,0 +1,34 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument } from 'mongoose'; +import { hash } from 'bcrypt'; + +import { Role } from '~/types/role.enum'; + +export type UserDocument = HydratedDocument; + +@Schema() +export class User { + @Prop({ required: true }) + name: string; + + @Prop({ required: true, unique: true }) + email: string; + + @Prop({ required: true, select: false }) + password: string; + + @Prop({ enum: Role, default: Role.USER }) + role: Role; +} + +export const UserSchema = SchemaFactory.createForClass(User); + +UserSchema.pre('save', function (next) { + hash(this.password, 10, (err, data) => { + if (err) return next(err); + + this.password = data; + + next(); + }); +}); diff --git a/src/types/role.enum.ts b/src/types/role.enum.ts new file mode 100644 index 0000000..faa426b --- /dev/null +++ b/src/types/role.enum.ts @@ -0,0 +1,4 @@ +export enum Role { + ADMIN = 'ADMIN', + USER = 'USER', +}