From bcdd04715d10e942cb4ed436bf990d171afbab89 Mon Sep 17 00:00:00 2001 From: wancat Date: Mon, 11 Sep 2023 22:59:41 +0800 Subject: [PATCH 01/18] feat: add password login --- server/package.json | 4 + server/src/api/routes/auth.js | 46 +++++ server/src/api/routes/index.js | 2 + server/src/index.js | 4 + server/src/models/user.js | 14 +- server/src/utils/auth.js | 30 ++++ server/swagger.yaml | 2 +- yarn.lock | 300 ++++++++++++++++++++++++++++++++- 8 files changed, 390 insertions(+), 12 deletions(-) create mode 100644 server/src/api/routes/auth.js diff --git a/server/package.json b/server/package.json index 678672b..ea35e64 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@linux-odyssey/file-graph": "*", + "bcrypt": "^5.1.1", "commander": "^11.0.0", "dockerode": "^3.3.5", "express": "^4.18.2", @@ -18,6 +19,9 @@ "minimist": "^1.2.8", "mongoose": "^7.4.0", "node-schedule": "^2.1.1", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "socket.io": "^4.7.1", "swagger-ui-express": "^5.0.0", "yaml": "^2.3.1" diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js new file mode 100644 index 0000000..6277312 --- /dev/null +++ b/server/src/api/routes/auth.js @@ -0,0 +1,46 @@ +import { Router } from 'express' +import passport from 'passport' +import LocalStrategy from 'passport-local' +import bcrypt from 'bcrypt' + +import User from '../../models/user.js' +import { genUserJWT } from '../../utils/auth.js' + +passport.use( + new LocalStrategy(async function verify(username, password, done) { + try { + const user = await User.findOne({ username }) + if (!user) { + return done(null, false, { message: 'Incorrect username.' }) + } + + const isPasswordValid = await bcrypt.compare( + password, + user.hashedPassword + ) + if (!isPasswordValid) { + return done(null, false, { message: 'Incorrect username or password.' }) + } + return done(null, user) + } catch (err) { + return done(err) + } + }) +) + +const router = Router() + +async function issueToken(req, res) { + const token = await genUserJWT(req.user) + res.json({ + token, + }) +} + +router.post( + '/login/password', + passport.authenticate('local', { session: false }), + issueToken +) + +export default router diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index e7c6084..759f85f 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -3,6 +3,7 @@ import sessions from './sessions.js' import quests from './quests.js' import commands from './commands.js' import { authMiddleware } from '../../middleware/auth.js' +import authRouter from './auth.js' const router = Router() @@ -10,6 +11,7 @@ router.get('/', (req, res) => { res.send('Hello API!') }) +router.use(authRouter) router.use('/sessions', authMiddleware, sessions) router.use('/quests', quests) router.use('/commands', commands) diff --git a/server/src/index.js b/server/src/index.js index d7570b3..2b38ff2 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -11,12 +11,16 @@ import loadAndUpdateQuests from './utils/quest.js' import config from './config.js' import errorHandler from './middleware/error.js' import expiryRemovalScheduler from './containers/expiryChecker.js' +import { createTestUser } from './utils/auth.js' async function main() { const app = express() const server = http.createServer(app) socketServer(server) await connectDB() + + await createTestUser() + await loadAndUpdateQuests() expiryRemovalScheduler() diff --git a/server/src/models/user.js b/server/src/models/user.js index cc0a1b4..5899e90 100644 --- a/server/src/models/user.js +++ b/server/src/models/user.js @@ -1,9 +1,17 @@ import { model } from 'mongoose' const User = model('User', { - username: String, - password: String, - email: String, + username: { + type: String, + unique: true, + required: true, + }, + email: { + type: String, + unique: true, + required: true, + }, + hashedPassword: String, }) export default User diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 49d3ac6..6d291cf 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -1,7 +1,20 @@ import jwt from 'jsonwebtoken' +import bcrypt from 'bcrypt' import config from '../config.js' import User from '../models/user.js' +export async function createTestUser() { + let user = await User.findOne({ username: 'testUser' }) + if (!user) { + user = new User({ + username: 'testUser', + email: 'test@example.com', + }) + user.hashedPassword = await bcrypt.hash('testPassword', 10) + await user.save() + } +} + export async function defaultUser() { let user = await User.findOne({ username: 'defaultUser' }) if (!user) { @@ -14,6 +27,23 @@ export async function defaultUser() { return user } +export function genUserJWT(user) { + return new Promise((resolve, reject) => { + jwt.sign( + { _id: user.id, username: user.username, email: user.email }, + config.jwtSecret, + { expiresIn: config.expiry }, + (err, token) => { + if (err) { + reject(err) + } else { + resolve(token) + } + } + ) + }) +} + export function genSessionJWT(session) { return new Promise((resolve, reject) => { jwt.sign( diff --git a/server/swagger.yaml b/server/swagger.yaml index 69e65a8..c895628 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -198,7 +198,7 @@ paths: type: string example: User already exists - /login: + /login/password: post: summary: Log in a user requestBody: diff --git a/yarn.lock b/yarn.lock index c3c6bed..bc7bafa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -755,6 +755,21 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1059,6 +1074,13 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1113,6 +1135,19 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" @@ -1303,6 +1338,14 @@ bcrypt-pbkdf@^1.0.2: dependencies: tweetnacl "^0.14.3" +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -1499,6 +1542,11 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^3.2.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" @@ -1552,6 +1600,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1579,6 +1632,11 @@ confusing-browser-globals@^1.0.10: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -1665,7 +1723,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1730,6 +1788,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -1740,6 +1803,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2407,6 +2475,13 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2437,6 +2512,21 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -2588,6 +2678,11 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -2611,6 +2706,14 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -3339,6 +3442,22 @@ json5@^2.2.2: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + 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.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsonwebtoken@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#81d8c901c112c24e497a55daf6b2be1225b40145" @@ -3413,11 +3532,46 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -3454,7 +3608,7 @@ magic-string@^0.30.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -3545,11 +3699,36 @@ minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mongodb-connection-string-url@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf" @@ -3638,6 +3817,18 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -3673,6 +3864,13 @@ nodemon@^3.0.1: touch "^3.1.0" undefsafe "^2.0.5" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -3704,6 +3902,16 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -3711,7 +3919,7 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -object-assign@^4, object-assign@^4.0.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -3864,6 +4072,35 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-jwt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" + integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ== + dependencies: + jsonwebtoken "^9.0.0" + passport-strategy "^1.0.0" + +passport-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" + integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow== + dependencies: + passport-strategy "1.x.x" + +passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d" + integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3894,6 +4131,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -4083,7 +4325,7 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -4208,7 +4450,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.6, semver@^7.3.8, semver@^7.5.3: +semver@^7.3.5, semver@^7.3.6, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -4244,6 +4486,11 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -4275,7 +4522,7 @@ sift@16.0.1: resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053" integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ== -signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -4419,7 +4666,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4602,6 +4849,18 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.1.11: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -4671,6 +4930,11 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" @@ -4791,7 +5055,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== @@ -4859,6 +5123,11 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -4872,6 +5141,14 @@ whatwg-url@^11.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -4902,6 +5179,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" From 41111f50368fd31d7b56cad2af820881c2209356 Mon Sep 17 00:00:00 2001 From: wancat Date: Mon, 11 Sep 2023 23:09:00 +0800 Subject: [PATCH 02/18] feat: add jwt verification --- server/src/api/routes/index.js | 22 ++++++++++++++++++++-- server/src/utils/auth.js | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 759f85f..fdea3fc 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -1,9 +1,23 @@ import { Router } from 'express' +import passport from 'passport' +import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt' import sessions from './sessions.js' import quests from './quests.js' import commands from './commands.js' -import { authMiddleware } from '../../middleware/auth.js' import authRouter from './auth.js' +import config from '../../config.js' + +const jwtOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: config.jwtSecret, + issuer: 'linuxodyssey.xyz', +} + +passport.use( + new JwtStrategy(jwtOptions, function verifyJwt(jwtPayload, done) { + return done(null, jwtPayload) + }) +) const router = Router() @@ -12,7 +26,11 @@ router.get('/', (req, res) => { }) router.use(authRouter) -router.use('/sessions', authMiddleware, sessions) +router.use( + '/sessions', + passport.authenticate('jwt', { session: false }), + sessions +) router.use('/quests', quests) router.use('/commands', commands) diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 6d291cf..9704e6c 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -32,7 +32,7 @@ export function genUserJWT(user) { jwt.sign( { _id: user.id, username: user.username, email: user.email }, config.jwtSecret, - { expiresIn: config.expiry }, + { expiresIn: config.expiry, issuer: 'linuxodyssey.xyz' }, (err, token) => { if (err) { reject(err) From 030dc95e24fbab71b040d19d0321b40162fd9fa9 Mon Sep 17 00:00:00 2001 From: wancat Date: Mon, 11 Sep 2023 23:38:29 +0800 Subject: [PATCH 03/18] refactor: put passport to auth folder --- server/src/api/routes/auth.js | 36 ++++++----------------------- server/src/api/routes/index.js | 22 +++++------------- server/src/auth/jwtStrategy.js | 13 +++++++++++ server/src/auth/passport.js | 6 +++++ server/src/auth/passwordStrategy.js | 22 ++++++++++++++++++ server/src/index.js | 1 + server/src/middleware/auth.js | 11 --------- server/src/utils/auth.js | 23 +++--------------- 8 files changed, 58 insertions(+), 76 deletions(-) create mode 100644 server/src/auth/jwtStrategy.js create mode 100644 server/src/auth/passport.js create mode 100644 server/src/auth/passwordStrategy.js delete mode 100644 server/src/middleware/auth.js diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index 6277312..8d7afdd 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -1,40 +1,18 @@ import { Router } from 'express' import passport from 'passport' -import LocalStrategy from 'passport-local' -import bcrypt from 'bcrypt' -import User from '../../models/user.js' -import { genUserJWT } from '../../utils/auth.js' - -passport.use( - new LocalStrategy(async function verify(username, password, done) { - try { - const user = await User.findOne({ username }) - if (!user) { - return done(null, false, { message: 'Incorrect username.' }) - } - - const isPasswordValid = await bcrypt.compare( - password, - user.hashedPassword - ) - if (!isPasswordValid) { - return done(null, false, { message: 'Incorrect username or password.' }) - } - return done(null, user) - } catch (err) { - return done(err) - } - }) -) +import { genJwt } from '../../utils/auth.js' const router = Router() async function issueToken(req, res) { - const token = await genUserJWT(req.user) - res.json({ - token, + const { user } = req + const token = await genJwt({ + _id: user.id, + username: user.username, + email: user.email, }) + res.json({ token }) } router.post( diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index fdea3fc..20db430 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -1,23 +1,9 @@ import { Router } from 'express' import passport from 'passport' -import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt' import sessions from './sessions.js' import quests from './quests.js' import commands from './commands.js' import authRouter from './auth.js' -import config from '../../config.js' - -const jwtOptions = { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: config.jwtSecret, - issuer: 'linuxodyssey.xyz', -} - -passport.use( - new JwtStrategy(jwtOptions, function verifyJwt(jwtPayload, done) { - return done(null, jwtPayload) - }) -) const router = Router() @@ -26,12 +12,16 @@ router.get('/', (req, res) => { }) router.use(authRouter) +router.use('/quests', quests) router.use( '/sessions', passport.authenticate('jwt', { session: false }), sessions ) -router.use('/quests', quests) -router.use('/commands', commands) +router.use( + '/commands', + passport.authenticate('jwt', { session: false }), + commands +) export default router diff --git a/server/src/auth/jwtStrategy.js b/server/src/auth/jwtStrategy.js new file mode 100644 index 0000000..f1b2cd4 --- /dev/null +++ b/server/src/auth/jwtStrategy.js @@ -0,0 +1,13 @@ +import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt' +import config from '../config.js' + +const jwtOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: config.jwtSecret, +} + +function verifyJwt(jwtPayload, done) { + return done(null, jwtPayload) +} + +export default new JwtStrategy(jwtOptions, verifyJwt) diff --git a/server/src/auth/passport.js b/server/src/auth/passport.js new file mode 100644 index 0000000..fc1fe04 --- /dev/null +++ b/server/src/auth/passport.js @@ -0,0 +1,6 @@ +import passport from 'passport' +import PasswordStrategy from './passwordStrategy.js' +import JwtStrategy from './jwtStrategy.js' + +passport.use(PasswordStrategy) +passport.use(JwtStrategy) diff --git a/server/src/auth/passwordStrategy.js b/server/src/auth/passwordStrategy.js new file mode 100644 index 0000000..86b0b90 --- /dev/null +++ b/server/src/auth/passwordStrategy.js @@ -0,0 +1,22 @@ +import bcrypt from 'bcrypt' +import LocalStrategy from 'passport-local' +import User from '../models/user.js' + +async function verifyPassword(username, password, done) { + try { + const user = await User.findOne({ username }) + if (!user) { + return done(null, false, { message: 'Incorrect username.' }) + } + + const isPasswordValid = await bcrypt.compare(password, user.hashedPassword) + if (!isPasswordValid) { + return done(null, false, { message: 'Incorrect username or password.' }) + } + return done(null, user) + } catch (err) { + return done(err) + } +} + +export default new LocalStrategy(verifyPassword) diff --git a/server/src/index.js b/server/src/index.js index 2b38ff2..8a898ea 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -4,6 +4,7 @@ import http from 'http' import YAML from 'yaml' import swaggerUI from 'swagger-ui-express' +import './auth/passport.js' import socketServer from './api/socket.js' import connectDB from './db.js' import apiRouter from './api/routes/index.js' diff --git a/server/src/middleware/auth.js b/server/src/middleware/auth.js deleted file mode 100644 index 62bb190..0000000 --- a/server/src/middleware/auth.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { defaultUser } from '../utils/auth.js' - -export async function authMiddleware(req, res, next) { - try { - req.user = await defaultUser() - next() - } catch (err) { - res.status(500).json({ message: err.message }) - } -} diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 9704e6c..6388cf6 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -27,27 +27,10 @@ export async function defaultUser() { return user } -export function genUserJWT(user) { +export function genJwt(payload) { return new Promise((resolve, reject) => { jwt.sign( - { _id: user.id, username: user.username, email: user.email }, - config.jwtSecret, - { expiresIn: config.expiry, issuer: 'linuxodyssey.xyz' }, - (err, token) => { - if (err) { - reject(err) - } else { - resolve(token) - } - } - ) - }) -} - -export function genSessionJWT(session) { - return new Promise((resolve, reject) => { - jwt.sign( - { session_id: session.id }, + payload, config.jwtSecret, { expiresIn: config.expiry }, (err, token) => { @@ -61,7 +44,7 @@ export function genSessionJWT(session) { }) } -export function verifySessionJWT(token) { +export function verifyJWT(token) { return new Promise((resolve, reject) => { jwt.verify(token, config.jwtSecret, (err, decoded) => { if (err) { From 0f53696f714786d722dfbf23b748c6fa840ff91f Mon Sep 17 00:00:00 2001 From: wancat Date: Tue, 12 Sep 2023 00:12:09 +0800 Subject: [PATCH 04/18] feat: apply auth to container and cli client --- packages/client/index.js | 43 +++++++++++++++++-- packages/container/cli.js | 2 +- .../src/api/controllers/commandController.js | 12 +----- .../src/api/controllers/sessionController.js | 6 +-- server/src/api/routes/auth.js | 4 +- server/src/api/socket.js | 14 ++++-- server/src/utils/auth.js | 2 +- server/swagger.yaml | 8 ++-- 8 files changed, 64 insertions(+), 27 deletions(-) diff --git a/packages/client/index.js b/packages/client/index.js index 99ec410..2fb574a 100644 --- a/packages/client/index.js +++ b/packages/client/index.js @@ -57,21 +57,29 @@ async function lastSession() { return session } -async function connect(sessionId) { +async function connect(sessionId, token) { console.log(`Session ID: ${sessionId}`) console.log('Use Ctrl + D to exit.') if (!sessionId) exit() const socket = io(program.opts().host, { + auth: { + token, + }, query: { session_id: sessionId, }, }) - socket.on('open', function open() { + socket.on('connection', function open() { console.log('Connected to the server.') }) + socket.on('connect_error', (err) => { + console.error(err.message) + exit(1) + }) + socket.on('message', console.log) socket.on('terminal', function incoming(data) { @@ -101,6 +109,26 @@ async function connect(sessionId) { }) } +async function login() { + try { + const res = await api.post('/login/password', { + username: program.opts().user, + password: program.opts().password, + }) + const { token } = res.data + if (!token) { + console.error(res.data) + exit(1) + } + console.log('Login success.') + return token + } catch (err) { + console.error(err.message) + exit(1) + return null + } +} + async function main() { api = axios.create({ baseURL: `${program.opts().host}/api/v1`, @@ -111,18 +139,27 @@ async function main() { console.log(program.opts().session) // main() + const token = await login() + api.defaults.headers.common.Authorization = `Bearer ${token}` + const sessionId = program.opts().session || (program.opts().create ? (await createSession())._id : null) || (await lastSession())._id - await connect(sessionId) + await connect(sessionId, token) } program .option('-s, --session ', 'Session ID') .option('-c, --create', 'Create a new session') .option('-d, --debug', 'Debug mode') + .option('-u, --user ', 'Username', get('USERNAME', 'testUser')) + .option( + '-p, --password ', + 'Password', + get('PASSWORD', 'testPassword') + ) .option( '-h, --host ', 'Server host', diff --git a/packages/container/cli.js b/packages/container/cli.js index fac7eba..a6f71d3 100644 --- a/packages/container/cli.js +++ b/packages/container/cli.js @@ -49,7 +49,6 @@ async function main() { output, error, exit_code: CMD_EXIT_CODE, - token: TOKEN, pwd: PWD, ...additionalData, } @@ -58,6 +57,7 @@ async function main() { method: 'POST', headers: { 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, }, body: JSON.stringify(payload), }) diff --git a/server/src/api/controllers/commandController.js b/server/src/api/controllers/commandController.js index fd583e1..5b3465c 100644 --- a/server/src/api/controllers/commandController.js +++ b/server/src/api/controllers/commandController.js @@ -1,26 +1,18 @@ /* eslint-disable import/prefer-default-export */ import Command from '../../models/command.js' import Session from '../../models/session.js' -import { verifySessionJWT } from '../../utils/auth.js' import CommandHandler from '../../game/commandHandler.js' export async function newCommand(req, res) { console.log('new command:', req.body) - const { token, command, pwd, output, error, ...additionalData } = req.body + const { command, pwd, output, error, ...additionalData } = req.body if (!command) { res.status(400).json({ message: 'command is required' }) return } - let sessionId - try { - const decoded = await verifySessionJWT(token) - sessionId = decoded.session_id - } catch (err) { - res.status(401).json({ message: 'token not valid' }) - return - } + const sessionId = req.user.session_id const session = await Session.findById(sessionId) diff --git a/server/src/api/controllers/sessionController.js b/server/src/api/controllers/sessionController.js index 3130288..656d12f 100644 --- a/server/src/api/controllers/sessionController.js +++ b/server/src/api/controllers/sessionController.js @@ -5,7 +5,7 @@ import SessionHandler from '../../game/sessionHandler.js' export async function getSessionList(req, res) { try { - const query = { user: req.user.id } + const query = { user: req.user._id } if (req.query.quest_id) { query.quest = req.query.quest_id } @@ -70,7 +70,7 @@ export async function getSessionById(req, res) { try { const session = await Session.findOne({ _id: req.params.id, - user: req.user.id, + user: req.user._id, }) if (!session) { @@ -88,7 +88,7 @@ export async function deleteSessionById(req, res) { try { const session = await Session.findOneAndDelete({ _id: req.params.id, - user: req.user.id, + user: req.user._id, }) if (!session) { diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index 8d7afdd..54bcc74 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -1,13 +1,13 @@ import { Router } from 'express' import passport from 'passport' -import { genJwt } from '../../utils/auth.js' +import { genJWT } from '../../utils/auth.js' const router = Router() async function issueToken(req, res) { const { user } = req - const token = await genJwt({ + const token = await genJWT({ _id: user.id, username: user.username, email: user.email, diff --git a/server/src/api/socket.js b/server/src/api/socket.js index 67b9d77..1188a1c 100644 --- a/server/src/api/socket.js +++ b/server/src/api/socket.js @@ -1,7 +1,7 @@ import { Server } from 'socket.io' import { getAndStartContainer, attachContainer } from '../containers/docker.js' import Session from '../models/session.js' -import { defaultUser, genSessionJWT } from '../utils/auth.js' +import { genJWT, verifyJWT } from '../utils/auth.js' const sessions = new Map() @@ -45,7 +45,9 @@ async function connectClient(socket) { return } - const token = await genSessionJWT(session) + const token = await genJWT({ + session_id: session.id, + }) let container let stream @@ -90,8 +92,14 @@ export default (server) => { io.use(async (socket, next) => { console.log('Authenticating...') try { - const user = await defaultUser() + const { token } = socket.handshake.auth + if (!token) { + next(new Error('Token not found.')) + return + } + const user = await verifyJWT(token) // eslint-disable-next-line no-param-reassign + console.log(user) socket.user = user next() } catch (err) { diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 6388cf6..cc8c97d 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -27,7 +27,7 @@ export async function defaultUser() { return user } -export function genJwt(payload) { +export function genJWT(payload) { return new Promise((resolve, reject) => { jwt.sign( payload, diff --git a/server/swagger.yaml b/server/swagger.yaml index c895628..9f3dfe6 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -303,6 +303,10 @@ paths: /commands: post: summary: Record a command that was executed in a session + tags: + - signed-in + security: + - BearerAuth: [] requestBody: required: true content: @@ -433,13 +437,9 @@ components: Command: type: object required: - - token - pwd - command properties: - token: - type: string - description: The JWT token of the session pwd: type: string example: '/root' From 04a0b21cf614f14af47f3daf1749bd9f301ce01d Mon Sep 17 00:00:00 2001 From: wancat Date: Tue, 12 Sep 2023 00:12:22 +0800 Subject: [PATCH 05/18] fix: session response payload --- .../src/api/controllers/sessionController.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/server/src/api/controllers/sessionController.js b/server/src/api/controllers/sessionController.js index 656d12f..444f1ce 100644 --- a/server/src/api/controllers/sessionController.js +++ b/server/src/api/controllers/sessionController.js @@ -3,6 +3,26 @@ import Quest from '../../models/quest.js' import { createContainer } from '../../containers/docker.js' import SessionHandler from '../../game/sessionHandler.js' +function sessionSummary(session) { + return { + _id: session._id, + user: session.user._id || session.user, + quest: session.quest._id || session.quest, + status: session.status, + createdAt: session.createdAt, + lastActivityAt: session.lastActivityAt, + } +} + +function sessionDetail(session) { + return { + ...sessionSummary(session), + hints: session.hints, + tasks: session.tasks, + graph: session.graph, + } +} + export async function getSessionList(req, res) { try { const query = { user: req.user._id } @@ -11,27 +31,7 @@ export async function getSessionList(req, res) { } query.status = req.query.status || 'active' const sessions = await Session.find(query) - res.json( - sessions.map( - ({ - _id, - user, - quest, - status, - progress, - createdAt, - lastActivityAt, - }) => ({ - _id, - user, - quest, - status, - progress, - createdAt, - lastActivityAt, - }) - ) - ) + res.json(sessions.map(sessionSummary)) } catch (err) { console.error(err) res.status(500).json({ message: err.message }) @@ -59,7 +59,7 @@ export async function createSession(req, res) { session.addNewTasks() await newSession.save() - res.status(201).json(newSession) + res.status(201).json(sessionDetail(newSession)) } catch (err) { console.error(err) res.status(400).json({ message: err.message }) @@ -78,7 +78,7 @@ export async function getSessionById(req, res) { return } - res.json(session) + res.json(sessionDetail(session)) } catch (err) { res.status(500).json({ message: err.message }) } From b55b7aa1ec012a7bbad909dfa69119090d831f17 Mon Sep 17 00:00:00 2001 From: wancat Date: Tue, 12 Sep 2023 23:01:49 +0800 Subject: [PATCH 06/18] feat: add register, checkUsername, update swagger --- server/src/api/controllers/authController.js | 51 ++++++++++++++++++ server/src/api/routes/auth.js | 22 ++++---- server/src/api/routes/index.js | 2 +- server/src/index.js | 3 +- server/src/utils/auth.js | 6 ++- server/swagger.yaml | 57 +++++++++++++++++--- 6 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 server/src/api/controllers/authController.js diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js new file mode 100644 index 0000000..0c18c81 --- /dev/null +++ b/server/src/api/controllers/authController.js @@ -0,0 +1,51 @@ +import User from '../../models/user.js' +import { genJWT, hashPassword } from '../../utils/auth.js' + +export async function issueToken(req, res) { + const { user } = req + const token = await genJWT({ + _id: user.id, + username: user.username, + email: user.email, + }) + res.json({ token }) +} + +export async function checkUsername(req, res) { + const { username } = req.query + const exists = await User.exists({ username }) + if (exists) { + res.status(409).json({ + message: `"${username}" already exists`, + }) + } else { + res.status(200).json({ + message: `"${username}" is available`, + }) + } +} + +export async function register(req, res, next) { + const { username, password, email } = req.body + if (!(username && password && email)) { + res.status(400).json({ + message: 'username, password, and email are required', + }) + return + } + if (await User.exists({ $or: [{ username }, { email }] })) { + res.status(409).json({ + message: 'username or email already exists', + }) + return + } + const user = new User({ + username, + email, + }) + user.hashedPassword = await hashPassword(password) + await user.save() + req.user = user + res.status(201) + next() +} diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index 54bcc74..be4cb8c 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -1,24 +1,22 @@ import { Router } from 'express' import passport from 'passport' -import { genJWT } from '../../utils/auth.js' +import { + issueToken, + register, + checkUsername, +} from '../controllers/authController.js' const router = Router() -async function issueToken(req, res) { - const { user } = req - const token = await genJWT({ - _id: user.id, - username: user.username, - email: user.email, - }) - res.json({ token }) -} - router.post( - '/login/password', + '/login', passport.authenticate('local', { session: false }), issueToken ) +router.post('/register', register, issueToken) + +router.get('/check', checkUsername) + export default router diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 20db430..0c801f3 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -11,7 +11,7 @@ router.get('/', (req, res) => { res.send('Hello API!') }) -router.use(authRouter) +router.use('/auth', authRouter) router.use('/quests', quests) router.use( '/sessions', diff --git a/server/src/index.js b/server/src/index.js index 8a898ea..20e8af4 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -27,6 +27,7 @@ async function main() { const file = await fs.readFile('./swagger.yaml', 'utf8') const swaggerDocument = YAML.parse(file) + app.use(errorHandler) app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument)) app.use(express.json()) @@ -36,8 +37,6 @@ async function main() { app.use('/api/v1', apiRouter) - app.use(errorHandler) - server.listen(config.port, config.host, () => { console.log(`Server listening at http://${config.host}:${config.port}`) }) diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index cc8c97d..3e29a82 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -3,6 +3,10 @@ import bcrypt from 'bcrypt' import config from '../config.js' import User from '../models/user.js' +export function hashPassword(password) { + return bcrypt.hash(password, 10) +} + export async function createTestUser() { let user = await User.findOne({ username: 'testUser' }) if (!user) { @@ -10,7 +14,7 @@ export async function createTestUser() { username: 'testUser', email: 'test@example.com', }) - user.hashedPassword = await bcrypt.hash('testPassword', 10) + user.hashedPassword = await hashPassword('testPassword') await user.save() } } diff --git a/server/swagger.yaml b/server/swagger.yaml index 9f3dfe6..564ab6d 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -150,7 +150,7 @@ paths: type: string example: 'Unauthorized.' - /register: + /auth/register: post: summary: Register a new user requestBody: @@ -162,10 +162,16 @@ paths: properties: username: type: string + format: username + example: alex email: type: string + format: email + example: alex@example.com password: type: string + format: password + example: 123456 responses: '201': description: User created @@ -174,9 +180,10 @@ paths: schema: type: object properties: - message: + token: type: string - example: User created successfully + description: Access token + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '400': description: Bad Request content: @@ -198,7 +205,7 @@ paths: type: string example: User already exists - /login/password: + /auth/login: post: summary: Log in a user requestBody: @@ -210,8 +217,12 @@ paths: properties: username: type: string + format: username + example: alex password: type: string + format: password + example: 123456 responses: '200': description: User logged in @@ -220,12 +231,10 @@ paths: schema: type: object properties: - message: - type: string - example: Login successful token: type: string description: Access token + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '400': description: Bad Request content: @@ -247,6 +256,40 @@ paths: type: string example: Incorrect username or password + /auth/check: + get: + summary: Check if the username is already taken + parameters: + - in: query + name: username + description: username to check + required: true + schema: + type: string + format: username + example: alex + responses: + '200': + description: Username is available + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Username is available + '409': + description: Username is already taken + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Username is already taken + /profile: get: summary: Get the profile of current logged-in user From 49417c405a7a5e3c355b53d34a504c20dd3335ec Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Wed, 13 Sep 2023 14:02:28 +0800 Subject: [PATCH 07/18] feat: add login in frontend * extract icon library from main.js * extract router from main.js * failure with error in passport * add example to swagger --- app/src/components/AuthenticationPart.vue | 44 ++++++++++++++++--- app/src/components/HeaderPart.vue | 18 +++++++- app/src/library.js | 37 ++++++++++++++++ app/src/main.js | 52 +---------------------- app/src/router.js | 46 ++++++++++++++++++++ app/src/utils/api.js | 11 +++++ app/src/utils/auth.js | 47 ++++++++++++++++++++ app/src/utils/socket.js | 5 +++ server/src/api/routes/auth.js | 2 +- server/src/api/routes/index.js | 4 +- server/swagger.yaml | 1 + 11 files changed, 206 insertions(+), 61 deletions(-) create mode 100644 app/src/library.js create mode 100644 app/src/router.js create mode 100644 app/src/utils/auth.js diff --git a/app/src/components/AuthenticationPart.vue b/app/src/components/AuthenticationPart.vue index 3d2da99..d1bfef4 100644 --- a/app/src/components/AuthenticationPart.vue +++ b/app/src/components/AuthenticationPart.vue @@ -1,11 +1,36 @@ - + diff --git a/app/src/components/HeaderPart.vue b/app/src/components/HeaderPart.vue index 60e000b..54d4591 100644 --- a/app/src/components/HeaderPart.vue +++ b/app/src/components/HeaderPart.vue @@ -1,4 +1,14 @@ - + diff --git a/app/src/library.js b/app/src/library.js new file mode 100644 index 0000000..f637869 --- /dev/null +++ b/app/src/library.js @@ -0,0 +1,37 @@ +/* import the fontawesome core */ +import { library } from '@fortawesome/fontawesome-svg-core' + +/* import specific icons */ +import { + faBars, + faArrowLeft, + faList, + faGear, + faTriangleExclamation, + faExpand, + faTerminal, + faArrowRotateLeft, + faArrowRightFromBracket, +} from '@fortawesome/free-solid-svg-icons' + +import { + faLightbulb, + faCircleQuestion, + faCircleRight, +} from '@fortawesome/free-regular-svg-icons' + +/* add icons to the library */ +library.add( + faBars, + faArrowLeft, + faTriangleExclamation, + faGear, + faExpand, + faList, + faLightbulb, + faCircleQuestion, + faArrowRotateLeft, + faCircleRight, + faTerminal, + faArrowRightFromBracket +) diff --git a/app/src/main.js b/app/src/main.js index 55e4c22..d5067e7 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,59 +1,11 @@ import { createApp } from 'vue' -import { createRouter, createWebHistory } from 'vue-router' import './style.css' - -/* import the fontawesome core */ -import { library } from '@fortawesome/fontawesome-svg-core' - /* import font awesome icon component */ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' - -/* import specific icons */ -import { - faBars, - faArrowLeft, - faList, - faGear, - faTriangleExclamation, - faExpand, - faTerminal, - faArrowRotateLeft, -} from '@fortawesome/free-solid-svg-icons' - -import { - faLightbulb, - faCircleQuestion, - faCircleRight, -} from '@fortawesome/free-regular-svg-icons' +import './library' import App from './App.vue' -import GamePage from './views/GamePage.vue' -import AuthPage from './views/AuthPage.vue' - -/* add icons to the library */ -library.add( - faBars, - faArrowLeft, - faTriangleExclamation, - faGear, - faExpand, - faList, - faLightbulb, - faCircleQuestion, - faArrowRotateLeft, - faCircleRight, - faTerminal -) - -const routes = [ - { path: '/', name: 'Home', component: AuthPage }, - { path: '/game', name: 'Game', component: GamePage }, -] - -const router = createRouter({ - history: createWebHistory(), - routes, -}) +import router from './router' const app = createApp(App) diff --git a/app/src/router.js b/app/src/router.js new file mode 100644 index 0000000..ae61a28 --- /dev/null +++ b/app/src/router.js @@ -0,0 +1,46 @@ +import { createRouter, createWebHistory } from 'vue-router' +import GamePage from './views/GamePage.vue' +import AuthPage from './views/AuthPage.vue' +import { isAuthenticated } from './utils/auth' + +const routes = [ + { + path: '/', + name: 'auth', + component: AuthPage, + meta: { requiresGuest: true }, + }, + { + path: '/game', + name: 'game', + component: GamePage, + meta: { requiresAuth: true }, + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +router.beforeEach((to, from, next) => { + console.log(to, from) + const isAuthed = isAuthenticated.value + // Check if the route requires authentication + if (to.meta.requiresAuth && !isAuthed) { + // Check if the user isn't authenticated + // Redirect to the login page + next({ + name: 'auth', + query: { redirect: to.fullPath }, // Optionally pass a redirect parameter + }) + } else if (to.meta.requiresGuest && isAuthed) { + next({ + name: 'game', // Or any default authenticated user route + }) + } else { + next() // Always call next() at the end + } +}) + +export default router diff --git a/app/src/utils/api.js b/app/src/utils/api.js index 67ad944..4a5e011 100644 --- a/app/src/utils/api.js +++ b/app/src/utils/api.js @@ -2,6 +2,17 @@ import axios from 'axios' const api = axios.create({ baseURL: '/api/v1', + headers: { + 'Content-Type': 'application/json', + }, }) +export const setToken = (token) => { + if (token) { + api.defaults.headers.common.Authorization = `Bearer ${token}` + } else { + delete api.defaults.headers.common.Authorization + } +} + export default api diff --git a/app/src/utils/auth.js b/app/src/utils/auth.js new file mode 100644 index 0000000..3c0eda2 --- /dev/null +++ b/app/src/utils/auth.js @@ -0,0 +1,47 @@ +import { computed, ref, watch } from 'vue' +import api, { setToken } from './api' + +const TOKEN_KEY = 'JWT_TOKEN' + +const myStorage = window.localStorage +const myToken = ref(myStorage.getItem(TOKEN_KEY)) + +if (myToken.value) { + setToken(myToken.value) +} + +// watch the token and store it in localStorage +watch(myToken, (newToken) => { + if (newToken) { + console.log('update token:', newToken) + myStorage.setItem(TOKEN_KEY, newToken) + setToken(newToken) + } else { + myStorage.removeItem(TOKEN_KEY) + setToken(null) + } +}) + +export const getToken = () => { + return myToken.value +} + +export const login = async (username, password) => { + try { + const res = await api.post('/auth/login', { username, password }) + const { token } = res.data + myToken.value = token + return true + } catch (err) { + if (err.response?.status === 401) { + return false + } + throw err + } +} + +export const logout = () => { + myToken.value = null +} + +export const isAuthenticated = computed(() => !!myToken.value) diff --git a/app/src/utils/socket.js b/app/src/utils/socket.js index 69050ca..ec719ce 100644 --- a/app/src/utils/socket.js +++ b/app/src/utils/socket.js @@ -1,5 +1,7 @@ import io from 'socket.io-client' +import { getToken } from './auth' + class SocketWrapper { constructor() { this.socket = null @@ -16,6 +18,9 @@ class SocketWrapper { query: { session_id: session._id, }, + auth: { + token: getToken(), + }, }) this.socket.on('connect', () => { diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index be4cb8c..9295dd9 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -11,7 +11,7 @@ const router = Router() router.post( '/login', - passport.authenticate('local', { session: false }), + passport.authenticate('local', { session: false, failWithError: true }), issueToken ) diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 0c801f3..94d95dc 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -15,12 +15,12 @@ router.use('/auth', authRouter) router.use('/quests', quests) router.use( '/sessions', - passport.authenticate('jwt', { session: false }), + passport.authenticate('jwt', { session: false, failWithError: true }), sessions ) router.use( '/commands', - passport.authenticate('jwt', { session: false }), + passport.authenticate('jwt', { session: false, failWithError: true }), commands ) diff --git a/server/swagger.yaml b/server/swagger.yaml index 564ab6d..571616a 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -62,6 +62,7 @@ paths: properties: quest_id: type: string + example: helloworld responses: '201': description: session created From 3aa27ae5972237162a09b04af60b6798c3602e8b Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 14 Sep 2023 17:39:26 +0800 Subject: [PATCH 08/18] feat: add username and email validation function --- server/src/utils/auth.js | 39 +++++++++++++++++++++++++++++ server/tests/utils/auth.test.js | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 server/tests/utils/auth.test.js diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 3e29a82..9199286 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -59,3 +59,42 @@ export function verifyJWT(token) { }) }) } + +export function isValidUsername(username) { + // Define a regular expression pattern for a valid Linux username. + const usernamePattern = /^[a-z][a-z0-9_-]*$/ + + // Test the username against the pattern. + if (!usernamePattern.test(username)) return false + + // check if the username is between 1 and 32 characters + if (username.length < 1 || username.length > 32) { + return false + } + + const reservedWords = [ + 'root', + 'admin', + 'administrator', + 'sysadmin', + 'system', + 'daemon', + 'bin', + 'nobody', + 'nogroup', + 'guest', + 'user', + ] + if (reservedWords.includes(username)) { + return false + } + return true +} + +export function isValidEmail(email) { + // Define a regular expression pattern for a valid email address. + const emailPattern = /^[\w-.+]+@([\w][\w-]*\.)+[\w-]{2,}$/ + + // Test the email against the pattern. + return emailPattern.test(email) +} diff --git a/server/tests/utils/auth.test.js b/server/tests/utils/auth.test.js new file mode 100644 index 0000000..3df34ed --- /dev/null +++ b/server/tests/utils/auth.test.js @@ -0,0 +1,44 @@ +import { isValidEmail, isValidUsername } from '../../src/utils/auth' + +test('check valid username', () => { + const valid = ['alex', 'alex123', 'john_doe', 'john-doe', 'rudeus'] + + const invalid = ['root', 'admin', '12michael', 'Justin', 'testUser'] + + valid.forEach((username) => { + expect(isValidUsername(username)).toBe(true) + }) + + invalid.forEach((username) => { + expect(isValidUsername(username)).toBe(false) + }) +}) + +test('check valid email', () => { + const validEmails = [ + 'user@example.com', + 'user.name@example.co.uk', + 'user1234@email.domain', + 'user+tag@example.net', + 'user@subdomain.example.com', + 'user@123.45.67.89', + ] + + const invalidEmails = [ + 'user@.com', + '@example.com', + 'user@-invalid-domain.com', + 'user@.123', + 'user@.123.45.67', + ] + + validEmails.forEach((email) => { + console.log(email) + expect(isValidEmail(email)).toBe(true) + }) + + invalidEmails.forEach((email) => { + console.log(email) + expect(isValidEmail(email)).toBe(false) + }) +}) From f1510df13eb4072b9f3e4a60aec252020e37243a Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 14 Sep 2023 17:43:07 +0800 Subject: [PATCH 09/18] feat: enhance checkUser api * determine whether it's email or username, and if it exists --- server/src/api/controllers/authController.js | 40 ++++++++++++++++---- server/src/utils/auth.js | 20 ++-------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js index 0c18c81..62802d0 100644 --- a/server/src/api/controllers/authController.js +++ b/server/src/api/controllers/authController.js @@ -1,5 +1,10 @@ import User from '../../models/user.js' -import { genJWT, hashPassword } from '../../utils/auth.js' +import { + genJWT, + hashPassword, + isValidEmail, + isValidUsername, +} from '../../utils/auth.js' export async function issueToken(req, res) { const { user } = req @@ -13,16 +18,37 @@ export async function issueToken(req, res) { export async function checkUsername(req, res) { const { username } = req.query - const exists = await User.exists({ username }) - if (exists) { - res.status(409).json({ - message: `"${username}" already exists`, - }) - } else { + if (isValidEmail(username)) { + if (await User.exists({ email: username })) { + res.status(409).json({ + type: 'email', + message: `"${username}" already exists`, + }) + return + } res.status(200).json({ + type: 'email', message: `"${username}" is available`, }) + return } + if (isValidUsername(username)) { + if (await User.exists({ username })) { + res.status(409).json({ + type: 'username', + message: `"${username}" already exists`, + }) + return + } + res.status(200).json({ + type: 'username', + message: `"${username}" is available`, + }) + return + } + res.status(400).json({ + message: 'invalid username or email', + }) } export async function register(req, res, next) { diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 9199286..fde5770 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -8,29 +8,17 @@ export function hashPassword(password) { } export async function createTestUser() { - let user = await User.findOne({ username: 'testUser' }) + let user = await User.findOne({ username: 'alex' }) if (!user) { user = new User({ - username: 'testUser', - email: 'test@example.com', + username: 'alex', + email: 'alex@example.com', }) - user.hashedPassword = await hashPassword('testPassword') + user.hashedPassword = await hashPassword('123456') await user.save() } } -export async function defaultUser() { - let user = await User.findOne({ username: 'defaultUser' }) - if (!user) { - user = new User({ - username: 'defaultUser', - email: 'jackson@example.com', - }) - await user.save() - } - return user -} - export function genJWT(payload) { return new Promise((resolve, reject) => { jwt.sign( From f5c465ab69f5c5d7e3163e56c69c6448c0eb0b46 Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 14 Sep 2023 18:54:40 +0800 Subject: [PATCH 10/18] feat: add login & signup flow --- app/src/components/AuthenticationPart.vue | 126 +++++++++++++++++-- app/src/utils/auth.js | 18 +++ server/src/api/controllers/authController.js | 44 ++++--- 3 files changed, 166 insertions(+), 22 deletions(-) diff --git a/app/src/components/AuthenticationPart.vue b/app/src/components/AuthenticationPart.vue index d1bfef4..c9b8eb0 100644 --- a/app/src/components/AuthenticationPart.vue +++ b/app/src/components/AuthenticationPart.vue @@ -1,28 +1,114 @@