mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
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:
1 parent
fe3f4ddc73
commit
3cea54ce71
11 files changed
+194
-6
No files matched your search
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"dockerode": "^3.3.5",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.1",
|
||||
"marked": "^5.1.1",
|
||||
"mongoose": "^7.4.0",
|
||||
"socket.io": "^4.7.1",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -291,6 +291,24 @@ paths:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
|
||||
/commands:
|
||||
post:
|
||||
summary: Record a command that was executed in a session
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Command'
|
||||
responses:
|
||||
'201':
|
||||
description: Command recorded successfully
|
||||
'400':
|
||||
description: Invalid request body
|
||||
'401':
|
||||
description: Unauthorized
|
||||
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
@@ -422,6 +440,28 @@ components:
|
||||
source:
|
||||
type: string
|
||||
|
||||
Command:
|
||||
type: object
|
||||
required:
|
||||
- token
|
||||
- pwd
|
||||
- command
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: The JWT token of the session
|
||||
pwd:
|
||||
type: string
|
||||
example: '/root'
|
||||
description: The current working directory
|
||||
command:
|
||||
type: string
|
||||
example: 'ls -al /'
|
||||
description: The command that was executed
|
||||
output:
|
||||
type: string
|
||||
description: The output of the command
|
||||
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
|
||||
@@ -1323,6 +1323,11 @@ bson@^5.4.0:
|
||||
resolved "https://registry.yarnpkg.com/bson/-/bson-5.4.0.tgz#0eea77276d490953ad8616b483298dbff07384c6"
|
||||
integrity sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@@ -1698,6 +1703,13 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@@ -3218,6 +3230,33 @@ 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.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#81d8c901c112c24e497a55daf6b2be1225b40145"
|
||||
integrity sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash "^4.17.21"
|
||||
ms "^2.1.1"
|
||||
semver "^7.3.8"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
kareem@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.5.1.tgz#7b8203e11819a8e77a34b3517d3ead206764d15d"
|
||||
@@ -4010,7 +4049,7 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
safe-buffer@5.2.1, safe-buffer@~5.2.0:
|
||||
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
@@ -4041,7 +4080,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.5.3:
|
||||
semver@^7.3.5, semver@^7.3.6, semver@^7.3.8, semver@^7.5.3:
|
||||
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==
|
||||
|
||||
Reference in new issue
Block a user