feat: add new API for recording executed commands

- Add the `jsonwebtoken` dependency to the server's `package.json`
- Add a new file `commandController.js` to the server's API controllers
- Add a new route `/commands` to the server's API routes
- Add a new file `commands.js` to the server's API routes
- Modify the `index.js` file in the server's API routes to import and use the `commands.js` route
- Add a `newCommand` function to the `commandController.js` file
- Add a `Command` model to the `command.js` file in the server's models
- Add the `genSessionJWT` and `verifySessionJWT` functions to the `auth.js` file in the server's utils
- Modify the `attachContainer` function in the `docker.js` file in the server's utils to include a `token` parameter
- Modify the `swagger.yaml` file to include a new route `/commands` for recording executed commands in a session

Signed-off-by: wancat <wancat@wancat.cc>
This commit is contained in:
wancat committed 2023-07-24 16:39:04 +08:00
1 parent fe3f4ddc73
commit 3cea54ce71
11 files changed
+194 -6

No files matched your search

@@ -0,0 +1,35 @@
/* eslint-disable import/prefer-default-export */
import Command from '../../models/command.js'
import Session from '../../models/session.js'
import { verifySessionJWT } from '../../utils/auth.js'
export async function newCommand(req, res) {
const { token, command, pwd, output } = req.body
let sessionId
try {
const decoded = await verifySessionJWT(token)
sessionId = decoded.session_id
} catch (err) {
res.status(401).json({ message: 'token not valid' })
return
}
const session = await Session.findById(sessionId)
if (!session) {
res.status(401).json({ message: 'session not found' })
return
}
const c = new Command({
session,
command,
pwd,
output,
})
await c.save()
res.status(201).end()
}
+8
View File
@@ -0,0 +1,8 @@
import { Router } from 'express'
import * as commandController from '../controllers/commandController.js'
const router = Router()
router.post('/', commandController.newCommand)
export default router
+2
View File
@@ -1,6 +1,7 @@
import { Router } from 'express'
import sessions from './sessions.js'
import quests from './quests.js'
import commands from './commands.js'
import { authMiddleware } from '../../middleware/auth.js'
const router = Router()
@@ -11,5 +12,6 @@ router.get('/', (req, res) => {
router.use('/sessions', authMiddleware, sessions)
router.use('/quests', quests)
router.use('/commands', commands)
export default router
+4 -2
View File
@@ -1,7 +1,7 @@
import { Server } from 'socket.io'
import { getOrCreateContainer, attachContainer } from '../utils/docker.js'
import Session from '../models/session.js'
import { defaultUser } from '../utils/auth.js'
import { defaultUser, genSessionJWT } from '../utils/auth.js'
export default (server) => {
const io = new Server(server)
@@ -32,8 +32,10 @@ export default (server) => {
return
}
const token = await genSessionJWT(session)
const container = await getOrCreateContainer(session.containerId)
const stream = await attachContainer(container)
const stream = await attachContainer(container, { token })
stream.on('data', (chunk) => {
socket.send(chunk)
+1
View File
@@ -10,4 +10,5 @@ export default {
host: get('HOST', 'localhost'),
port: get('PORT', 3000),
db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'),
jwtSecret: get('JWT_SECRET', 'secret'),
}
+29
View File
@@ -0,0 +1,29 @@
import { model, Schema } from 'mongoose'
const Command = model(
'Command',
new Schema(
{
session: {
type: Schema.ObjectId,
ref: 'Session',
required: true,
},
pwd: {
type: String,
required: true,
},
command: {
type: String,
required: true,
},
output: {
type: String,
required: false,
},
},
{ timestamps: true }
)
)
export default Command
+31 -1
View File
@@ -1,4 +1,5 @@
/* eslint-disable import/prefer-default-export */
import jwt from 'jsonwebtoken'
import config from '../config.js'
import User from '../models/user.js'
export async function defaultUser() {
@@ -12,3 +13,32 @@ export async function defaultUser() {
}
return user
}
export function genSessionJWT(session) {
return new Promise((resolve, reject) => {
jwt.sign(
{ session_id: session.id },
config.jwtSecret,
{ expiresIn: '1h' },
(err, token) => {
if (err) {
reject(err)
} else {
resolve(token)
}
}
)
})
}
export function verifySessionJWT(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, config.jwtSecret, (err, decoded) => {
if (err) {
reject(err)
} else {
resolve(decoded)
}
})
})
}
+2 -1
View File
@@ -46,7 +46,7 @@ export async function getOrCreateContainer(id) {
}
}
export async function attachContainer(container) {
export async function attachContainer(container, { token }) {
// Create an exec instance with bash shell
const exec = await container.exec({
AttachStdin: true,
@@ -54,6 +54,7 @@ export async function attachContainer(container) {
AttachStderr: true,
Cmd: ['/bin/bash'],
Tty: true,
Env: [`TOKEN=${token}`],
})
const execOutput = await exec.start({