diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index 738d4ea..d6c2d35 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -6,6 +6,8 @@ COPY server/package.json ./server/package.json COPY packages/file-graph/package.json ./packages/file-graph/package.json RUN yarn install --frozen-lockfile +COPY server/nodemon.json ./server/nodemon.json + ENV NODE_ENV=development ENV HOST=0.0.0.0 ENV PORT=3000 diff --git a/server/nodemon.json b/server/nodemon.json new file mode 100644 index 0000000..cc2ba22 --- /dev/null +++ b/server/nodemon.json @@ -0,0 +1,4 @@ +{ + "watch": ["src", "swagger.yaml"], + "ext": "js,yaml" +} diff --git a/server/package.json b/server/package.json index ea35e64..60519fb 100644 --- a/server/package.json +++ b/server/package.json @@ -13,8 +13,10 @@ "@linux-odyssey/file-graph": "*", "bcrypt": "^5.1.1", "commander": "^11.0.0", + "connect-mongo": "^5.0.0", "dockerode": "^3.3.5", "express": "^4.18.2", + "express-session": "^1.17.3", "jsonwebtoken": "^9.0.1", "minimist": "^1.2.8", "mongoose": "^7.4.0", diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js index 803feba..7e3aaec 100644 --- a/server/src/api/controllers/authController.js +++ b/server/src/api/controllers/authController.js @@ -92,8 +92,15 @@ export async function register(req, res, next) { user.hashedPassword = await hashPassword(password) await user.save() req.user = user - res.status(201) - next() + req.login(user, (err) => { + if (err) { + next(err) + return + } + res.status(201).json({ + message: 'user created', + }) + }) } catch (err) { console.error(err) res.status(500).json({ @@ -101,3 +108,16 @@ export async function register(req, res, next) { }) } } + +export function logout(req, res) { + req.logout((err) => { + if (err) { + console.error(err) + res.status(500).json({ + message: 'error logging out', + }) + return + } + res.json({ message: 'success' }) + }) +} diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index 9295dd9..36879ee 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -2,20 +2,24 @@ import { Router } from 'express' import passport from 'passport' import { - issueToken, register, checkUsername, + logout, } from '../controllers/authController.js' const router = Router() router.post( '/login', - passport.authenticate('local', { session: false, failWithError: true }), - issueToken + passport.authenticate('local', { failureMessage: true }), + (req, res) => { + res.json({ message: 'success' }) + } ) -router.post('/register', register, issueToken) +router.post('/register', register) + +router.post('/logout', logout) router.get('/check', checkUsername) diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 94d95dc..33cbdf2 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -6,6 +6,7 @@ import commands from './commands.js' import authRouter from './auth.js' const router = Router() +router.use(passport.authenticate('session')) router.get('/', (req, res) => { res.send('Hello API!') @@ -13,15 +14,11 @@ router.get('/', (req, res) => { router.use('/auth', authRouter) router.use('/quests', quests) -router.use( - '/sessions', - passport.authenticate('jwt', { session: false, failWithError: true }), - sessions -) -router.use( - '/commands', - passport.authenticate('jwt', { session: false, failWithError: true }), - commands -) + +const authZone = Router() +authZone.use('/sessions', sessions) +authZone.use('/commands', commands) + +router.use(authZone) export default router diff --git a/server/src/auth/jwtStrategy.js b/server/src/auth/jwtStrategy.js deleted file mode 100644 index f1b2cd4..0000000 --- a/server/src/auth/jwtStrategy.js +++ /dev/null @@ -1,13 +0,0 @@ -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 index fc1fe04..a498832 100644 --- a/server/src/auth/passport.js +++ b/server/src/auth/passport.js @@ -1,6 +1,20 @@ import passport from 'passport' import PasswordStrategy from './passwordStrategy.js' -import JwtStrategy from './jwtStrategy.js' passport.use(PasswordStrategy) -passport.use(JwtStrategy) + +passport.serializeUser((user, done) => { + process.nextTick(() => { + done(null, { + _id: user._id, + username: user.username, + email: user.email, + }) + }) +}) + +passport.deserializeUser((user, done) => { + process.nextTick(() => { + done(null, user) + }) +}) diff --git a/server/src/db.js b/server/src/db.js index 842ce6f..7522e35 100644 --- a/server/src/db.js +++ b/server/src/db.js @@ -3,10 +3,15 @@ import config from './config.js' export default async function connectDB() { try { - await mongoose.connect(config.db) + const db = await mongoose.connect(config.db, { + useNewUrlParser: true, + useUnifiedTopology: true, + }) console.log('Connected to MongoDB') + return db.connection.getClient() } catch (err) { console.error(err) process.exit(1) + return null } } diff --git a/server/src/index.js b/server/src/index.js index 20e8af4..5c88208 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -11,14 +11,12 @@ import apiRouter from './api/routes/index.js' import loadAndUpdateQuests from './utils/quest.js' import config from './config.js' import errorHandler from './middleware/error.js' +import sessionMiddleware from './middleware/session.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() + const db = await connectDB() await createTestUser() @@ -27,8 +25,19 @@ async function main() { const file = await fs.readFile('./swagger.yaml', 'utf8') const swaggerDocument = YAML.parse(file) + + const app = express() + const server = http.createServer(app) + socketServer(server) app.use(errorHandler) - app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument)) + app.use(sessionMiddleware(db)) + app.use( + '/api-docs', + swaggerUI.serve, + swaggerUI.setup(swaggerDocument, { + withCredentials: true, + }) + ) app.use(express.json()) app.get('/', (req, res) => { diff --git a/server/src/middleware/session.js b/server/src/middleware/session.js new file mode 100644 index 0000000..b889903 --- /dev/null +++ b/server/src/middleware/session.js @@ -0,0 +1,20 @@ +import session from 'express-session' +import MongoStore from 'connect-mongo' + +import config from '../config.js' + +export default (db) => { + console.log(db.constructor.name) + return session({ + secret: config.jwtSecret, + resave: false, + saveUninitialized: false, + cookie: { + httpOnly: true, + }, + store: MongoStore.create({ + mongoUrl: config.db, + collectionName: 'loginSessions', + }), + }) +} diff --git a/server/swagger.yaml b/server/swagger.yaml index 571616a..6cd6147 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -24,7 +24,7 @@ paths: - signed-in summary: list existing sessions of current user security: - - BearerAuth: [] + - cookieAuth: [] parameters: - in: query name: quest_id @@ -52,7 +52,7 @@ paths: tags: - signed-in security: - - BearerAuth: [] + - cookieAuth: [] requestBody: description: session data content: @@ -86,7 +86,7 @@ paths: - signed-in summary: get a specific session of current user by ID security: - - BearerAuth: [] + - cookieAuth: [] parameters: - in: path name: session_id @@ -117,7 +117,7 @@ paths: - signed-in summary: delete a specific session by ID security: - - BearerAuth: [] + - cookieAuth: [] parameters: - in: path name: session_id @@ -176,6 +176,11 @@ paths: responses: '201': description: User created + headers: + Set-Cookie: + schema: + type: string + example: connect.sid=s%3Aq1Z; Path=/; HttpOnly; SameSite=Strict; Secure content: application/json: schema: @@ -227,15 +232,19 @@ paths: responses: '200': description: User logged in + headers: + Set-Cookie: + schema: + type: string + example: connect.sid=s%3Aq1Z; Path=/; HttpOnly; SameSite=Strict; Secure content: application/json: schema: type: object properties: - token: + message: type: string - description: Access token - example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + example: Logged in successfully '400': description: Bad Request content: @@ -257,6 +266,25 @@ paths: type: string example: Incorrect username or password + /auth/logout: + post: + summary: Log out a user + tags: + - signed-in + security: + - cookieAuth: [] + responses: + '200': + description: User logged out + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Logged out successfully + /auth/check: get: summary: Check if the username is already taken @@ -291,19 +319,6 @@ paths: type: string example: Username is already taken - /profile: - get: - summary: Get the profile of current logged-in user - security: - - BearerAuth: [] - responses: - '200': - description: Get user's profile - content: - application/json: - schema: - $ref: '#/components/schemas/User' - /quests: get: summary: Get quests list @@ -350,7 +365,7 @@ paths: tags: - signed-in security: - - BearerAuth: [] + - cookieAuth: [] requestBody: required: true content: @@ -537,7 +552,7 @@ components: example: 'Unix Spirit' securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT + cookieAuth: + type: apiKey + in: cookie + name: connect.sid diff --git a/yarn.lock b/yarn.lock index bc7bafa..d30aaa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1209,6 +1209,16 @@ array.prototype.flatmap@^1.3.1: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" +asn1.js@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + asn1@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -1365,6 +1375,11 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +bn.js@^4.0.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -1632,6 +1647,14 @@ 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== +connect-mongo@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/connect-mongo/-/connect-mongo-5.0.0.tgz#2a902d293d369a4baff2e988634cda6f8c0bf721" + integrity sha512-s93jiP6GkRApn5duComx6RLwtP23YrulPxShz+8peX7svd6Q+MS8nKLhKCCazbP92C13eTVaIOxgeLt0ezIiCg== + dependencies: + debug "^4.3.1" + kruptein "^3.0.0" + 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" @@ -1664,16 +1687,16 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie@0.4.2, cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + cookie@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -cookie@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - cors@~2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -1723,7 +1746,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -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: +debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.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== @@ -1793,7 +1816,7 @@ delegates@^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: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -2296,6 +2319,20 @@ expect@^29.6.1: jest-message-util "^29.6.1" jest-util "^29.6.1" +express-session@^1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36" + integrity sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw== + dependencies: + cookie "0.4.2" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.1" + uid-safe "~2.1.5" + express@^4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -2775,7 +2812,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3495,6 +3532,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +kruptein@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/kruptein/-/kruptein-3.0.6.tgz#9cf976225af4bc1fbeba069dbb24f17eebad974d" + integrity sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA== + dependencies: + asn1.js "^5.4.1" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -3687,6 +3731,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -3974,6 +4023,11 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4298,6 +4352,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -4433,7 +4492,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -5010,6 +5069,13 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"