diff --git a/app/src/store/session.js b/app/src/store/session.js index eae3b08..f904f51 100644 --- a/app/src/store/session.js +++ b/app/src/store/session.js @@ -45,15 +45,13 @@ async function setSession(session) { export async function createSession() { console.log('Creating a new session...') - const res = await api.post('/sessions', { quest_id: store.questId }) + const res = await api.post('/sessions', { questId: store.questId }) const { data } = res await setSession(data) } async function getActiveSession(questId) { - const res = await api.post('/sessions/active', { - quest_id: questId, - }) + const res = await api.post('/sessions/active', { questId }) const session = res.data try { await setSession(session) diff --git a/app/src/utils/socket.js b/app/src/utils/socket.js index f242485..5411ab1 100644 --- a/app/src/utils/socket.js +++ b/app/src/utils/socket.js @@ -12,7 +12,7 @@ class SocketWrapper { this.socket = io('', { query: { - session_id: session._id, + sessionId: session._id, }, }) @@ -23,7 +23,7 @@ class SocketWrapper { this.socket.once('terminal', () => { resolve() }) - this.socket.once('error', reject) + this.socket.once('connect_error', reject) this.bindListeners() }) } diff --git a/packages/client/index.js b/packages/client/index.js index a732579..4505774 100644 --- a/packages/client/index.js +++ b/packages/client/index.js @@ -27,7 +27,7 @@ function debug(...args) { async function createSession() { console.log('Creating a new session...') const res = await api.post('/sessions', { - quest_id: 'helloworld', + questId: 'helloworld', }) const { data } = await res if (!data._id) { @@ -68,7 +68,7 @@ async function connect(sessionId, cookie) { Cookie: cookie, }, query: { - session_id: sessionId, + sessionId, }, }) diff --git a/server/src/api/controllers/commandController.js b/server/src/api/controllers/commandController.js index 6e7aa4d..ca19ac3 100644 --- a/server/src/api/controllers/commandController.js +++ b/server/src/api/controllers/commandController.js @@ -13,7 +13,7 @@ export async function newCommand(req, res) { return } - const sessionId = req.user.session_id + const { sessionId } = req.user const session = await Session.findById(sessionId) @@ -64,7 +64,7 @@ export async function newCommand(req, res) { } export async function completedCommand(req, res) { - const sessionId = req.user.session_id + const { sessionId } = req.user const session = await Session.findById(sessionId) diff --git a/server/src/api/controllers/questController.js b/server/src/api/controllers/questController.js index 745756d..098a77c 100644 --- a/server/src/api/controllers/questController.js +++ b/server/src/api/controllers/questController.js @@ -1,4 +1,5 @@ import { Quest } from '@linux-odyssey/models' +import { matchedData } from 'express-validator' export async function getQuests(req, res) { const quests = await Quest.find({}).sort({ order: 1 }) @@ -12,8 +13,8 @@ export async function getQuests(req, res) { } export async function getQuestDetail(req, res) { - const { id } = req.params - const quest = await Quest.findById(id) + const { questId } = matchedData(req) + const quest = await Quest.findById(questId) if (!quest) { res.status(404).json({ message: 'Quest not found.' }) return diff --git a/server/src/api/controllers/sessionController.js b/server/src/api/controllers/sessionController.js index 1fdbf32..69ce2af 100644 --- a/server/src/api/controllers/sessionController.js +++ b/server/src/api/controllers/sessionController.js @@ -28,10 +28,11 @@ function sessionDetail(session) { export async function getSessionList(req, res) { try { const query = { user: req.user._id } - if (req.query.quest_id) { - query.quest = req.query.quest_id + const { questId, status } = matchedData(req) + if (questId) { + query.quest = questId } - query.status = req.query.status || 'active' + query.status = status || 'active' const sessions = await Session.find(query) res.json(sessions.map(sessionSummary)) } catch (err) { @@ -41,9 +42,9 @@ export async function getSessionList(req, res) { } export async function createSession(req, res) { - const { quest_id } = matchedData(req) + const { questId } = matchedData(req) try { - const session = await createNewSession(req.user, quest_id) + const session = await createNewSession(req.user, questId) res.status(201).json(sessionDetail(session)) } catch (err) { console.error(err) @@ -53,8 +54,9 @@ export async function createSession(req, res) { export async function getSessionById(req, res) { try { + const { sessionId } = matchedData(req) const session = await Session.findOne({ - _id: req.params.id, + _id: sessionId, user: req.user._id, }) @@ -69,28 +71,10 @@ export async function getSessionById(req, res) { } } -export async function deleteSessionById(req, res) { - try { - const session = await Session.findOneAndDelete({ - _id: req.params.id, - user: req.user._id, - }) - - if (!session) { - res.status(404).json({ message: 'Session not found.' }) - return - } - - res.status(204).end() - } catch (err) { - res.status(500).json({ message: err.message }) - } -} - export async function getOrCreateSession(req, res) { - const { quest_id } = matchedData(req) + const { questId } = matchedData(req) try { - const session = await getOrCreateActiveSession(req.user, quest_id) + const session = await getOrCreateActiveSession(req.user, questId) res.json(sessionDetail(session)) } catch (err) { console.error(err) diff --git a/server/src/api/routes/questRouter.js b/server/src/api/routes/questRouter.js index 00fda55..4749f54 100644 --- a/server/src/api/routes/questRouter.js +++ b/server/src/api/routes/questRouter.js @@ -1,9 +1,10 @@ import { Router } from 'express' import * as questController from '../controllers/questController.js' +import { questIdValidator } from '../validators/questValidator.js' const router = Router() router.get('/', questController.getQuests) -router.get('/:id', questController.getQuestDetail) +router.get('/:questId', questIdValidator, questController.getQuestDetail) export default router diff --git a/server/src/api/routes/sessionRouter.js b/server/src/api/routes/sessionRouter.js index aad0db8..ddc7cdb 100644 --- a/server/src/api/routes/sessionRouter.js +++ b/server/src/api/routes/sessionRouter.js @@ -4,11 +4,13 @@ import { sessionRateLimit } from '../../middleware/rateLimiter.js' import { createSessionValidator, activeSessionValidator, + sessionListValidator, + sessionDetailValidator, } from '../validators/sessionValidator.js' const router = Router() -router.get('/', sessionController.getSessionList) +router.get('/', sessionListValidator, sessionController.getSessionList) router.post( '/', createSessionValidator, @@ -20,7 +22,6 @@ router.post( activeSessionValidator, sessionController.getOrCreateSession ) -router.get('/:id', sessionController.getSessionById) -router.delete('/:id', sessionController.deleteSessionById) +router.get('/:id', sessionDetailValidator, sessionController.getSessionById) export default router diff --git a/server/src/api/socket.js b/server/src/api/socket.js index 276970e..cd482c9 100644 --- a/server/src/api/socket.js +++ b/server/src/api/socket.js @@ -1,8 +1,10 @@ import { Server } from 'socket.io' +import validator from 'validator' import { Session } from '@linux-odyssey/models' import { getAndStartContainer, attachContainer } from '../containers/docker.js' import SessionMiddleware from '../middleware/session.js' import { genJWT } from '../utils/auth.js' +import { checkSessionId } from './validators/sessionValidator.js' const sessions = new Map() @@ -26,33 +28,26 @@ export function pushToSession(sessionId, event, ...args) { } } -async function connectClient(socket) { +async function connectClient(socket, next) { const user = socket.request.session?.passport?.user if (!user) { - socket.send('User not found.') - socket.disconnect() + next(new Error('User not found.')) return } - const sessionId = socket.handshake.query.session_id - if (!sessionId) { - socket.emit('error', 'Session ID not found.') - socket.disconnect() + const { sessionId } = socket.handshake.query + if (!validator.isMongoId(sessionId)) { + next(new Error('Invalid Session ID.')) return } - const session = await Session.findOne({ - _id: sessionId, - user, - status: 'active', - }) + const session = await Session.findById(sessionId) if (!session) { - socket.emit('error', 'Session not found.') - socket.disconnect() + next(new Error('Session not found.')) return } const token = await genJWT({ - session_id: session.id, + sessionId: session.id, }) let container @@ -61,8 +56,7 @@ async function connectClient(socket) { container = await getAndStartContainer(session.containerId) stream = await attachContainer(container, { token }) } catch (err) { - socket.emit('error', 'Failed to start container.') - socket.disconnect() + next(new Error('Failed to start container.')) return } @@ -95,6 +89,10 @@ export default (server) => { const io = new Server(server) io.engine.use(SessionMiddleware) + io.engine.use(checkSessionId()) io.on('connection', connectClient) + io.on('connect_error', (err) => { + console.error(err) + }) } diff --git a/server/src/api/validators/questValidator.js b/server/src/api/validators/questValidator.js new file mode 100644 index 0000000..be610a8 --- /dev/null +++ b/server/src/api/validators/questValidator.js @@ -0,0 +1,11 @@ +import { check } from 'express-validator' +import { noError } from '../../middleware/validator.js' + +export const checkQuestId = () => + check('questId').notEmpty().isString().isAlphanumeric().isLength({ max: 64 }) + +export const questIdValidator = [checkQuestId(), noError] + +export default { + questIdValidator, +} diff --git a/server/src/api/validators/sessionValidator.js b/server/src/api/validators/sessionValidator.js index 73a7bd2..fc80dde 100644 --- a/server/src/api/validators/sessionValidator.js +++ b/server/src/api/validators/sessionValidator.js @@ -1,8 +1,20 @@ import { check } from 'express-validator' +import { checkQuestId } from './questValidator.js' import { noError } from '../../middleware/validator.js' -const checkQuestId = () => - check('quest_id').notEmpty().isString().isLowercase().isLength({ max: 64 }) +const checkSessionStatus = () => + check('status') + .isString() + .custom((value) => ['active', 'inactive', 'finished'].includes(value)) +export const checkSessionId = () => + check('sessionId').notEmpty().isString().isMongoId() + +export const sessionListValidator = [ + checkQuestId(), + checkSessionStatus(), + noError, +] +export const sessionDetailValidator = [checkSessionId(), noError] export const createSessionValidator = [checkQuestId(), noError] export const activeSessionValidator = [checkQuestId(), noError] diff --git a/server/swagger.yaml b/server/swagger.yaml index fab89f7..bae7705 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -143,44 +143,6 @@ paths: properties: message: type: string - delete: - tags: - - signed-in - summary: delete a specific session by ID - security: - - cookieAuth: [] - parameters: - - in: path - name: sessionId - required: true - schema: - type: string - format: objectId - example: 64b7a3a3c182b4826dd1aaa1 - responses: - '204': - description: Session successfully deleted - - '404': - description: Session not found - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: 'Session not found.' - '401': - description: Unauthorized - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: 'Unauthorized.' /auth/register: post: