From d06087658fb38222bff39c49db6d114432a00fa7 Mon Sep 17 00:00:00 2001 From: Zenfection Date: Thu, 21 Sep 2023 23:36:24 +0700 Subject: [PATCH] Use cookie express store token via Redis --- server/.env | 5 +- server/package.json | 7 +- server/pnpm-lock.yaml | 186 ++++++++++++------ .../authentication/authentication.module.ts | 35 +++- .../session-authentication.controller.spec.ts | 18 ++ .../session-authentication.controller.ts | 37 ++++ .../active-user/active-user.decorator.ts | 2 +- .../guards/session/session.guard.spec.ts | 7 + .../guards/session/session.guard.ts | 12 ++ .../session-authentication.service.spec.ts | 18 ++ .../session-authentication.service.ts | 39 ++++ .../user-serializer/user-serializer.spec.ts | 7 + .../utils/user-serializer/user-serializer.ts | 38 ++++ 13 files changed, 345 insertions(+), 66 deletions(-) create mode 100644 server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.spec.ts create mode 100644 server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.ts create mode 100644 server/src/iam/authentication/guards/session/session.guard.spec.ts create mode 100644 server/src/iam/authentication/guards/session/session.guard.ts create mode 100644 server/src/iam/authentication/services/session-authentication/session-authentication.service.spec.ts create mode 100644 server/src/iam/authentication/services/session-authentication/session-authentication.service.ts create mode 100644 server/src/iam/authentication/utils/user-serializer/user-serializer.spec.ts create mode 100644 server/src/iam/authentication/utils/user-serializer/user-serializer.ts diff --git a/server/.env b/server/.env index 46c5f2d..0c2efa5 100644 --- a/server/.env +++ b/server/.env @@ -14,4 +14,7 @@ JWT_SECRET=secret JWT_AUDIENCE=localhost:3000 JWT_ISSUER=localhost:3000 JWT_ACESSS_TOKEN_TTL=3600 -JWT_REFRESH_TOKEN_TTL=86400 \ No newline at end of file +JWT_REFRESH_TOKEN_TTL=86400\ + +# SESSION SECRET +SESSION_SECRET=secret \ No newline at end of file diff --git a/server/package.json b/server/package.json index 4d75258..d42cbe4 100644 --- a/server/package.json +++ b/server/package.json @@ -31,6 +31,8 @@ "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "connect-redis": "^6.1.3", + "express-session": "^1.17.3", "ioredis": "^5.3.2", "nestjs-prisma": "^0.22.0", "passport": "^0.6.0", @@ -48,9 +50,12 @@ "@swc/core": "^1.3.86", "@swc/jest": "^0.2.29", "@types/bcrypt": "^5.0.0", + "@types/connect-redis": "^0.0.21", "@types/express": "^4.17.17", + "@types/express-session": "^1.17.7", "@types/jest": "^29.5.5", - "@types/node": "^20.6.3", + "@types/node": "^18", + "@types/passport": "^1.0.12", "@types/passport-jwt": "^3.0.9", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.7.2", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index dcc4ffa..2744aa5 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -38,6 +38,12 @@ dependencies: class-validator: specifier: ^0.14.0 version: 0.14.0 + connect-redis: + specifier: ^6.1.3 + version: 6.1.3 + express-session: + specifier: ^1.17.3 + version: 1.17.3 ioredis: specifier: ^5.3.2 version: 5.3.2 @@ -85,15 +91,24 @@ devDependencies: '@types/bcrypt': specifier: ^5.0.0 version: 5.0.0 + '@types/connect-redis': + specifier: ^0.0.21 + version: 0.0.21 '@types/express': specifier: ^4.17.17 version: 4.17.17 + '@types/express-session': + specifier: ^1.17.7 + version: 1.17.7 '@types/jest': specifier: ^29.5.5 version: 29.5.5 '@types/node': - specifier: ^20.6.3 - version: 20.6.3 + specifier: ^18 + version: 18.17.18 + '@types/passport': + specifier: ^1.0.12 + version: 1.0.12 '@types/passport-jwt': specifier: ^3.0.9 version: 3.0.9 @@ -117,7 +132,7 @@ devDependencies: version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.49.0)(prettier@3.0.3) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + version: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) prettier: specifier: ^3.0.3 version: 3.0.3 @@ -138,7 +153,7 @@ devDependencies: version: 9.4.4(typescript@5.2.2)(webpack@5.88.2) ts-node: specifier: ^10.9.1 - version: 10.9.1(@swc/core@1.3.86)(@types/node@20.6.3)(typescript@5.2.2) + version: 10.9.1(@swc/core@1.3.86)(@types/node@18.17.18)(typescript@5.2.2) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -1679,7 +1694,6 @@ packages: /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} - dev: false /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -1714,7 +1728,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -1735,14 +1749,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.8.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -1777,7 +1791,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 jest-mock: 29.7.0 dev: true @@ -1811,7 +1825,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.6.3 + '@types/node': 18.17.18 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -1844,7 +1858,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.19 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -1931,7 +1945,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.6.3 + '@types/node': 18.17.18 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -1943,7 +1957,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.6.3 + '@types/node': 18.17.18 '@types/yargs': 17.0.24 chalk: 4.1.2 dev: true @@ -2591,14 +2605,14 @@ packages: /@types/bcrypt@5.0.0: resolution: {integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.36 - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/cacheable-request@6.0.3: @@ -2606,14 +2620,25 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.2 '@types/keyv': 3.1.4 - '@types/node': 20.6.3 + '@types/node': 18.17.18 '@types/responselike': 1.0.0 dev: true + /@types/connect-redis@0.0.21: + resolution: {integrity: sha512-xIrcRu9Qt5HVXUKVxkUI2m53Pt4WrvstMLSXsdWqeb3v8MeM48O+fb+3Bm0aDfAjIwR81+zV676Iuaw/OYsXyw==} + dependencies: + '@types/express': 4.17.17 + '@types/express-session': 1.17.7 + '@types/redis': 2.8.32 + ioredis: 5.3.2 + transitivePeerDependencies: + - supports-color + dev: true + /@types/connect@3.4.36: resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/cookiejar@2.1.2: @@ -2641,12 +2666,18 @@ packages: /@types/express-serve-static-core@4.17.36: resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 '@types/qs': 6.9.8 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 dev: true + /@types/express-session@1.17.7: + resolution: {integrity: sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==} + dependencies: + '@types/express': 4.17.17 + dev: true + /@types/express@4.17.17: resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} dependencies: @@ -2659,7 +2690,7 @@ packages: /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/http-cache-semantics@4.0.2: @@ -2700,12 +2731,12 @@ packages: /@types/jsonwebtoken@9.0.2: resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/mime@1.3.2: @@ -2716,8 +2747,8 @@ packages: resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} dev: true - /@types/node@20.6.3: - resolution: {integrity: sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==} + /@types/node@18.17.18: + resolution: {integrity: sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==} /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} @@ -2752,10 +2783,16 @@ packages: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true + /@types/redis@2.8.32: + resolution: {integrity: sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==} + dependencies: + '@types/node': 18.17.18 + dev: true + /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/semver@7.5.1: @@ -2766,7 +2803,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/serve-static@1.15.2: @@ -2774,7 +2811,7 @@ packages: dependencies: '@types/http-errors': 2.0.1 '@types/mime': 3.0.1 - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/stack-utils@2.0.1: @@ -2785,7 +2822,7 @@ packages: resolution: {integrity: sha512-LOWgpacIV8GHhrsQU+QMZuomfqXiqzz3ILLkCtKx3Us6AmomFViuzKT9D693QTKgyut2oCytMG8/efOop+DB+w==} dependencies: '@types/cookiejar': 2.1.2 - '@types/node': 20.6.3 + '@types/node': 18.17.18 dev: true /@types/supertest@2.0.12: @@ -3851,7 +3888,6 @@ packages: /cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - dev: false /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} @@ -3947,6 +3983,11 @@ packages: readable-stream: 2.3.8 typedarray: 0.0.6 + /connect-redis@6.1.3: + resolution: {integrity: sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw==} + engines: {node: '>=12'} + dev: false + /connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} @@ -3987,6 +4028,11 @@ packages: /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -4038,7 +4084,7 @@ packages: typescript: 5.2.2 dev: true - /create-jest@29.7.0(@types/node@20.6.3)(ts-node@10.9.1): + /create-jest@29.7.0(@types/node@18.17.18)(ts-node@10.9.1): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -4047,7 +4093,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -4239,7 +4285,6 @@ packages: /denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} - dev: false /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} @@ -4794,6 +4839,22 @@ packages: jest-util: 29.7.0 dev: true + /express-session@1.17.3: + resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} + engines: {node: '>= 0.8.0'} + dependencies: + cookie: 0.4.2 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + on-headers: 1.0.2 + parseurl: 1.3.3 + safe-buffer: 5.2.1 + uid-safe: 2.1.5 + transitivePeerDependencies: + - supports-color + dev: false + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -5582,7 +5643,6 @@ packages: standard-as-callback: 2.1.0 transitivePeerDependencies: - supports-color - dev: false /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} @@ -5810,7 +5870,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -5831,7 +5891,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.6.3)(ts-node@10.9.1): + /jest-cli@29.7.0(@types/node@18.17.18)(ts-node@10.9.1): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -5845,10 +5905,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + create-jest: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -5859,7 +5919,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.6.3)(ts-node@10.9.1): + /jest-config@29.7.0(@types/node@18.17.18)(ts-node@10.9.1): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -5874,7 +5934,7 @@ packages: '@babel/core': 7.22.17 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 babel-jest: 29.7.0(@babel/core@7.22.17) chalk: 4.1.2 ci-info: 3.8.0 @@ -5894,7 +5954,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1(@swc/core@1.3.86)(@types/node@20.6.3)(typescript@5.2.2) + ts-node: 10.9.1(@swc/core@1.3.86)(@types/node@18.17.18)(typescript@5.2.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -5945,7 +6005,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -5961,7 +6021,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 20.6.3 + '@types/node': 18.17.18 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -6037,7 +6097,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 jest-util: 29.7.0 dev: true @@ -6092,7 +6152,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -6123,7 +6183,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -6175,7 +6235,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -6187,7 +6247,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -6212,7 +6272,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.6.3 + '@types/node': 18.17.18 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -6224,7 +6284,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -6233,13 +6293,13 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.6.3 + '@types/node': 18.17.18 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.6.3)(ts-node@10.9.1): + /jest@29.7.0(@types/node@18.17.18)(ts-node@10.9.1): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -6252,7 +6312,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.1) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + jest-cli: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -6419,11 +6479,9 @@ packages: /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - dev: false /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - dev: false /lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -6902,7 +6960,6 @@ packages: /on-headers@1.0.2: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} engines: {node: '>= 0.8'} - dev: true /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -7348,6 +7405,11 @@ packages: through2: 2.0.5 dev: true + /random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -7423,14 +7485,12 @@ packages: /redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} - dev: false /redis-parser@3.0.0: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} dependencies: redis-errors: 1.2.0 - dev: false /reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -7870,7 +7930,6 @@ packages: /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - dev: false /static-eval@2.1.0: resolution: {integrity: sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==} @@ -8251,7 +8310,7 @@ packages: '@babel/core': 7.22.17 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.6.3)(ts-node@10.9.1) + jest: 29.7.0(@types/node@18.17.18)(ts-node@10.9.1) jest-util: 29.6.3 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -8283,7 +8342,7 @@ packages: code-block-writer: 12.0.0 dev: true - /ts-node@10.9.1(@swc/core@1.3.86)(@types/node@20.6.3)(typescript@5.2.2): + /ts-node@10.9.1(@swc/core@1.3.86)(@types/node@18.17.18)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -8303,7 +8362,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.6.3 + '@types/node': 18.17.18 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -8407,6 +8466,13 @@ packages: dev: true optional: true + /uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + dependencies: + random-bytes: 1.0.0 + dev: false + /uid@2.0.2: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} diff --git a/server/src/iam/authentication/authentication.module.ts b/server/src/iam/authentication/authentication.module.ts index 4ba6b4c..708c592 100644 --- a/server/src/iam/authentication/authentication.module.ts +++ b/server/src/iam/authentication/authentication.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { HashingService } from '../hashing/hashing.service'; import { BcryptService } from '../hashing/bcrypt/bcrypt.service'; import { PrismaService } from 'nestjs-prisma'; @@ -13,6 +13,14 @@ import { AccessTokenGuard } from './guards/access-token/access-token.guard'; import { AuthenticationGuard } from './guards/authentication/authentication.guard'; import { APP_GUARD } from '@nestjs/core'; import { RefreshTokenIdsStorage } from './utils/refresh-token-ids.storage/refresh-token-ids.storage'; +import { SessionAuthenticationService } from './services/session-authentication/session-authentication.service'; +import { SessionAuthenticationController } from './controllers/session-authentication/session-authentication.controller'; + +import passport from 'passport'; +import session from 'express-session'; +import Redis from 'ioredis'; +import createRedisStore from 'connect-redis'; +import { UserSerializer } from './utils/user-serializer/user-serializer'; @Module({ providers: [ @@ -29,12 +37,33 @@ import { RefreshTokenIdsStorage } from './utils/refresh-token-ids.storage/refres PrismaService, AccessTokenStrategy, AccessTokenGuard, + SessionAuthenticationService, + UserSerializer, ], imports: [ PassportModule, ConfigModule.forFeature(jwtConfig), JwtModule.registerAsync(jwtConfig.asProvider()), ], - controllers: [AuthenticationController], + controllers: [AuthenticationController, SessionAuthenticationController], }) -export class AuthenticationModule {} +export class AuthenticationModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + const RedisStore = createRedisStore(session); + consumer + .apply( + session({ + store: new RedisStore({ + client: new Redis(process.env.REDIS_URL), + }), + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + cookie: { sameSite: true, httpOnly: true }, + }), + passport.initialize(), + passport.session(), + ) + .forRoutes('*'); + } +} diff --git a/server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.spec.ts b/server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.spec.ts new file mode 100644 index 0000000..5a55450 --- /dev/null +++ b/server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SessionAuthenticationController } from './session-authentication.controller'; + +describe('SessionAuthenticationController', () => { + let controller: SessionAuthenticationController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SessionAuthenticationController], + }).compile(); + + controller = module.get(SessionAuthenticationController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.ts b/server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.ts new file mode 100644 index 0000000..072bd74 --- /dev/null +++ b/server/src/iam/authentication/controllers/session-authentication/session-authentication.controller.ts @@ -0,0 +1,37 @@ +import { + Body, + Controller, + Get, + HttpCode, + HttpStatus, + Post, + Req, +} from '@nestjs/common'; +import { Request } from 'express'; +import { promisify } from 'util'; +import { AuthType } from '../../enums/auth-type.enum'; +import { Auth } from '../../decorators/auth/auth.decorator'; +import { SessionAuthenticationService } from '../../services/session-authentication/session-authentication.service'; +import { SignInDto } from '../../dto/sign-in.dto/sign-in.dto'; +import { ActiveUser } from '../../decorators/active-user/active-user.decorator'; +import { ActiveUserData } from '../../interfaces/active-user-data.interface'; + +@Auth(AuthType.None) +@Controller('session-authentication') +export class SessionAuthenticationController { + constructor( + private readonly sessionAuthService: SessionAuthenticationService, + ) {} + + @HttpCode(HttpStatus.OK) + @Post('sign-in') + async signIn(@Req() request: Request, @Body() signInDto: SignInDto) { + const user = await this.sessionAuthService.signIn(signInDto); + await promisify(request.logIn.bind(request))(user); + } + + @Get('test') + async sayHello(@ActiveUser() user: ActiveUserData) { + return `Hello ${user.email}`; + } +} diff --git a/server/src/iam/authentication/decorators/active-user/active-user.decorator.ts b/server/src/iam/authentication/decorators/active-user/active-user.decorator.ts index 368a285..a5529ad 100644 --- a/server/src/iam/authentication/decorators/active-user/active-user.decorator.ts +++ b/server/src/iam/authentication/decorators/active-user/active-user.decorator.ts @@ -1,6 +1,6 @@ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; import { ActiveUserData } from '../../interfaces/active-user-data.interface'; -import { REQUEST_USER_KEY } from 'src/iam/constants/iam.contant'; +import { REQUEST_USER_KEY } from '../../../constants/iam.contant'; export const ActiveUser = createParamDecorator( (field: keyof ActiveUserData | undefined, ctx: ExecutionContext) => { diff --git a/server/src/iam/authentication/guards/session/session.guard.spec.ts b/server/src/iam/authentication/guards/session/session.guard.spec.ts new file mode 100644 index 0000000..b29e989 --- /dev/null +++ b/server/src/iam/authentication/guards/session/session.guard.spec.ts @@ -0,0 +1,7 @@ +import { SessionGuard } from './session.guard'; + +describe('SessionGuard', () => { + it('should be defined', () => { + expect(new SessionGuard()).toBeDefined(); + }); +}); diff --git a/server/src/iam/authentication/guards/session/session.guard.ts b/server/src/iam/authentication/guards/session/session.guard.ts new file mode 100644 index 0000000..2973032 --- /dev/null +++ b/server/src/iam/authentication/guards/session/session.guard.ts @@ -0,0 +1,12 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable() +export class SessionGuard implements CanActivate { + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const request = context.switchToHttp().getRequest(); + return request.isAuthenticated(); + } +} diff --git a/server/src/iam/authentication/services/session-authentication/session-authentication.service.spec.ts b/server/src/iam/authentication/services/session-authentication/session-authentication.service.spec.ts new file mode 100644 index 0000000..9490f78 --- /dev/null +++ b/server/src/iam/authentication/services/session-authentication/session-authentication.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SessionAuthenticationService } from './session-authentication.service'; + +describe('SessionAuthenticationService', () => { + let service: SessionAuthenticationService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SessionAuthenticationService], + }).compile(); + + service = module.get(SessionAuthenticationService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/iam/authentication/services/session-authentication/session-authentication.service.ts b/server/src/iam/authentication/services/session-authentication/session-authentication.service.ts new file mode 100644 index 0000000..afa3244 --- /dev/null +++ b/server/src/iam/authentication/services/session-authentication/session-authentication.service.ts @@ -0,0 +1,39 @@ +import { + ConflictException, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { User } from '@prisma/client'; +import { PrismaService } from 'nestjs-prisma'; +import { SignInDto } from '../../dto/sign-in.dto/sign-in.dto'; +import { HashingService } from '../../../hashing/hashing.service'; + +@Injectable() +export class SessionAuthenticationService { + constructor( + private readonly hashService: HashingService, + private prismaSerive: PrismaService, + ) {} + + private async checkExist(email: string): Promise { + return await this.prismaSerive.user.findUnique({ + where: { + email, + }, + }); + } + + async signIn(signInDto: SignInDto) { + const user = await this.checkExist(signInDto.email); + if (!user) throw new ConflictException('Email not exists'); + + const isMatch = await this.hashService.compare( + signInDto.password, + user.password, + ); + + if (!isMatch) throw new UnauthorizedException('User or password not match'); + + return user; + } +} diff --git a/server/src/iam/authentication/utils/user-serializer/user-serializer.spec.ts b/server/src/iam/authentication/utils/user-serializer/user-serializer.spec.ts new file mode 100644 index 0000000..77a7a47 --- /dev/null +++ b/server/src/iam/authentication/utils/user-serializer/user-serializer.spec.ts @@ -0,0 +1,7 @@ +import { UserSerializer } from './user-serializer'; + +describe('UserSerializer', () => { + it('should be defined', () => { + expect(new UserSerializer()).toBeDefined(); + }); +}); diff --git a/server/src/iam/authentication/utils/user-serializer/user-serializer.ts b/server/src/iam/authentication/utils/user-serializer/user-serializer.ts new file mode 100644 index 0000000..2157f21 --- /dev/null +++ b/server/src/iam/authentication/utils/user-serializer/user-serializer.ts @@ -0,0 +1,38 @@ +import { PassportSerializer } from '@nestjs/passport'; +import { User } from '@prisma/client'; +import passport from 'passport'; +import { ActiveUserData } from '../../interfaces/active-user-data.interface'; + +export class UserSerializer implements PassportSerializer { + constructor() { + const passportInstance = this.getPassportInstance(); + passportInstance.serializeUser((user, done) => + this.serializeUser(user as User, done), + ); + passportInstance.deserializeUser((payload, done) => + this.deserializeUser(payload as ActiveUserData, done), + ); + } + + getPassportInstance() { + return passport; + } + + serializeUser(user: User, done: (err: Error, user: ActiveUserData) => void) { + // store user info authenticated in session + done(null, { + sub: user.id, + email: user.email, + // role: user.role, + // permissions: user.permissions as any, + }); + } + + async deserializeUser( + payload: ActiveUserData, + done: (err: Error, payload: ActiveUserData) => void, + ) { + // retrieve user info authenticated from session + done(null, payload); + } +}