Repository: awesome-academy/nodejs2025_training-management
Pull request #2: feat(auth): implement user registration and login with session handling
Author: login: nthanhminh
Created: 2025-05-20T09:40:46Z | Merged: 2025-05-21T04:08:26Z

## Related Tickets
- Ticket: [[Redmine](https://edu-redmine.sun-asterisk.vn/issues/87421)]

## WHAT (Optional)
- implement user registration and login with session handling 
## HOW
- Create models, controllers, and routes for user authentication.

- Configure session handling to manage user login states.

- Organize the project directory with a modular structure for better maintainability.

- Connect components such as the User model, authentication logic, and session management through controllers.

## WHY (Optional)

## Evidence (Screenshot or Video)
![image](https://github.com/user-attachments/assets/f70f628c-a9e4-42ab-a5c1-d8760134bb62)

## Notes (Kiến thức tìm hiểu thêm)
> _Ghi chú kiến thức liên quan nếu có._
> - Session
> - Cookies
> - Passport



Diff:
diff --git a/package-lock.json b/package-lock.json
index 21ea8b9..c407521 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -236,13 +236,14 @@
       }
     },
     "node_modules/@babel/code-frame": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz",
-      "integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==",
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+      "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
       "dev": true,
       "dependencies": {
-        "@babel/highlight": "^7.25.9",
-        "picocolors": "^1.0.0"
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -390,18 +391,18 @@
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
-      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
       "devOptional": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
-      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
       "devOptional": true,
       "engines": {
         "node": ">=6.9.0"
@@ -417,111 +418,25 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.9.tgz",
-      "integrity": "sha512-oKWp3+usOJSzDZOucZUAMayhPz/xVjzymyDzUN8dk0Wd3RWMlGLXi07UCQ/CgQVb8LvXx3XBajJH4XGgkt7H7g==",
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
+      "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
       "dev": true,
       "dependencies": {
-        "@babel/template": "^7.25.9",
-        "@babel/types": "^7.25.9"
+        "@babel/template": "^7.27.1",
+        "@babel/types": "^7.27.1"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
-    "node_modules/@babel/highlight": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz",
-      "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-validator-identifier": "^7.25.9",
-        "chalk": "^2.4.2",
-        "js-tokens": "^4.0.0",
-        "picocolors": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/highlight/node_modules/ansi-styles": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^1.9.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/highlight/node_modules/chalk": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^3.2.1",
-        "escape-string-regexp": "^1.0.5",
-        "supports-color": "^5.3.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/highlight/node_modules/color-convert": {
-      "version": "1.9.3",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "1.1.3"
-      }
-    },
-    "node_modules/@babel/highlight/node_modules/color-name": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-      "dev": true
-    },
-    "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8.0"
-      }
-    },
-    "node_modules/@babel/highlight/node_modules/has-flag": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/highlight/node_modules/supports-color": {
-      "version": "5.5.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/@babel/parser": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz",
-      "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+      "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
       "devOptional": true,
       "dependencies": {
-        "@babel/types": "^7.25.9"
+        "@babel/types": "^7.27.1"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -753,26 +668,23 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
-      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
+      "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
       "optional": true,
-      "dependencies": {
-        "regenerator-runtime": "^0.14.0"
-      },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
-      "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+      "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
       "dev": true,
       "dependencies": {
-        "@babel/code-frame": "^7.25.9",
-        "@babel/parser": "^7.25.9",
-        "@babel/types": "^7.25.9"
+        "@babel/code-frame": "^7.27.1",
+        "@babel/parser": "^7.27.2",
+        "@babel/types": "^7.27.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -806,13 +718,13 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz",
-      "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==",
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+      "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
       "devOptional": true,
       "dependencies": {
-        "@babel/helper-string-parser": "^7.25.9",
-        "@babel/helper-validator-identifier": "^7.25.9"
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -2006,12 +1918,13 @@
       }
     },
     "node_modules/@nestjs/common": {
-      "version": "10.4.6",
-      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.6.tgz",
-      "integrity": "sha512-KkezkZvU9poWaNq4L+lNvx+386hpOxPJkfXBBeSMrcqBOx8kVr36TGN2uYkF4Ta4zNu1KbCjmZbc0rhHSg296g==",
+      "version": "10.4.17",
+      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.17.tgz",
+      "integrity": "sha512-NKzPA4Tb35XjlxizsT8KZb3CCX8tNKj5EnsXsTl/gZX//uAWccBsqB8BjU69x/u4/kQ0106/Kt6PP+yrHAez0w==",
       "dependencies": {
+        "file-type": "20.4.1",
         "iterare": "1.2.1",
-        "tslib": "2.7.0",
+        "tslib": "2.8.1",
         "uid": "2.0.2"
       },
       "funding": {
@@ -2033,6 +1946,11 @@
         }
       }
     },
+    "node_modules/@nestjs/common/node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+    },
     "node_modules/@nestjs/config": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.3.0.tgz",
@@ -2125,15 +2043,15 @@
       }
     },
     "node_modules/@nestjs/platform-express": {
-      "version": "10.4.6",
-      "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.6.tgz",
-      "integrity": "sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg==",
+      "version": "10.4.17",
+      "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.17.tgz",
+      "integrity": "sha512-ovn4Wxney3QGBrqNPv0QLcCuH5QoAi6pb/GNWAz6B/NmBjZbs9/zl4a2beGDA2SaYre9w43YbfmHTm17PneP9w==",
       "dependencies": {
         "body-parser": "1.20.3",
         "cors": "2.8.5",
-        "express": "4.21.1",
+        "express": "4.21.2",
         "multer": "1.4.4-lts.1",
-        "tslib": "2.7.0"
+        "tslib": "2.8.1"
       },
       "funding": {
         "type": "opencollective",
@@ -2144,6 +2062,11 @@
         "@nestjs/core": "^10.0.0"
       }
     },
+    "node_modules/@nestjs/platform-express/node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+    },
     "node_modules/@nestjs/schematics": {
       "version": "10.2.2",
       "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.2.tgz",
@@ -2319,6 +2242,18 @@
         "typeorm": "^0.3.0"
       }
     },
+    "node_modules/@noble/hashes": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+      "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+      "dev": true,
+      "engines": {
+        "node": "^14.21.3 || >=16"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2377,6 +2312,15 @@
       "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
       "optional": true
     },
+    "node_modules/@paralleldrive/cuid2": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
+      "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
+      "dev": true,
+      "dependencies": {
+        "@noble/hashes": "^1.1.5"
+      }
+    },
     "node_modules/@phc/format": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
@@ -2448,6 +2392,44 @@
       "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
       "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="
     },
+    "node_modules/@tokenizer/inflate": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
+      "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
+      "dependencies": {
+        "debug": "^4.4.0",
+        "fflate": "^0.8.2",
+        "token-types": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Borewit"
+      }
+    },
+    "node_modules/@tokenizer/inflate/node_modules/debug": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@tokenizer/token": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
+      "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
+    },
     "node_modules/@tsconfig/node10": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -4620,9 +4602,9 @@
       }
     },
     "node_modules/cross-spawn": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
       "dependencies": {
         "path-key": "^3.1.0",
         "shebang-command": "^2.0.0",
@@ -5483,9 +5465,9 @@
       }
     },
     "node_modules/express": {
-      "version": "4.21.1",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
-      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+      "version": "4.21.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+      "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
@@ -5506,7 +5488,7 @@
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.10",
+        "path-to-regexp": "0.1.12",
         "proxy-addr": "~2.0.7",
         "qs": "6.13.0",
         "range-parser": "~1.2.1",
@@ -5521,6 +5503,10 @@
       },
       "engines": {
         "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
       }
     },
     "node_modules/express-session": {
@@ -5581,9 +5567,9 @@
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
     },
     "node_modules/express/node_modules/path-to-regexp": {
-      "version": "0.1.10",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
-      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
     },
     "node_modules/extend-object": {
       "version": "1.0.0",
@@ -5674,6 +5660,11 @@
         "bser": "2.1.1"
       }
     },
+    "node_modules/fflate": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+    },
     "node_modules/figures": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -5710,6 +5701,23 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
+    "node_modules/file-type": {
+      "version": "20.4.1",
+      "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz",
+      "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==",
+      "dependencies": {
+        "@tokenizer/inflate": "^0.2.6",
+        "strtok3": "^10.2.0",
+        "token-types": "^6.0.0",
+        "uint8array-extras": "^1.4.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/file-type?sponsor=1"
+      }
+    },
     "node_modules/filelist": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -5918,15 +5926,18 @@
       }
     },
     "node_modules/formidable": {
-      "version": "3.5.2",
-      "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz",
-      "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==",
+      "version": "3.5.4",
+      "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
+      "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
       "dev": true,
       "dependencies": {
+        "@paralleldrive/cuid2": "^2.2.2",
         "dezalgo": "^1.0.4",
-        "hexoid": "^2.0.0",
         "once": "^1.4.0"
       },
+      "engines": {
+        "node": ">=14.0.0"
+      },
       "funding": {
         "url": "https://ko-fi.com/tunnckoCore/commissions"
       }
@@ -6274,15 +6285,6 @@
         "he": "bin/he"
       }
     },
-    "node_modules/hexoid": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz",
-      "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/highlight.js": {
       "version": "10.7.3",
       "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -8830,13 +8832,13 @@
       "devOptional": true
     },
     "node_modules/nestjs-i18n": {
-      "version": "10.4.9",
-      "resolved": "https://registry.npmjs.org/nestjs-i18n/-/nestjs-i18n-10.4.9.tgz",
-      "integrity": "sha512-LYH4bcXZdS2heCQDzvASQS1fzFCozFUannsZzJeUV7FIQxW0GafTopZ79IcViMljVLLYRmddmvKqD1XzZu54fQ==",
+      "version": "10.5.1",
+      "resolved": "https://registry.npmjs.org/nestjs-i18n/-/nestjs-i18n-10.5.1.tgz",
+      "integrity": "sha512-cJJFz+RUfav23QACpGCq1pdXNLYC3tBesrP14RGoE/YYcD4xosQPX2eyjvDNuo0Ti63Xtn6j57wDNEUKrZqmSw==",
       "dependencies": {
         "accept-language-parser": "^1.5.0",
-        "chokidar": "^3.5.3",
-        "cookie": "^0.5.0",
+        "chokidar": "^3.6.0",
+        "cookie": "^0.7.0",
         "iterare": "^1.2.1",
         "js-yaml": "^4.1.0",
         "string-format": "^2.0.0"
@@ -8851,14 +8853,6 @@
         "rxjs": "*"
       }
     },
-    "node_modules/nestjs-i18n/node_modules/cookie": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
-      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
     "node_modules/nice-try": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -9436,6 +9430,18 @@
         "url": "https://ko-fi.com/killymxi"
       }
     },
+    "node_modules/peek-readable": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz",
+      "integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Borewit"
+      }
+    },
     "node_modules/pg": {
       "version": "8.13.1",
       "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
@@ -10120,12 +10126,6 @@
       "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
       "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
     },
-    "node_modules/regenerator-runtime": {
-      "version": "0.14.1",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
-      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
-      "optional": true
-    },
     "node_modules/relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
@@ -10918,6 +10918,22 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/strtok3": {
+      "version": "10.2.2",
+      "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz",
+      "integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==",
+      "dependencies": {
+        "@tokenizer/token": "^0.3.0",
+        "peek-readable": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Borewit"
+      }
+    },
     "node_modules/superagent": {
       "version": "9.0.2",
       "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz",
@@ -11252,6 +11268,22 @@
       "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==",
       "optional": true
     },
+    "node_modules/token-types": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
+      "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==",
+      "dependencies": {
+        "@tokenizer/token": "^0.3.0",
+        "ieee754": "^1.2.1"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Borewit"
+      }
+    },
     "node_modules/tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -11676,6 +11708,17 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/uint8array-extras": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
+      "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/undici-types": {
       "version": "6.19.8",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
diff --git a/src/app.module.ts b/src/app.module.ts
index 601ed43..3dc1d46 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -19,6 +19,8 @@ import { UserSubjectModule } from '@modules/user_subject/user_subject.module';
 import { UserTaskModule } from '@modules/user_task/user_task.module';
 import { SupervisorCourseModule } from '@modules/supervisor_course/supervisor_course.module';
 import { CourseSubjectModule } from '@modules/course_subject/course_subject.module';
+import { AuthModule } from '@modules/auth/auth.module';
+import { PassportModule } from '@nestjs/passport';
 // import { AuthModule } from './auth/auth.module';
 
 @Module({
@@ -56,6 +58,9 @@ import { CourseSubjectModule } from '@modules/course_subject/course_subject.modu
             },
             resolvers: [{ use: QueryResolver, options: ['lang'] }, AcceptLanguageResolver],
         }),
+        PassportModule.register({
+            session: true,
+        }),
         DatabaseModule,
         UserModule,
         CourseModule,
@@ -66,6 +71,7 @@ import { CourseSubjectModule } from '@modules/course_subject/course_subject.modu
         UserSubjectModule,
         UserTaskModule,
         SupervisorCourseModule,
+        AuthModule,
     ],
     controllers: [AppController],
     providers: [
diff --git a/src/constants/contants.ts b/src/constants/contants.ts
new file mode 100644
index 0000000..f8d932d
--- /dev/null
+++ b/src/constants/contants.ts
@@ -0,0 +1 @@
+export const DEFAULT_COURSE_THUMBNAIL = 'https://i.ytimg.com/vi/Oe421EPjeBE/maxresdefault.jpg';
diff --git a/src/i18n/en/auths.json b/src/i18n/en/auths.json
new file mode 100644
index 0000000..9921700
--- /dev/null
+++ b/src/i18n/en/auths.json
@@ -0,0 +1,7 @@
+{
+    "This account is not actived": "This account is not actived",
+    "Email is exists": "Email is exists",
+    "Please using correct platform": "Please using correct platform",
+    "Email or Password is not correct": "Email or Password is not correct",
+    "Email is not exists": "Email is not exists"
+}
diff --git a/src/i18n/vi/auths.json b/src/i18n/vi/auths.json
new file mode 100644
index 0000000..4c0a2f9
--- /dev/null
+++ b/src/i18n/vi/auths.json
@@ -0,0 +1,7 @@
+{
+    "This account is not actived": "Tài khoản này chưa được kích hoạt",
+    "Email is exists": "Email đã tồn tại",
+    "Please using correct platform": "Vui lòng sử dụng đúng nền tảng",
+    "Email or Password is not correct": "Email hoặc mật khẩu không đúng",
+    "Email is not exists": "Email không tồn tại"
+}
diff --git a/src/i18n/vi/language.json b/src/i18n/vi/language.json
new file mode 100644
index 0000000..cc0a5dc
--- /dev/null
+++ b/src/i18n/vi/language.json
@@ -0,0 +1,3 @@
+{
+    "Unauthorized": "Bạn không có quyền thực hiện hành động này"
+}
diff --git a/src/main.ts b/src/main.ts
index fa224d7..e19f97a 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2,22 +2,30 @@ import { NestFactory } from '@nestjs/core';
 import { AppModule } from './app.module';
 import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
 import * as session from 'express-session';
+import * as passport from 'passport';
+import { ConfigService } from '@nestjs/config';
 
 async function bootstrap() {
     const app = await NestFactory.create(AppModule);
 
+    const configService = app.get(ConfigService);
+    const sessionSecret = configService.get<string>('SESSION_SECRET');
+
     app.use(
         session({
-            secret: 'JDVADKVBSDAOWEIFJDSVBDSFHBVDJF',
+            secret: sessionSecret,
             resave: false,
             saveUninitialized: false,
             cookie: {
-                maxAge: 60000,
-                secure: true,
+                maxAge: 3600000,
+                secure: false,
             },
         }),
     );
 
+    app.use(passport.initialize());
+    app.use(passport.session());
+
     const config = new DocumentBuilder()
         .setTitle('Users API')
         .setDescription('The users API description')
diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts
new file mode 100644
index 0000000..464e435
--- /dev/null
+++ b/src/modules/auth/auth.controller.ts
@@ -0,0 +1,24 @@
+import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common';
+import { ApiTags } from '@nestjs/swagger';
+import { SignUpDto } from './dto/auth.dto';
+import { AuthService } from './auth.service';
+import { AppResponse } from 'src/types/common.type';
+import { User } from '@modules/users/entity/user.entity';
+import { LocalAuthGuard } from './guards/local.guard';
+import { RequestWithUser } from 'src/types/requests.type';
+
+@Controller('auth')
+@ApiTags('auths')
+export class AuthController {
+    constructor(private readonly authService: AuthService) {}
+    @UseGuards(LocalAuthGuard)
+    @Post('login')
+    async handleLogin(@Request() req: RequestWithUser): Promise<AppResponse<User>> {
+        return await this.authService.buildLoginResponse(req);
+    }
+
+    @Post('signUp')
+    async handleSignUp(@Body() dto: SignUpDto): Promise<AppResponse<User>> {
+        return await this.authService.signUp(dto);
+    }
+}
diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts
new file mode 100644
index 0000000..840e336
--- /dev/null
+++ b/src/modules/auth/auth.module.ts
@@ -0,0 +1,14 @@
+import { UserModule } from '@modules/users/user.module';
+import { Module } from '@nestjs/common';
+import { AuthService } from './auth.service';
+import { AuthController } from './auth.controller';
+import { SessionSerializer } from './serialize/session.serialize';
+import { PassportModule } from '@nestjs/passport';
+import { LocalStrategy } from './strategy/session.strategy';
+
+@Module({
+    imports: [PassportModule.register({ session: true }), UserModule],
+    providers: [AuthService, LocalStrategy, SessionSerializer],
+    controllers: [AuthController],
+})
+export class AuthModule {}
diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts
new file mode 100644
index 0000000..3b1d664
--- /dev/null
+++ b/src/modules/auth/auth.service.ts
@@ -0,0 +1,87 @@
+import { UsersService } from '@modules/users/user.services';
+import { Injectable, NotFoundException, UnauthorizedException, UnprocessableEntityException } from '@nestjs/common';
+import { SignInDto, SignUpDto } from './dto/auth.dto';
+import * as argon2 from 'argon2';
+import { User } from '@modules/users/entity/user.entity';
+import { EEnvironment } from './enum/index.enum';
+import { ERolesUser, EStatusUser } from '@modules/users/enums/index.enum';
+import { AppResponse } from 'src/types/common.type';
+import { RequestWithUser } from 'src/types/requests.type';
+
+@Injectable()
+export class AuthService {
+    constructor(private readonly userService: UsersService) {}
+
+    async signIn(dto: SignInDto): Promise<User> {
+        const { email, password, environment } = dto;
+        const user: User = await this.userService.findByEmail(email);
+        if (!user) {
+            throw new UnauthorizedException('auths.Email is not exists');
+        }
+        const passwordIsCorrect: boolean = await this.verifyPassword(password, user.password);
+        if (!passwordIsCorrect) {
+            throw new UnauthorizedException('auths.Email or Password is not correct');
+        }
+        const checkRoleIsSuitableWithEnvironment: boolean = this._checkRoleIsSuitableWithEnvironment(
+            environment,
+            user.role,
+        );
+        if (!checkRoleIsSuitableWithEnvironment) {
+            throw new UnauthorizedException('auths.Email or Password is not correct');
+        }
+        return user;
+    }
+
+    async buildLoginResponse(req: RequestWithUser): Promise<AppResponse<User>> {
+        return {
+            data: req.user,
+        };
+    }
+
+    _getUserRoleFromEnvironment(environment: EEnvironment): ERolesUser {
+        switch (environment) {
+            case EEnvironment.SUPERVISOR:
+                return ERolesUser.SUPERVISOR;
+            case EEnvironment.TRAINEE:
+                return ERolesUser.TRAINEE;
+        }
+    }
+
+    _checkRoleIsSuitableWithEnvironment(environment: EEnvironment, role: ERolesUser): boolean {
+        if (
+            (environment === EEnvironment.SUPERVISOR && role === ERolesUser.SUPERVISOR) ||
+            (environment === EEnvironment.TRAINEE && role === ERolesUser.TRAINEE)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    async signUp(dto: SignUpDto): Promise<AppResponse<User>> {
+        const { email, password, name, environment, role } = dto;
+        if (!this._checkRoleIsSuitableWithEnvironment(environment, role)) {
+            throw new UnprocessableEntityException('auths.Please using correct platform');
+        }
+        const user: User = await this.userService.findByEmail(email);
+        if (user) {
+            throw new NotFoundException('auths.Email is exists');
+        }
+        const hashedPassword: string = await argon2.hash(password);
+        return {
+            data: await this.userService.create({
+                email,
+                password: hashedPassword,
+                name,
+                status: role === ERolesUser.SUPERVISOR ? EStatusUser.INACTIVE : EStatusUser.ACTIVE,
+            }),
+        };
+    }
+
+    async verifyPassword(plainTextPassword: string, hashedPassword: string): Promise<boolean> {
+        try {
+            return await argon2.verify(hashedPassword, plainTextPassword);
+        } catch (err) {
+            return false;
+        }
+    }
+}
diff --git a/src/modules/auth/dto/auth.dto.ts b/src/modules/auth/dto/auth.dto.ts
new file mode 100644
index 0000000..747c128
--- /dev/null
+++ b/src/modules/auth/dto/auth.dto.ts
@@ -0,0 +1,56 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsEmail, IsEnum, IsNotEmpty, IsStrongPassword } from 'class-validator';
+import { EEnvironment } from '../enum/index.enum';
+import { ERolesUser } from '@modules/users/enums/index.enum';
+
+export class BaseAuthDto {
+    @ApiProperty({
+        default: 'trainee@gmail.com',
+        required: true,
+    })
+    @IsNotEmpty()
+    @IsEmail()
+    email: string;
+
+    @ApiProperty({
+        default: EEnvironment.TRAINEE,
+        required: true,
+        enum: EEnvironment,
+    })
+    @IsEnum(EEnvironment)
+    environment: EEnvironment;
+
+    @ApiProperty({
+        default: ERolesUser.TRAINEE,
+        required: true,
+        enum: ERolesUser,
+    })
+    @IsEnum(ERolesUser)
+    role: ERolesUser;
+}
+
+export class SignInDto extends BaseAuthDto {
+    @ApiProperty({
+        required: true,
+        default: 'Test@#123',
+    })
+    @IsNotEmpty()
+    password: string;
+}
+
+export class SignUpDto extends BaseAuthDto {
+    @ApiProperty({
+        required: true,
+        default: 'Test@#123',
+    })
+    @IsNotEmpty()
+    @IsStrongPassword()
+    password: string;
+
+    @ApiProperty({
+        required: true,
+        default: 'trainee',
+    })
+    @IsNotEmpty()
+    name: string;
+}
diff --git a/src/modules/auth/enum/index.enum.ts b/src/modules/auth/enum/index.enum.ts
new file mode 100644
index 0000000..10babca
--- /dev/null
+++ b/src/modules/auth/enum/index.enum.ts
@@ -0,0 +1,4 @@
+export enum EEnvironment {
+    SUPERVISOR = 'SUPERVISOR',
+    TRAINEE = 'TRAINEE',
+}
diff --git a/src/modules/auth/guards/local.guard.ts b/src/modules/auth/guards/local.guard.ts
new file mode 100644
index 0000000..6890335
--- /dev/null
+++ b/src/modules/auth/guards/local.guard.ts
@@ -0,0 +1,12 @@
+import { ExecutionContext, Injectable } from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+
+@Injectable()
+export class LocalAuthGuard extends AuthGuard('local') {
+    async canActivate(context: ExecutionContext) {
+        const result = (await super.canActivate(context)) as boolean;
+        const req = context.switchToHttp().getRequest();
+        await super.logIn(req);
+        return result;
+    }
+}
diff --git a/src/modules/auth/serialize/session.serialize.ts b/src/modules/auth/serialize/session.serialize.ts
new file mode 100644
index 0000000..9b9ff51
--- /dev/null
+++ b/src/modules/auth/serialize/session.serialize.ts
@@ -0,0 +1,32 @@
+import { PassportSerializer } from '@nestjs/passport';
+import { Injectable } from '@nestjs/common';
+import { User } from '@modules/users/entity/user.entity';
+import { UsersService } from '@modules/users/user.services';
+
+@Injectable()
+export class SessionSerializer extends PassportSerializer {
+    constructor(private readonly userService: UsersService) {
+        super();
+    }
+
+    serializeUser(user: User, done: any) {
+        done(null, {
+            userId: user.id,
+            email: user.email,
+            role: user.role,
+        });
+    }
+
+    async deserializeUser(
+        payload: {
+            userId: string;
+            email: string;
+            role: string;
+        },
+        done: any,
+    ) {
+        const { userId } = payload;
+        const user = await this.userService.findUserById(userId);
+        done(null, user);
+    }
+}
diff --git a/src/modules/auth/strategy/session.strategy.ts b/src/modules/auth/strategy/session.strategy.ts
new file mode 100644
index 0000000..296b32c
--- /dev/null
+++ b/src/modules/auth/strategy/session.strategy.ts
@@ -0,0 +1,30 @@
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { PassportStrategy } from '@nestjs/passport';
+import { Strategy } from 'passport-local';
+import { AuthService } from '../auth.service';
+import { SignInDto } from '../dto/auth.dto';
+import { Request } from 'express';
+import { EStatusUser } from '@modules/users/enums/index.enum';
+
+@Injectable()
+export class LocalStrategy extends PassportStrategy(Strategy) {
+    constructor(private authService: AuthService) {
+        super({
+            passReqToCallback: true,
+            usernameField: 'email',
+            passwordField: 'password',
+        });
+    }
+
+    async validate(req: Request): Promise<any> {
+        const dto: SignInDto = req.body;
+        const user = await this.authService.signIn(dto);
+        if (!user) {
+            throw new UnauthorizedException();
+        }
+        if (user.status === EStatusUser.INACTIVE) {
+            throw new UnauthorizedException('auths.This account is not actived');
+        }
+        return user;
+    }
+}
diff --git a/src/modules/course_subject/entity/course_subject.entity.ts b/src/modules/course_subject/entity/course_subject.entity.ts
index 75beffa..d48f990 100644
--- a/src/modules/course_subject/entity/course_subject.entity.ts
+++ b/src/modules/course_subject/entity/course_subject.entity.ts
@@ -26,9 +26,6 @@ export class CourseSubject extends BaseEntity {
     })
     status: ECourseSubjectStatus;
 
-    @Column({ type: 'varchar' })
-    image: string;
-
     @ManyToOne(() => Course, (course) => course.courseSubjects)
     @JoinColumn({ name: 'courseId' })
     course: Course;
diff --git a/src/modules/courses/entity/course.entity.ts b/src/modules/courses/entity/course.entity.ts
index 790a5ef..d9179c2 100644
--- a/src/modules/courses/entity/course.entity.ts
+++ b/src/modules/courses/entity/course.entity.ts
@@ -14,6 +14,7 @@ import { User } from '@modules/users/entity/user.entity';
 import { CourseSubject } from '@modules/course_subject/entity/course_subject.entity';
 import { UserCourse } from '@modules/user_course/entity/user_course.entity';
 import { SupervisorCourse } from '@modules/supervisor_course/entity/supervisor_course.entity';
+import { DEFAULT_COURSE_THUMBNAIL } from 'src/constants/contants';
 
 @Entity()
 export class Course extends BaseEntity {
@@ -39,6 +40,9 @@ export class Course extends BaseEntity {
     @Column({ type: 'date' })
     endDate: Date;
 
+    @Column({ type: 'varchar', default: DEFAULT_COURSE_THUMBNAIL })
+    image: string;
+
     @ManyToOne(() => User, (user) => user.coursesCreated)
     @JoinColumn({ name: 'creatorId' })
     creator: User;
diff --git a/src/modules/users/dto/createNewUser.dto.ts b/src/modules/users/dto/createNewUser.dto.ts
index 15db9ea..8bb3c58 100644
--- a/src/modules/users/dto/createNewUser.dto.ts
+++ b/src/modules/users/dto/createNewUser.dto.ts
@@ -1,4 +1,6 @@
 import { ApiProperty } from '@nestjs/swagger';
+import { ERolesUser, EStatusUser } from '../enums/index.enum';
+import { IsOptional } from 'class-validator';
 
 export class CreateNewUserDto {
     @ApiProperty({
@@ -15,4 +17,18 @@ export class CreateNewUserDto {
         required: true,
     })
     email: string;
+
+    @ApiProperty({
+        required: false,
+        enum: ERolesUser,
+    })
+    @IsOptional()
+    role?: ERolesUser;
+
+    @ApiProperty({
+        required: false,
+        enum: EStatusUser,
+    })
+    @IsOptional()
+    status?: EStatusUser;
 }
diff --git a/src/modules/users/entity/user.entity.ts b/src/modules/users/entity/user.entity.ts
index 4826335..0d80ea9 100644
--- a/src/modules/users/entity/user.entity.ts
+++ b/src/modules/users/entity/user.entity.ts
@@ -7,7 +7,7 @@ import {
     OneToMany,
     BaseEntity,
 } from 'typeorm';
-import { ERolesUser } from '../enums/index.enum';
+import { ERolesUser, EStatusUser } from '../enums/index.enum';
 import { Course } from '@modules/courses/entity/course.entity';
 import { Subject } from '@modules/subjects/entity/subject.entity';
 import { UserCourse } from '@modules/user_course/entity/user_course.entity';
@@ -43,6 +43,9 @@ export class User extends BaseEntity {
     @Column('text')
     email: string;
 
+    @Column({ type: 'enum', enum: EStatusUser, default: EStatusUser.ACTIVE })
+    status: EStatusUser;
+
     @Column('text')
     password: string;
 


Reviews:
user: login: huongnt-2545
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T01:53:49Z
user: login: huongnt-2545
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T04:08:14Z

Review comments:
diff_hunk: @@ -2,22 +2,31 @@ import { NestFactory } from '@nestjs/core';
 import { AppModule } from './app.module';
 import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
 import * as session from 'express-session';
+import * as passport from 'passport';
+import { ConfigService } from '@nestjs/config';
 
 async function bootstrap() {
     const app = await NestFactory.create(AppModule);
 
+    const configService = app.get(ConfigService);
+    const sessionSecret = configService.get<string>('SESSION_SECRET');
+    console.log('sessionSecret', sessionSecret);
path: src/main.ts
user: login: huongnt-2545
body: xóa debug
created_at: 2025-05-21T01:31:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,35 @@
+import { Body, Controller, Get, Post, Request, UseGuards } from '@nestjs/common';
+import { ApiTags } from '@nestjs/swagger';
+import { SignUpDto } from './dto/auth.dto';
+import { AuthService } from './auth.service';
+import { AppResponse } from 'src/types/common.type';
+import { User } from '@modules/users/entity/user.entity';
+import { LocalAuthGuard } from './guards/local.guard';
+import { RequestWithUser } from 'src/types/requests.type';
+
+@Controller('auth')
+@ApiTags('auths')
+export class AuthController {
+    constructor(private readonly authService: AuthService) {}
+    @UseGuards(LocalAuthGuard)
+    @Post('login')
+    async handleLogin(@Request() req: RequestWithUser): Promise<AppResponse<User>> {
+        return {
+            data: req.user,
+        };
path: src/modules/auth/auth.controller.ts
user: login: huongnt-2545
body: template response xử lý bên service e nha, controller chỉ gọi từ service thôi
created_at: 2025-05-21T01:51:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { PassportStrategy } from '@nestjs/passport';
+import { Strategy } from 'passport-local';
+import { AuthService } from '../auth.service';
+import { SignInDto } from '../dto/auth.dto';
+import { Request } from 'express';
+import { EStatusUser } from '@modules/users/enums/index.enum';
+
+@Injectable()
+export class LocalStrategy extends PassportStrategy(Strategy) {
+    constructor(private authService: AuthService) {
+        super({
+            passReqToCallback: true,
+            usernameField: 'email',
+            passwordField: 'password',
+        });
+    }
+
+    async validate(req: Request): Promise<any> {
+        const dto: SignInDto = req.body;
+        const user = await this.authService.signIn(dto);
+        if (!user) {
+            throw new UnauthorizedException();
+        }
+        if (user.status === EStatusUser.INACTIVE) {
+            throw new UnauthorizedException('auths.This account is not actived');
path: src/modules/auth/strategy/session.strategy.ts
user: login: huongnt-2545
body: `'auths.This account is not actived'` -> I18n cho các message
created_at: 2025-05-21T01:52:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,6 +39,9 @@ export class Course extends BaseEntity {
     @Column({ type: 'date' })
     endDate: Date;
 
+    @Column({ type: 'varchar', default: 'https://i.ytimg.com/vi/Oe421EPjeBE/maxresdefault.jpg' })
path: src/modules/courses/entity/course.entity.ts
user: login: huongnt-2545
body: `'https://i.ytimg.com/vi/Oe421EPjeBE/maxresdefault.jpg'` -> define constants nhé
created_at: 2025-05-21T01:53:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: nthanhminh
created_at: 2025-05-20T09:44:47Z
body: READY
author_association: CONTRIBUTOR
user: login: nthanhminh
created_at: 2025-05-21T02:46:15Z
body: READY
author_association: CONTRIBUTOR