From 53237318da01519d59e1781b2124ce265ad3e1ca Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sun, 3 Sep 2023 01:01:55 +0800 Subject: [PATCH] feat: add jwt auth --- package.json | 9 ++ pnpm-lock.yaml | 225 +++++++++++++++++++++++++++++++++- src/server/main.ts | 11 +- src/server/middleware/auth.ts | 58 +++++++++ src/server/model/user.ts | 15 +++ src/server/router/user.ts | 25 +++- 6 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 src/server/middleware/auth.ts diff --git a/package.json b/package.json index 960bc09..7f1421e 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,20 @@ "@ant-design/icons": "^5.2.5", "@prisma/client": "^5.2.0", "antd": "^5.8.5", + "axios": "^1.5.0", "bcryptjs": "^2.4.3", "clsx": "^2.0.0", "compose-middleware": "^5.0.1", "compression": "^1.7.4", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-errors": "^3.1.1", "express-validator": "^7.0.1", + "jsonwebtoken": "^9.0.2", "lodash-es": "^4.17.21", + "nanoid": "^3.3.6", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.15.0", @@ -37,8 +43,11 @@ "@types/bcryptjs": "^2.4.3", "@types/compression": "^1.7.2", "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", "@types/lodash-es": "^4.17.9", "@types/node": "^18.17.12", + "@types/passport": "^1.0.12", + "@types/passport-jwt": "^3.0.9", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de86abc..1e0c641 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ dependencies: antd: specifier: ^5.8.5 version: 5.8.5(react-dom@18.2.0)(react@18.2.0) + axios: + specifier: ^1.5.0 + version: 1.5.0 bcryptjs: specifier: ^2.4.3 version: 2.4.3 @@ -31,12 +34,27 @@ dependencies: express: specifier: ^4.18.2 version: 4.18.2 + express-async-errors: + specifier: ^3.1.1 + version: 3.1.1(express@4.18.2) express-validator: specifier: ^7.0.1 version: 7.0.1 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 + nanoid: + specifier: ^3.3.6 + version: 3.3.6 + passport: + specifier: ^0.6.0 + version: 0.6.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 react: specifier: ^18.2.0 version: 18.2.0 @@ -75,12 +93,21 @@ devDependencies: '@types/express': specifier: ^4.17.17 version: 4.17.17 + '@types/jsonwebtoken': + specifier: ^9.0.2 + version: 9.0.2 '@types/lodash-es': specifier: ^4.17.9 version: 4.17.9 '@types/node': specifier: ^18.17.12 version: 18.17.12 + '@types/passport': + specifier: ^1.0.12 + version: 1.0.12 + '@types/passport-jwt': + specifier: ^3.0.9 + version: 3.0.9 '@types/react': specifier: ^18.2.21 version: 18.2.21 @@ -1844,6 +1871,12 @@ packages: resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} dev: false + /@types/jsonwebtoken@9.0.2: + resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} + dependencies: + '@types/node': 18.17.12 + dev: true + /@types/lodash-es@4.17.9: resolution: {integrity: sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==} dependencies: @@ -1871,6 +1904,27 @@ packages: /@types/node@18.17.12: resolution: {integrity: sha512-d6xjC9fJ/nSnfDeU0AMDsaJyb1iHsqCSOdi84w4u+SlN/UgQdY5tRhpMzaFYsI4mnpvgTivEaQd0yOUhAtOnEQ==} + /@types/passport-jwt@3.0.9: + resolution: {integrity: sha512-5XJt+79emfgpuBvBQusUPylFIVtW1QVAAkTRwCbRJAmxUjmLtIqUU6V1ovpnHPu6Qut3mR5Juc+s7kd06roNTg==} + dependencies: + '@types/express': 4.17.17 + '@types/jsonwebtoken': 9.0.2 + '@types/passport-strategy': 0.2.35 + dev: true + + /@types/passport-strategy@0.2.35: + resolution: {integrity: sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==} + dependencies: + '@types/express': 4.17.17 + '@types/passport': 1.0.12 + dev: true + + /@types/passport@1.0.12: + resolution: {integrity: sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==} + dependencies: + '@types/express': 4.17.17 + dev: true + /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: true @@ -2140,6 +2194,10 @@ packages: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /autoprefixer@10.4.15(postcss@8.4.29): resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} engines: {node: ^10 || ^12 || >=14} @@ -2161,6 +2219,16 @@ packages: engines: {node: '>= 0.4'} dev: false + /axios@1.5.0: + resolution: {integrity: sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2222,6 +2290,10 @@ packages: update-browserslist-db: 1.0.11(browserslist@4.21.10) dev: true + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + /bytes@3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} engines: {node: '>= 0.8'} @@ -2339,6 +2411,13 @@ packages: color-string: 1.9.1 dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: false @@ -2651,6 +2730,11 @@ packages: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2697,6 +2781,12 @@ packages: resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} dev: false + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false @@ -2874,6 +2964,14 @@ packages: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} dev: false + /express-async-errors@3.1.1(express@4.18.2): + resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} + peerDependencies: + express: ^4.16.2 + dependencies: + express: 4.18.2 + dev: false + /express-validator@7.0.1: resolution: {integrity: sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==} engines: {node: '>= 8.0.0'} @@ -3010,12 +3108,31 @@ packages: uglify-js: 2.8.29 dev: false + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -3476,6 +3593,37 @@ packages: hasBin: true dev: true + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.5.4 + dev: false + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + /kdbush@3.0.0: resolution: {integrity: sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==} dev: false @@ -3534,6 +3682,34 @@ packages: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: false + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false @@ -3556,6 +3732,13 @@ packages: yallist: 3.1.1 dev: true + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: false + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -3738,7 +3921,6 @@ packages: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} @@ -3841,6 +4023,27 @@ packages: engines: {node: '>= 0.8'} dev: false + /passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + dev: false + + /passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + dev: false + + /passport@0.6.0: + resolution: {integrity: sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + dev: false + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -3852,6 +4055,10 @@ packages: resolution: {integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=} dev: false + /pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + dev: false + /pbf@3.2.1: resolution: {integrity: sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==} hasBin: true @@ -4012,6 +4219,10 @@ packages: ipaddr.js: 1.9.1 dev: false + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} dev: true @@ -4944,6 +5155,14 @@ packages: hasBin: true dev: true + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -5636,6 +5855,10 @@ packages: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: true + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: false + /yaml@2.3.2: resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} engines: {node: '>= 14'} diff --git a/src/server/main.ts b/src/server/main.ts index e90abfd..687e9a8 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -1,13 +1,18 @@ import 'dotenv/config'; import express from 'express'; +import 'express-async-errors'; import ViteExpress from 'vite-express'; import compression from 'compression'; import { userRouter } from './router/user'; +import passport from 'passport'; + +const port = Number(process.env.PORT || 3000); const app = express(); app.use(compression()); app.use(express.json()); +app.use(passport.initialize()); // http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header app.disable('x-powered-by'); @@ -19,7 +24,7 @@ app.use((err: any, req: any, res: any, next: any) => { res.json({ error: err.message }); }); -ViteExpress.listen(app, 3000, () => { - console.log('Server is listening on port 3000...'); - console.log('Website: http://localhost:3000'); +ViteExpress.listen(app, port, () => { + console.log(`Server is listening on port ${port}...`); + console.log(`Website: http://localhost:${port}`); }); diff --git a/src/server/middleware/auth.ts b/src/server/middleware/auth.ts new file mode 100644 index 0000000..db7b1d4 --- /dev/null +++ b/src/server/middleware/auth.ts @@ -0,0 +1,58 @@ +import { authUser, findUser } from '../model/user'; +import passport from 'passport'; +import { Handler } from 'express'; +import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'; +import { nanoid } from 'nanoid'; +import jwt from 'jsonwebtoken'; + +export const jwtSecret = process.env.JWT_SECRET || nanoid(); +export const jwtIssuer = process.env.JWT_ISSUER || 'tianji.msgbyte.com'; +export const jwtAudience = process.env.JWT_AUDIENCE || 'msgbyte.com'; + +passport.use( + new JwtStrategy( + { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtSecret, + issuer: jwtIssuer, + audience: jwtAudience, + }, + function (jwt_payload, done) { + findUser(jwt_payload.sub) + .then((user) => { + if (user) { + done(null, user); + } else { + done(null, false); + } + }) + .catch((err) => { + done(err); + }); + } + ) +); + +passport.serializeUser(function (user: any, cb) { + cb(null, { id: user.id, username: user.username }); +}); + +passport.deserializeUser(function (user: any, cb) { + cb(null, user); +}); + +export function jwtSign(payload: {}): string { + const token = jwt.sign(payload, jwtSecret, { + issuer: jwtIssuer, + audience: jwtAudience, + expiresIn: '30d', + }); + + return token; +} + +export function auth(): Handler { + return passport.authenticate('jwt', { + session: false, + }); +} diff --git a/src/server/model/user.ts b/src/server/model/user.ts index a002af4..3927b65 100644 --- a/src/server/model/user.ts +++ b/src/server/model/user.ts @@ -44,3 +44,18 @@ export async function authUser(username: string, password: string) { return user; } + +export async function findUser(userId: string) { + const user = await prisma.user.findFirst({ + where: { + id: userId, + }, + select: { + id: true, + username: true, + role: true, + }, + }); + + return user; +} diff --git a/src/server/router/user.ts b/src/server/router/user.ts index 1064e3e..6596c1e 100644 --- a/src/server/router/user.ts +++ b/src/server/router/user.ts @@ -1,11 +1,34 @@ import { Router } from 'express'; import { body, validate } from '../middleware/validate'; -import { createAdminUser } from '../model/user'; +import { authUser, createAdminUser } from '../model/user'; +import { auth, jwtSign } from '../middleware/auth'; export const userRouter = Router(); +userRouter.post( + '/login', + validate( + body('username').exists().withMessage('Username should be existed'), + body('password').exists().withMessage('Password should be existed') + ), + async (req, res) => { + const { username, password } = req.body; + + const user = await authUser(username, password); + + const token = jwtSign({ + id: user.id, + username: user.username, + role: user.role, + }); + + res.json({ token }); + } +); + userRouter.post( '/createAdmin', + auth(), validate( body('username').exists().withMessage('Username should be existed'), body('password').exists().withMessage('Password should be existed')