Commit 5bf022b2 authored by Nguyễn Thị Nguyệt Quế's avatar Nguyễn Thị Nguyệt Quế

Merge branch 'feat/challenge_3_auth' into 'develop'

feat(challenge_3): add register, login, get profile with json web token (JWT)

See merge request !3
parents d2554426 96ed263f
...@@ -19,8 +19,10 @@ ...@@ -19,8 +19,10 @@
"#docs/*": "./src/docs/*" "#docs/*": "./src/docs/*"
}, },
"dependencies": { "dependencies": {
"bcrypt": "^6.0.0",
"express": "^5.2.1", "express": "^5.2.1",
"express-automatic-routes": "^1.1.0", "express-automatic-routes": "^1.1.0",
"jsonwebtoken": "^9.0.3",
"module-alias": "^2.3.4", "module-alias": "^2.3.4",
"pg": "^8.20.0", "pg": "^8.20.0",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
...@@ -30,7 +32,9 @@ ...@@ -30,7 +32,9 @@
"swagger-ui-express": "^5.0.1" "swagger-ui-express": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.7.0", "@types/node": "^25.7.0",
"@types/swagger-jsdoc": "^6.0.4", "@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8", "@types/swagger-ui-express": "^4.1.8",
......
...@@ -8,12 +8,18 @@ importers: ...@@ -8,12 +8,18 @@ importers:
.: .:
dependencies: dependencies:
bcrypt:
specifier: ^6.0.0
version: 6.0.0
express: express:
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.1 version: 5.2.1
express-automatic-routes: express-automatic-routes:
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0 version: 1.1.0
jsonwebtoken:
specifier: ^9.0.3
version: 9.0.3
module-alias: module-alias:
specifier: ^2.3.4 specifier: ^2.3.4
version: 2.3.4 version: 2.3.4
...@@ -36,9 +42,15 @@ importers: ...@@ -36,9 +42,15 @@ importers:
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1(express@5.2.1) version: 5.0.1(express@5.2.1)
devDependencies: devDependencies:
'@types/bcrypt':
specifier: ^6.0.0
version: 6.0.0
'@types/express': '@types/express':
specifier: ^5.0.6 specifier: ^5.0.6
version: 5.0.6 version: 5.0.6
'@types/jsonwebtoken':
specifier: ^9.0.10
version: 9.0.10
'@types/node': '@types/node':
specifier: ^25.7.0 specifier: ^25.7.0
version: 25.7.0 version: 25.7.0
...@@ -263,6 +275,9 @@ packages: ...@@ -263,6 +275,9 @@ packages:
'@tsconfig/node16@1.0.4': '@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@types/bcrypt@6.0.0':
resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==}
'@types/body-parser@1.19.6': '@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
...@@ -284,6 +299,9 @@ packages: ...@@ -284,6 +299,9 @@ packages:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/jsonwebtoken@9.0.10':
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
'@types/ms@2.1.0': '@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
...@@ -341,6 +359,10 @@ packages: ...@@ -341,6 +359,10 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
bcrypt@6.0.0:
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
engines: {node: '>= 18'}
body-parser@2.2.2: body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'} engines: {node: '>=18'}
...@@ -348,6 +370,9 @@ packages: ...@@ -348,6 +370,9 @@ packages:
brace-expansion@1.1.14: brace-expansion@1.1.14:
resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==}
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
bytes@3.1.2: bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
...@@ -432,6 +457,9 @@ packages: ...@@ -432,6 +457,9 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
ee-first@1.1.1: ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
...@@ -569,17 +597,48 @@ packages: ...@@ -569,17 +597,48 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true hasBin: true
jsonwebtoken@9.0.3:
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
engines: {node: '>=12', npm: '>=6'}
jwa@2.0.1:
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
lodash.get@4.4.2: lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
lodash.isboolean@3.0.3:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
lodash.isequal@4.5.0: lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
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.mergewith@4.6.2: lodash.mergewith@4.6.2:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
lodash.once@4.1.1:
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
lodash@4.18.1: lodash@4.18.1:
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
...@@ -630,6 +689,14 @@ packages: ...@@ -630,6 +689,14 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
node-addon-api@8.7.0:
resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==}
engines: {node: ^18 || ^20 || >= 21}
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
object-inspect@1.13.4: object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -742,6 +809,9 @@ packages: ...@@ -742,6 +809,9 @@ packages:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safer-buffer@2.1.2: safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
...@@ -1081,6 +1151,10 @@ snapshots: ...@@ -1081,6 +1151,10 @@ snapshots:
'@tsconfig/node16@1.0.4': {} '@tsconfig/node16@1.0.4': {}
'@types/bcrypt@6.0.0':
dependencies:
'@types/node': 25.7.0
'@types/body-parser@1.19.6': '@types/body-parser@1.19.6':
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
...@@ -1111,6 +1185,11 @@ snapshots: ...@@ -1111,6 +1185,11 @@ snapshots:
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/jsonwebtoken@9.0.10':
dependencies:
'@types/ms': 2.1.0
'@types/node': 25.7.0
'@types/ms@2.1.0': {} '@types/ms@2.1.0': {}
'@types/node@25.7.0': '@types/node@25.7.0':
...@@ -1162,6 +1241,11 @@ snapshots: ...@@ -1162,6 +1241,11 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
bcrypt@6.0.0:
dependencies:
node-addon-api: 8.7.0
node-gyp-build: 4.8.4
body-parser@2.2.2: body-parser@2.2.2:
dependencies: dependencies:
bytes: 3.1.2 bytes: 3.1.2
...@@ -1181,6 +1265,8 @@ snapshots: ...@@ -1181,6 +1265,8 @@ snapshots:
balanced-match: 1.0.2 balanced-match: 1.0.2
concat-map: 0.0.1 concat-map: 0.0.1
buffer-equal-constant-time@1.0.1: {}
bytes@3.1.2: {} bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2: call-bind-apply-helpers@1.0.2:
...@@ -1244,6 +1330,10 @@ snapshots: ...@@ -1244,6 +1330,10 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer: 5.2.1
ee-first@1.1.1: {} ee-first@1.1.1: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
...@@ -1424,12 +1514,50 @@ snapshots: ...@@ -1424,12 +1514,50 @@ snapshots:
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
jsonwebtoken@9.0.3:
dependencies:
jws: 4.0.1
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.8.0
jwa@2.0.1:
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jws@4.0.1:
dependencies:
jwa: 2.0.1
safe-buffer: 5.2.1
lodash.get@4.4.2: {} lodash.get@4.4.2: {}
lodash.includes@4.3.0: {}
lodash.isboolean@3.0.3: {}
lodash.isequal@4.5.0: {} lodash.isequal@4.5.0: {}
lodash.isinteger@4.0.4: {}
lodash.isnumber@3.0.3: {}
lodash.isplainobject@4.0.6: {}
lodash.isstring@4.0.1: {}
lodash.mergewith@4.6.2: {} lodash.mergewith@4.6.2: {}
lodash.once@4.1.1: {}
lodash@4.18.1: {} lodash@4.18.1: {}
make-error@1.3.6: {} make-error@1.3.6: {}
...@@ -1464,6 +1592,10 @@ snapshots: ...@@ -1464,6 +1592,10 @@ snapshots:
negotiator@1.0.0: {} negotiator@1.0.0: {}
node-addon-api@8.7.0: {}
node-gyp-build@4.8.4: {}
object-inspect@1.13.4: {} object-inspect@1.13.4: {}
on-finished@2.4.1: on-finished@2.4.1:
...@@ -1567,6 +1699,8 @@ snapshots: ...@@ -1567,6 +1699,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
safe-buffer@5.2.1: {}
safer-buffer@2.1.2: {} safer-buffer@2.1.2: {}
semver@7.8.0: {} semver@7.8.0: {}
......
import { LoginProvider } from "#providers/LoginProvider.js";
import { Application } from "express"
import { Resource } from "express-automatic-routes"
export default (_express: Application) => {
const loginProvider = new LoginProvider();
return <Resource>{
/**
* @openapi
* /api/v1.0/auth/login:
* post:
* tags: [Auth]
* description: Đăng nhập người dùng
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/LoginInput"
* responses:
* 200:
* description: Đăng nhập thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/LoginResponse"
*/
post: {
handler: async (req, res) => {
try {
const login = await loginProvider.loginUser(req.body);
return res.status(200).json(login);
} catch (error) {
console.error('Error logging in user:', error);
return res.status(500).json({ error: (error as Error).message });
}
}
}
}
}
\ No newline at end of file
import { authMiddleware } from "#middlewares/authorization";
import { ProfileProvider } from "#providers/ProfileProvider.js";
import { Application } from "express";
import { Resource } from "express-automatic-routes";
export default (_express: Application) => {
const profileProvider = new ProfileProvider();
return <Resource>{
/**
* @openapi
* /api/v1.0/auth/profile:
* get:
* tags: [Auth]
* security:
* - bearerAuth: []
* description: Lấy thông tin hồ sơ người dùng
* responses:
* 200:
* description: Trả về thông tin hồ sơ người dùng
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Profile"
*/
get: {
middleware: [authMiddleware],
handler: async (req, res) => {
try {
const userId = (req as any).user.id;
const profile = await profileProvider.profileProvider(userId);
return res.status(200).json(profile);
} catch (error) {
return res.status(500).json({ error: (error as Error).message });
}
}
}
}
}
\ No newline at end of file
import { RegisterProvider } from "#providers/RegisterProvider.js";
import { Application } from "express";
import { Resource } from "express-automatic-routes";
export default (_express: Application) => {
const registerProvider = new RegisterProvider();
return <Resource>{
/**
* @openapi
* /api/v1.0/auth/register:
* post:
* tags: [Auth]
* description: Đăng ký người dùng mới
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/CreateUserInput"
* responses:
* 201:
* description: Tạo người dùng mới thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Register"
*/
post: {
handler: async (req, res) => {
try {
const { name, email, password } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ error: 'Vui lòng nhập đầy đủ thông tin' });
}
if (password.length < 6) {
return res.status(400).json({ error: 'Mật khẩu phải có ít nhất 6 ký tự' });
}
const register = await registerProvider.registerUser(req.body);
return res.status(201).json(register);
} catch (error) {
console.error('Error registering user:', error);
return res.status(500).json({ error: (error as Error).message });
}
}
}
}
}
\ No newline at end of file
...@@ -5,6 +5,13 @@ ...@@ -5,6 +5,13 @@
"version": "1.0.0" "version": "1.0.0"
}, },
"components": { "components": {
"securitySchemes": {
"bearerAuth": {
"type": "apiKey",
"in": "header",
"name": "Authorization"
}
},
"schemas": { "schemas": {
"Class": { "Class": {
"type": "object", "type": "object",
...@@ -190,6 +197,117 @@ ...@@ -190,6 +197,117 @@
"example": "active" "example": "active"
} }
} }
},
"Register": {
"type": "object",
"example": {
"id": "123",
"name": "Pham Quang Bao",
"email": "phamquangbao@example.com"
},
"properties": {
"id": {
"type": "uuid",
"format": "uuid",
"example": "123"
},
"name": {
"type": "string",
"example": "Pham Quang Bao"
},
"email": {
"type": "string",
"format": "email",
"example": "phamquangbao@example.com"
}
}
},
"CreateUserInput": {
"type": "object",
"example": {
"name": "Pham Quang Bao",
"email": "phamquangbao@example.com",
"password": "123456"
},
"properties": {
"name": {
"type": "string",
"example": "Pham Quang Bao"
},
"email": {
"type": "string",
"format": "email",
"example": "phamquangbao@example.com"
},
"password": {
"type": "string",
"example": "123456"
}
}
},
"LoginResponse": {
"type": "object",
"example": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567"
},
"properties": {
"accessToken": {
"type": "string",
"example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567"
}
}
},
"LoginInput": {
"type": "object",
"example": {
"email": "phamquangbao@example.com",
"password": "123456"
},
"properties": {
"email": {
"type": "string",
"format": "email",
"example": "phamquangbao@example.com"
},
"password": {
"type": "string",
"example": "123456"
}
}
},
"Profile": {
"type": "object",
"example": {
"id": "123",
"name": "Pham Quang Bao",
"email": "phamquangbao@example.com",
"phone": "+1234567890",
"address": "123 Main St, Anytown, USA"
},
"properties": {
"id": {
"type": "uuid",
"format": "uuid",
"example": "123"
},
"name": {
"type": "string",
"example": "Pham Quang Bao"
},
"email": {
"type": "string",
"format": "email",
"example": "phamquangbao@example.com"
},
"phone": {
"type": "string",
"example": "+1234567890"
},
"address": {
"type": "string",
"example": "123 Main St, Anytown, USA"
}
}
} }
}, },
"parameters": { "parameters": {
...@@ -228,6 +346,97 @@ ...@@ -228,6 +346,97 @@
} }
}, },
"paths": { "paths": {
"/api/v1.0/auth/login": {
"post": {
"tags": [
"Auth"
],
"description": "Đăng nhập người dùng",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginInput"
}
}
}
},
"responses": {
"200": {
"description": "Đăng nhập thành công",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
}
}
}
}
},
"/api/v1.0/auth/profile": {
"get": {
"tags": [
"Auth"
],
"security": [
{
"bearerAuth": []
}
],
"description": "Lấy thông tin hồ sơ người dùng",
"responses": {
"200": {
"description": "Trả về thông tin hồ sơ người dùng",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Profile"
}
}
}
}
}
}
},
"/api/v1.0/auth/register": {
"post": {
"tags": [
"Auth"
],
"description": "Đăng ký người dùng mới",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateUserInput"
}
}
}
},
"responses": {
"201": {
"description": "Tạo người dùng mới thành công",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Register"
}
}
}
}
}
}
}
},
"/api/v1.0/classes/{id}": { "/api/v1.0/classes/{id}": {
"get": { "get": {
"tags": [ "tags": [
......
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
//demo
const JWT_SECRET = '1234567890';
export const authMiddleware = (req: any, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Không tìm thấy token' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, JWT_SECRET) as any;
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({ error: 'Token không hợp lệ hoặc đã hết hạn' });
}
};
\ No newline at end of file
import { models } from "#models/sequelize-config.js";
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
interface LoginInput {
email: string;
password: string;
}
export class LoginProvider {
// demo
private readonly JWT_SECRET = '1234567890';
async loginUser(input: LoginInput) {
const user = await models.users.findOne({
where: { email: input.email },
});
if (!user) {
throw new Error('Email hoặc mật khẩu không đúng');
}
const auth = await models.user_auth.findOne({
where: { user_id: user?.id }
});
const passwordMatch = await bcrypt.compare(input.password, auth?.password_hash || '');
if (!passwordMatch) {
throw new Error('Email hoặc mật khẩu không đúng');
}
const payload = {
id: user.id,
email: user.email,
role_id: user.role_id
};
const token = jwt.sign(payload, this.JWT_SECRET, {
expiresIn: '1h',
});
return { accessToken: token };
}
}
\ No newline at end of file
import { models } from "#models/sequelize-config.js";
export class ProfileProvider {
async profileProvider(userId: number) {
const user = await models.users.findByPk(userId);
if (!user) {
throw new Error('Người dùng không tồn tại');
}
return user;
}
}
\ No newline at end of file
import { models, sequelize } from '#models/sequelize-config.js';
import bcrypt from 'bcrypt';
interface RegisterInput {
name: string;
email: string;
password: string;
}
export class RegisterProvider {
async registerUser(input: RegisterInput) {
const salt = await bcrypt.genSalt(10);
const passwordHash = await bcrypt.hash(input.password, salt);
try {
const result = await sequelize.transaction(async (t) => {
const registeredUser = await models.users.create(
{
name: input.name,
email: input.email,
},
{ transaction: t }
);
await models.user_auth.create(
{
user_id: registeredUser.id,
password_hash: passwordHash,
},
{ transaction: t }
);
return {
id: registeredUser.id,
name: registeredUser.name,
email: registeredUser.email,
};
});
return result;
} catch (error) {
console.error('Error during user registration:', error);
throw new Error('Failed to register user');
}
}
}
\ No newline at end of file
const authProfileSchemas = {
Profile: {
type: 'object',
example: {
id: '123',
name: 'Pham Quang Bao',
email: 'phamquangbao@example.com',
phone: '+1234567890',
address: '123 Main St, Anytown, USA',
},
properties: {
id: {
type: 'uuid',
format: 'uuid',
example: '123',
},
name: {
type: 'string',
example: 'Pham Quang Bao',
},
email: {
type: 'string',
format: 'email',
example: 'phamquangbao@example.com',
},
phone: {
type: 'string',
example: '+1234567890',
},
address: {
type: 'string',
example: '123 Main St, Anytown, USA',
},
},
},
};
export default authProfileSchemas;
\ No newline at end of file
...@@ -3,6 +3,9 @@ import classSchemas from './classes/schemas.js'; ...@@ -3,6 +3,9 @@ import classSchemas from './classes/schemas.js';
import courseSchemas from './courses/schemas.js'; import courseSchemas from './courses/schemas.js';
import type { Options } from 'swagger-jsdoc'; import type { Options } from 'swagger-jsdoc';
import registerSchemas from './register/schemas.js';
import loginSchemas from './login/schemas.js';
import authProfileSchemas from './authProfile/schema.js';
const swaggerOptions: Options = { const swaggerOptions: Options = {
definition: { definition: {
...@@ -12,9 +15,19 @@ const swaggerOptions: Options = { ...@@ -12,9 +15,19 @@ const swaggerOptions: Options = {
version: '1.0.0', version: '1.0.0',
}, },
components: { components: {
securitySchemes: {
bearerAuth: {
type: 'apiKey',
in: 'header',
name: 'Authorization',
}
},
schemas: { schemas: {
...classSchemas, ...classSchemas,
...courseSchemas, ...courseSchemas,
...registerSchemas,
...loginSchemas,
...authProfileSchemas,
}, },
parameters: { parameters: {
filters: { filters: {
......
const loginSchemas = {
LoginResponse: {
type: 'object',
example: {
accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567',
},
properties: {
accessToken: {
type: 'string',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567',
},
},
},
LoginInput: {
type: 'object',
example: {
email: 'phamquangbao@example.com',
password: '123456',
},
properties: {
email: {
type: 'string',
format: 'email',
example: 'phamquangbao@example.com',
},
password: {
type: 'string',
example: '123456',
},
},
},
};
export default loginSchemas;
\ No newline at end of file
const registerSchemas = {
Register: {
type: 'object',
example: {
id: '123',
name: 'Pham Quang Bao',
email: 'phamquangbao@example.com',
},
properties: {
id: {
type: 'uuid',
format: 'uuid',
example: '123',
},
name: {
type: 'string',
example: 'Pham Quang Bao',
},
email: {
type: 'string',
format: 'email',
example: 'phamquangbao@example.com',
},
},
},
CreateUserInput: {
type: 'object',
example: {
name: 'Pham Quang Bao',
email: 'phamquangbao@example.com',
password: '123456',
},
properties: {
name: {
type: 'string',
example: 'Pham Quang Bao',
},
email: {
type: 'string',
format: 'email',
example: 'phamquangbao@example.com',
},
password: {
type: 'string',
example: '123456',
},
},
},
};
export default registerSchemas;
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment