diff --git a/client/src/features/page/hooks/usePage.ts b/client/src/features/page/hooks/usePage.ts index 2ec215b0..618edd2c 100644 --- a/client/src/features/page/hooks/usePage.ts +++ b/client/src/features/page/hooks/usePage.ts @@ -1,8 +1,8 @@ -import { useIsSidebarOpen } from "@src/stores/useSidebarStore"; -import { Position, Size } from "@src/types/page"; import { useEffect, useState } from "react"; import { PAGE, SIDE_BAR } from "@constants/size"; import { SPACING } from "@constants/spacing"; +import { useIsSidebarOpen } from "@src/stores/useSidebarStore"; +import { Position, Size } from "@src/types/page"; const PADDING = SPACING.MEDIUM * 2; diff --git a/package.json b/package.json index 01ce7c5f..3affd83e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint": "eslint . --fix", "lint:client": "eslint \"client/src/**/*.{ts,tsx}\" --fix", "lint:server": "eslint \"server/src/**/*.{ts,tsx}\" --fix", - "build": "cd @noctaCrdt && pnpm build && cd .. && pnpm -r build", + "build": "pnpm build:lib && pnpm -r build", "build:lib": "cd @noctaCrdt && pnpm build", "build:client": "cd client && pnpm build", "build:server": "cd server && pnpm build", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 190afeb2..3a27edb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,9 +140,15 @@ importers: '@nestjs/core': specifier: ^10.0.0 version: 10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.6)(@nestjs/websockets@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1)) '@nestjs/mongoose': specifier: ^10.1.0 version: 10.1.0(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)(mongoose@8.8.0(socks@2.8.3))(rxjs@7.8.1) + '@nestjs/passport': + specifier: ^10.0.3 + version: 10.0.3(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6) @@ -155,12 +161,24 @@ importers: '@noctaCrdt': specifier: workspace:* version: link:../@noctaCrdt + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 mongodb-memory-server: specifier: ^10.1.2 version: 10.1.2(socks@2.8.3) mongoose: specifier: ^8.8.0 version: 8.8.0(socks@2.8.3) + nanoid: + specifier: ^5.0.8 + version: 5.0.8 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 reflect-metadata: specifier: ^0.2.0 version: 0.2.2 @@ -876,6 +894,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 + '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} @@ -928,6 +950,11 @@ packages: '@nestjs/websockets': optional: true + '@nestjs/jwt@10.2.0': + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/mongoose@10.1.0': resolution: {integrity: sha512-1ExAnZUfh2QffEaGjqYGgVPy/sYBQCVLCLqVgkcClKx/BCd0QNgND8MB70lwyobp3nm/+nbGQqBpu9F3/hgOCw==} peerDependencies: @@ -936,6 +963,12 @@ packages: mongoose: ^6.0.2 || ^7.0.0 || ^8.0.0 rxjs: ^7.0.0 + '@nestjs/passport@10.0.3': + resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@nestjs/platform-express@10.4.6': resolution: {integrity: sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg==} peerDependencies: @@ -1303,6 +1336,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.5': + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -1523,6 +1559,9 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + 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'} @@ -1546,6 +1585,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'} + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} @@ -1604,6 +1647,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==} @@ -1730,6 +1781,10 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + 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'} @@ -1784,6 +1839,9 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1846,6 +1904,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'} @@ -1902,6 +1964,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1939,6 +2005,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'} @@ -2089,6 +2158,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'} @@ -2102,6 +2174,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + 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'} @@ -2140,6 +2216,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2590,6 +2669,10 @@ packages: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} + 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==} @@ -2611,6 +2694,11 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + 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'} @@ -2712,6 +2800,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 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'} @@ -2730,6 +2821,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'} + https-proxy-agent@7.0.5: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} @@ -3161,10 +3256,20 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + kareem@2.6.3: resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==} engines: {node: '>=12.0.0'} @@ -3330,12 +3435,33 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -3459,14 +3585,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 + mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} @@ -3582,6 +3725,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.8: + resolution: {integrity: sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -3599,6 +3747,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==} @@ -3621,6 +3772,11 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + 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'} @@ -3629,6 +3785,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + 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'} @@ -3733,6 +3893,17 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -3768,6 +3939,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -4108,6 +4282,9 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} 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'} @@ -4316,6 +4493,10 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + 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'} @@ -4689,6 +4870,9 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4739,6 +4923,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -5457,6 +5644,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.3 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + '@mongodb-js/saslprep@1.1.9': dependencies: sparse-bitfield: 3.0.3 @@ -5520,6 +5722,12 @@ snapshots: transitivePeerDependencies: - encoding + '@nestjs/jwt@10.2.0(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))': + dependencies: + '@nestjs/common': 10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + '@nestjs/mongoose@10.1.0(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)(mongoose@8.8.0(socks@2.8.3))(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -5527,6 +5735,11 @@ snapshots: mongoose: 8.8.0(socks@2.8.3) rxjs: 7.8.1 + '@nestjs/passport@10.0.3(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': + dependencies: + '@nestjs/common': 10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1) + passport: 0.7.0 + '@nestjs/platform-express@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)': dependencies: '@nestjs/common': 10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -6115,6 +6328,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.5': + dependencies: + '@types/node': 20.17.6 + '@types/methods@1.1.4': {} '@types/mime@1.3.5': {} @@ -6431,6 +6648,8 @@ snapshots: '@xtuc/long@4.2.2': {} + abbrev@1.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -6450,6 +6669,12 @@ snapshots: acorn@8.14.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + agent-base@7.1.1: dependencies: debug: 4.3.7 @@ -6503,6 +6728,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: @@ -6674,6 +6906,14 @@ snapshots: base64id@2.0.0: {} + 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: @@ -6747,6 +6987,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -6813,6 +7055,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@2.0.0: {} + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} @@ -6855,6 +7099,8 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -6890,6 +7136,8 @@ snapshots: consola@2.15.3: {} + console-control-strings@1.1.0: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -7021,12 +7269,16 @@ snapshots: delayed-stream@1.0.0: {} + delegates@1.0.0: {} + depd@2.0.0: {} destroy@1.2.0: {} detect-libc@1.0.3: {} + detect-libc@2.0.3: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -7056,6 +7308,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} ejs@3.1.10: @@ -7735,6 +7991,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: {} @@ -7753,6 +8013,18 @@ snapshots: functions-have-names@1.2.3: {} + 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: {} @@ -7855,6 +8127,8 @@ snapshots: dependencies: has-symbols: 1.0.3 + has-unicode@2.0.1: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -7873,6 +8147,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.7 + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 @@ -8500,6 +8781,19 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -8507,6 +8801,17 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + kareem@2.6.3: {} keyv@4.5.4: @@ -8631,10 +8936,24 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.uniq@4.5.0: {} lodash@4.17.21: {} @@ -8739,12 +9058,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: {} + mkdirp@3.0.1: {} mlly@1.7.2: @@ -8901,6 +9233,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.8: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -8915,6 +9249,8 @@ snapshots: node-abort-controller@3.1.1: {} + node-addon-api@5.1.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.17.21 @@ -8931,12 +9267,23 @@ snapshots: node-releases@2.0.18: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + normalize-path@3.0.0: {} npm-run-path@4.0.1: dependencies: path-key: 3.1.1 + 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-hash@3.0.0: {} @@ -9051,6 +9398,19 @@ snapshots: parseurl@1.3.3: {} + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -9074,6 +9434,8 @@ snapshots: pathe@1.1.2: {} + pause@0.0.1: {} + pend@1.2.0: {} perfect-debounce@1.0.0: {} @@ -9457,6 +9819,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -9732,6 +10096,15 @@ snapshots: fast-fifo: 1.3.2 streamx: 2.20.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.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -10123,6 +10496,10 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -10160,6 +10537,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/server/jest.config.ts b/server/jest.config.ts index cb3fd39c..5f2f1e54 100644 --- a/server/jest.config.ts +++ b/server/jest.config.ts @@ -5,13 +5,19 @@ const config: Config = { rootDir: ".", testRegex: ".*\\.spec\\.ts$", transform: { - "^.+\\.(t|j)s$": "ts-jest", + "^.+\\.(t|j)s$": ["ts-jest", { useESM: true }], }, collectCoverageFrom: ["**/*.(t|j)s"], coverageDirectory: "./coverage", testEnvironment: "node", preset: "@shelf/jest-mongodb", watchPathIgnorePatterns: ["globalConfig"], + transformIgnorePatterns: ["node_modules/(?!(nanoid)/)"], + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + "^nanoid$": require.resolve("nanoid"), + }, }; export default config; diff --git a/server/package.json b/server/package.json index a07d4600..8d01efe5 100644 --- a/server/package.json +++ b/server/package.json @@ -25,13 +25,19 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/mongoose": "^10.1.0", + "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.4.7", "@nestjs/websockets": "^10.4.7", "@noctaCrdt": "workspace:*", + "bcrypt": "^5.1.1", "mongodb-memory-server": "^10.1.2", "mongoose": "^8.8.0", + "nanoid": "^5.0.8", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "socket.io": "^4.8.1" diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 1a662a69..2fde69fa 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -3,6 +3,7 @@ import { AppController } from "./app.controller"; import { AppService } from "./app.service"; import { ConfigModule, ConfigService } from "@nestjs/config"; import { MongooseModule } from "@nestjs/mongoose"; +import { AuthModule } from './auth/auth.module'; @Module({ imports: [ @@ -19,6 +20,7 @@ import { MongooseModule } from "@nestjs/mongoose"; uri: configService.get("MONGO_URI"), // 환경 변수에서 MongoDB URI 가져오기 }), }), + AuthModule, ], controllers: [AppController], providers: [AppService], diff --git a/server/src/auth/auth.controller.spec.ts b/server/src/auth/auth.controller.spec.ts new file mode 100644 index 00000000..d32087b9 --- /dev/null +++ b/server/src/auth/auth.controller.spec.ts @@ -0,0 +1,52 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { AuthController } from "./auth.controller"; +import { AuthService } from "./auth.service"; + +jest.mock("nanoid", () => ({ + nanoid: jest.fn(() => "mockNanoId123"), +})); + +describe("AuthController", () => { + let authController: AuthController; + let authService: AuthService; + + const mockAuthService = { + register: jest.fn(), + login: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AuthController], + providers: [{ provide: AuthService, useValue: mockAuthService }], + }).compile(); + + authController = module.get(AuthController); + authService = module.get(AuthService); + }); + + it("should be defined", () => { + expect(authController).toBeDefined(); + }); + + describe("register", () => { + it("should call authService.register and return the result", async () => { + const dto = { + email: "test@example.com", + password: "password123", + name: "Test User", + }; + const mockResult = { + id: "mockNanoId123", + email: "test@example.com", + name: "Test User", + }; + mockAuthService.register.mockResolvedValue(mockResult); + + const result = await authController.register(dto); + + expect(authService.register).toHaveBeenCalledWith(dto.email, dto.password, dto.name); + expect(result).toEqual(mockResult); + }); + }); +}); diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts new file mode 100644 index 00000000..cb10d2e1 --- /dev/null +++ b/server/src/auth/auth.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Post, Body, Request, UseGuards } from "@nestjs/common"; +import { AuthService } from "./auth.service"; +import { JwtAuthGuard } from "./jwt-auth.guard"; + +@Controller("auth") +export class AuthController { + constructor(private authService: AuthService) {} + + @Post("register") + async register(@Body() body: { email: string; password: string; name: string }) { + const { email, password, name } = body; + return this.authService.register(email, password, name); + } + + @Post("login") + async login(@Body() body: { email: string; password: string }) { + const user = await this.authService.validateUser(body.email, body.password); + if (!user) { + throw new Error("Invalid credentials"); + } + return this.authService.login(user); + } + + @UseGuards(JwtAuthGuard) + @Post("profile") + getProfile(@Request() req) { + return req.user; + } +} diff --git a/server/src/auth/auth.module.ts b/server/src/auth/auth.module.ts new file mode 100644 index 00000000..5aeb45a1 --- /dev/null +++ b/server/src/auth/auth.module.ts @@ -0,0 +1,22 @@ +import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; +import { User, UserSchema } from "./schemas/user.schema"; +import { AuthService } from "./auth.service"; +import { AuthController } from "./auth.controller"; +import { JwtModule } from "@nestjs/jwt"; +import { PassportModule } from "@nestjs/passport"; +import { JwtStrategy } from "./jwt.strategy"; + +@Module({ + imports: [ + MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), + PassportModule, + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: "1h" }, + }), + ], + providers: [AuthService, JwtStrategy], + controllers: [AuthController], +}) +export class AuthModule {} diff --git a/server/src/auth/auth.service.spec.ts b/server/src/auth/auth.service.spec.ts new file mode 100644 index 00000000..8833273f --- /dev/null +++ b/server/src/auth/auth.service.spec.ts @@ -0,0 +1,119 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { AuthService } from "./auth.service"; +import { getModelToken } from "@nestjs/mongoose"; +import { JwtService } from "@nestjs/jwt"; +import * as bcrypt from "bcrypt"; +import { nanoid } from "nanoid"; + +jest.mock("nanoid", () => ({ + nanoid: jest.fn(() => "mockNanoId123"), +})); + +jest.mock("bcrypt", () => ({ + hash: jest.fn(() => Promise.resolve("hashedPassword123")), + compare: jest.fn(() => Promise.resolve(true)), +})); + +describe("AuthService", () => { + let authService: AuthService; + let userModel: any; + let jwtService: JwtService; + + const mockUserModel = { + create: jest.fn(), + findOne: jest.fn(), + }; + + const mockJwtService = { + sign: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AuthService, + { provide: getModelToken("User"), useValue: mockUserModel }, + { provide: JwtService, useValue: mockJwtService }, + ], + }).compile(); + + authService = module.get(AuthService); + userModel = module.get(getModelToken("User")); + jwtService = module.get(JwtService); + }); + + it("should be defined", () => { + expect(authService).toBeDefined(); + }); + + describe("register", () => { + it("should create a new user with a nanoid as id", async () => { + // nanoid mock 설정 + const mockId = "mockNanoId123"; + (nanoid as jest.Mock).mockReturnValue(mockId); + + const hashedPassword = "hashedPassword123"; + jest.spyOn(bcrypt, "hash").mockResolvedValue(hashedPassword); + + const mockUser = { + id: mockId, + email: "test@example.com", + password: hashedPassword, + name: "Test User", + }; + + userModel.create.mockResolvedValue(mockUser); + + const result = await authService.register("test@example.com", "password123", "Test User"); + + expect(nanoid).toHaveBeenCalled(); // nanoid 호출 확인 + expect(userModel.create).toHaveBeenCalledWith({ + id: mockId, + email: "test@example.com", + password: hashedPassword, + name: "Test User", + }); + expect(result).toEqual(mockUser); + }); + }); + + describe("validateUser", () => { + it("should return the user if credentials are valid", async () => { + const mockUser = { + id: "mockNanoId123", + email: "test@example.com", + password: "hashedPassword123", + }; + userModel.findOne.mockResolvedValue(mockUser); + jest.spyOn(bcrypt, "compare").mockResolvedValue(true); + + const result = await authService.validateUser("test@example.com", "password123"); + + expect(userModel.findOne).toHaveBeenCalledWith({ email: "test@example.com" }); + expect(result).toEqual(mockUser); + }); + + it("should return null if credentials are invalid", async () => { + userModel.findOne.mockResolvedValue(null); + + const result = await authService.validateUser("test@example.com", "password123"); + + expect(result).toBeNull(); + }); + }); + + describe("login", () => { + it("should return a JWT token", async () => { + const mockUser = { id: "mockNanoId123", email: "test@example.com" }; + mockJwtService.sign.mockReturnValue("mockJwtToken"); + + const result = await authService.login(mockUser); + + expect(jwtService.sign).toHaveBeenCalledWith({ + sub: mockUser.id, + email: mockUser.email, + }); + expect(result).toEqual({ accessToken: "mockJwtToken" }); + }); + }); +}); diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts new file mode 100644 index 00000000..a2bb3aee --- /dev/null +++ b/server/src/auth/auth.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { Model } from "mongoose"; +import { User, UserDocument } from "./schemas/user.schema"; +import * as bcrypt from "bcrypt"; +import { JwtService } from "@nestjs/jwt"; + +@Injectable() +export class AuthService { + constructor( + @InjectModel(User.name) private userModel: Model, + private jwtService: JwtService, + ) {} + + async register(email: string, password: string, name: string): Promise { + const hashedPassword = await bcrypt.hash(password, 10); + const newUser = new this.userModel({ + email, + password: hashedPassword, + name, + }); + return newUser.save(); + } + + async validateUser(email: string, password: string): Promise { + const user = await this.userModel.findOne({ email }); + if (user && (await bcrypt.compare(password, user.password))) { + return user; + } + return null; + } + + async login(user: { id: string; email: string }) { + const payload = { sub: user.id, email: user.email }; + return { + accessToken: this.jwtService.sign(payload), + }; + } +} diff --git a/server/src/auth/jwt-auth.guard.ts b/server/src/auth/jwt-auth.guard.ts new file mode 100644 index 00000000..2e81dba6 --- /dev/null +++ b/server/src/auth/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; + +@Injectable() +export class JwtAuthGuard extends AuthGuard("jwt") {} diff --git a/server/src/auth/jwt.strategy.ts b/server/src/auth/jwt.strategy.ts new file mode 100644 index 00000000..ad324ab5 --- /dev/null +++ b/server/src/auth/jwt.strategy.ts @@ -0,0 +1,18 @@ +import { Injectable } from "@nestjs/common"; +import { PassportStrategy } from "@nestjs/passport"; +import { ExtractJwt, Strategy } from "passport-jwt"; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: process.env.JWT_SECRET, + }); + } + + async validate(payload: any) { + return { userId: payload.sub, email: payload.email }; + } +} diff --git a/server/src/auth/schemas/user.schema.ts b/server/src/auth/schemas/user.schema.ts new file mode 100644 index 00000000..97e490d6 --- /dev/null +++ b/server/src/auth/schemas/user.schema.ts @@ -0,0 +1,22 @@ +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; +import { Document } from "mongoose"; +import { nanoid } from "nanoid"; + +export type UserDocument = User & Document; + +@Schema() +export class User { + @Prop({ required: true, unique: true, default: () => nanoid() }) + id: string; + + @Prop({ required: true, unique: true }) + email: string; + + @Prop({ required: true }) + password: string; + + @Prop({ required: true }) + name: string; +} + +export const UserSchema = SchemaFactory.createForClass(User); diff --git a/server/tsconfig.json b/server/tsconfig.json index 5b93a0ad..8770f99f 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -3,7 +3,7 @@ "references": [{ "path": "../@noctaCrdt" }], "compilerOptions": { // 기본 설정 - "module": "commonjs", + "module": "ESNext", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true,