mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Merge pull request #5 from lancatlin/server-commands-api
Server commands api
This commit is contained in:
28 files changed
+529
-79
No files matched your search
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.git
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
vite.config.js
|
||||
.gitignore
|
||||
data
|
||||
data
|
||||
mongo-init.js
|
||||
@@ -6,6 +6,9 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
.bash_history
|
||||
.viminfo
|
||||
*.swp
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM node
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
COPY server/package.json ./server/
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
ARG DOCKER_GID=140
|
||||
|
||||
RUN groupadd -g $DOCKER_GID docker && usermod -aG docker node
|
||||
|
||||
USER node
|
||||
|
||||
ENV NODE_ENV=development
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=3000
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "yarn", "dev" ]
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
containers=$(docker ps -a | grep 'quest-' | awk '{print $1}')
|
||||
echo $containers
|
||||
|
||||
docker rm -f $containers
|
||||
@@ -0,0 +1,42 @@
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: lancatlin/linux-odyssey
|
||||
restart: on-failure
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
networks:
|
||||
- internal
|
||||
- players
|
||||
ports:
|
||||
- '3000:3000'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- MONGO_URL=mongodb://user:user-password@db:27017/odyssey-test
|
||||
- DOCKER_NETWORK=linux-odyssey-players
|
||||
db:
|
||||
image: mongo
|
||||
restart: always
|
||||
networks:
|
||||
- internal
|
||||
ports:
|
||||
- '27017:27017'
|
||||
volumes:
|
||||
- mongo:/data/db
|
||||
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=admin
|
||||
- MONGO_INITDB_ROOT_PASSWORD=admin-password
|
||||
- MONGO_USER=user
|
||||
- MONGO_PASS=user-password
|
||||
- MONGO_DB=odyssey-test
|
||||
|
||||
networks:
|
||||
internal:
|
||||
players:
|
||||
name: linux-odyssey-players
|
||||
|
||||
volumes:
|
||||
mongo:
|
||||
+16
-4
@@ -1,20 +1,32 @@
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: lancatlin/linux-odyssey-server
|
||||
build: .
|
||||
restart: on-failure
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
networks:
|
||||
- internal
|
||||
- players
|
||||
ports:
|
||||
- '3000:3000'
|
||||
links:
|
||||
- db
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./server:/app/server
|
||||
environment:
|
||||
- MONGO_URL=mongodb://db:27017/odyssey-test
|
||||
- DOCKER_NETWORK=linux-odyssey-players
|
||||
db:
|
||||
image: mongo
|
||||
restart: always
|
||||
networks:
|
||||
- internal
|
||||
ports:
|
||||
- '27017:27017'
|
||||
volumes:
|
||||
- ./data:/data/db
|
||||
|
||||
networks:
|
||||
internal:
|
||||
players:
|
||||
name: linux-odyssey-players
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
docker compose logs app -f --no-log-prefix
|
||||
@@ -0,0 +1,10 @@
|
||||
const user = process.env.MONGO_USER
|
||||
const password = process.env.MONGO_PASS
|
||||
const dbName = process.env.MONGO_DB
|
||||
|
||||
db = db.getSiblingDB(dbName)
|
||||
db.createUser({
|
||||
user: user,
|
||||
pwd: password,
|
||||
roles: [{ role: 'readWrite', db: dbName }],
|
||||
})
|
||||
@@ -10,8 +10,10 @@
|
||||
"test": "yarn node --experimental-vm-modules $(yarn bin jest)"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^11.0.0",
|
||||
"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,16 @@
|
||||
FROM ubuntu:latest
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
vim \
|
||||
jq
|
||||
|
||||
RUN adduser --disabled-password --gecos '' rudeus
|
||||
|
||||
USER rudeus
|
||||
|
||||
WORKDIR /home/rudeus
|
||||
|
||||
COPY ./home /home/rudeus/
|
||||
|
||||
CMD [ "/bin/bash" ]
|
||||
@@ -0,0 +1,30 @@
|
||||
# This function will be called before each command is executed
|
||||
preexec() {
|
||||
[ -n "$COMP_LINE" ] && return # do nothing if completing
|
||||
[ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return # do nothing if completing
|
||||
local command="$BASH_COMMAND"
|
||||
local pwd="$PWD"
|
||||
|
||||
if [[ $command =~ ^(vim|top|less|more|nano) ]]; then
|
||||
local payload=$(printf '{"token": "%s", "pwd": "%s", "command": "%s"}' "$TOKEN" "$pwd" "$command")
|
||||
curl -X POST $API_ENDPOINT/api/v1/commands -H 'accept: */*' -H 'Content-Type: application/json' -d "$payload"
|
||||
return
|
||||
fi
|
||||
|
||||
# Execute the command, capture output and exit code
|
||||
local output=$( { eval $command 2>&1; echo $? > /tmp/exit_code; })
|
||||
local exit_code=$(cat /tmp/exit_code)
|
||||
|
||||
local output_esc=$(echo $output | jq -Rs .)
|
||||
|
||||
# Store command in variable
|
||||
local payload=$(printf '{"token": "%s", "pwd": "%s", "command": "%s", "exit_code": "%s", "output": %s}' "$TOKEN" "$pwd" "$command" "$exit_code" "$output_esc")
|
||||
|
||||
# Send command to the API
|
||||
curl -X POST $API_ENDPOINT/api/v1/commands -H 'accept: */*' -H 'Content-Type: application/json' -d "$payload" || echo $payload
|
||||
|
||||
BASH_COMMAND=""
|
||||
}
|
||||
|
||||
# Install the trap
|
||||
trap 'preexec' DEBUG
|
||||
@@ -0,0 +1,37 @@
|
||||
/* 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) {
|
||||
console.log('new command:', req.body)
|
||||
const { token, command, pwd, exit_code, 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,
|
||||
exit_code,
|
||||
output,
|
||||
})
|
||||
|
||||
await c.save()
|
||||
|
||||
res.status(201).end()
|
||||
}
|
||||
@@ -22,13 +22,13 @@ export async function createSession(req, res) {
|
||||
return
|
||||
}
|
||||
|
||||
const newSession = new Session({
|
||||
user: req.user,
|
||||
quest,
|
||||
containerId: `quest-${quest.id}-${req.user.username}-${Date.now()}`,
|
||||
})
|
||||
|
||||
try {
|
||||
const newSession = new Session({
|
||||
user: req.user,
|
||||
quest,
|
||||
containerId: `quest-${quest.id}-${req.user.username}-${Date.now()}`,
|
||||
})
|
||||
|
||||
const session = await newSession.save()
|
||||
res.status(201).json(session)
|
||||
} catch (err) {
|
||||
|
||||
@@ -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
|
||||
+23
-10
@@ -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)
|
||||
@@ -21,26 +21,39 @@ export default (server) => {
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
console.log('Connected to the client.')
|
||||
const session = await Session.findOne({
|
||||
_id: socket.handshake.query.session_id,
|
||||
user: socket.user,
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
socket.send('Session not found.')
|
||||
const sessionId = socket.handshake.query.session_id
|
||||
if (!sessionId) {
|
||||
socket.send('Session ID not found.')
|
||||
socket.disconnect()
|
||||
return
|
||||
}
|
||||
let session
|
||||
try {
|
||||
session = await Session.findOne({
|
||||
_id: sessionId,
|
||||
user: socket.user,
|
||||
})
|
||||
if (!session) {
|
||||
socket.send('Session not found.')
|
||||
socket.disconnect()
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
socket.send(err.message)
|
||||
socket.disconnect()
|
||||
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)
|
||||
})
|
||||
|
||||
socket.on('message', function incoming(message) {
|
||||
console.log(`From client: ${message}`)
|
||||
stream.write(message)
|
||||
})
|
||||
|
||||
|
||||
+105
-27
@@ -1,37 +1,115 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { stdin, stdout, exit } from 'process'
|
||||
import { io } from 'socket.io-client'
|
||||
import { program } from 'commander'
|
||||
import { get } from './utils/env.js'
|
||||
|
||||
stdin.setRawMode(true)
|
||||
stdin.resume()
|
||||
stdin.setEncoding('utf8')
|
||||
|
||||
const sessionId = process.argv[2]
|
||||
console.log(`Session ID: ${sessionId}`)
|
||||
async function createdSession() {
|
||||
console.log('Creating a new session...')
|
||||
const res = await fetch(`${program.opts().host}/api/v1/sessions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quest_id: 'helloworld',
|
||||
}),
|
||||
})
|
||||
const data = await res.json()
|
||||
console.log('Created Session ID:', data._id)
|
||||
return data
|
||||
}
|
||||
|
||||
const socket = io('ws://localhost:3000', {
|
||||
query: {
|
||||
session_id: sessionId,
|
||||
},
|
||||
})
|
||||
async function getSessionList() {
|
||||
const res = await fetch(`${program.opts().host}/api/v1/sessions`)
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
socket.on('open', function open() {
|
||||
console.log('Connected to the server.')
|
||||
})
|
||||
|
||||
socket.on('message', function incoming(data) {
|
||||
stdout.write(data)
|
||||
})
|
||||
|
||||
socket.on('close', function close() {
|
||||
console.log('Disconnected from the server.')
|
||||
exit()
|
||||
})
|
||||
|
||||
stdin.on('data', (key) => {
|
||||
if (key === '\u0003') {
|
||||
socket.close()
|
||||
exit()
|
||||
} else {
|
||||
socket.send(key)
|
||||
async function lastSession() {
|
||||
const sessions = await getSessionList()
|
||||
if (sessions.length === 0) {
|
||||
console.log(
|
||||
'No session found. Please use --create to create a new session.'
|
||||
)
|
||||
exit(1)
|
||||
}
|
||||
})
|
||||
const session = sessions[sessions.length - 1]
|
||||
console.log(
|
||||
`Last Session ID: ${session._id}, Quest: ${session.quest}, Created At: ${session.createdAt}`
|
||||
)
|
||||
return session
|
||||
}
|
||||
|
||||
async function connect(sessionId) {
|
||||
console.log(`Session ID: ${sessionId}`)
|
||||
console.log('Use Ctrl + D to exit.')
|
||||
if (!sessionId) exit()
|
||||
|
||||
const socket = io(program.opts().host, {
|
||||
query: {
|
||||
session_id: sessionId,
|
||||
},
|
||||
})
|
||||
|
||||
socket.on('open', function open() {
|
||||
console.log('Connected to the server.')
|
||||
})
|
||||
|
||||
socket.on('message', function incoming(data) {
|
||||
stdout.write(data)
|
||||
})
|
||||
|
||||
socket.on('close', function close() {
|
||||
console.log('Disconnected from the server.')
|
||||
exit()
|
||||
})
|
||||
|
||||
stdin.on('data', (key) => {
|
||||
if (key === '\u0004') {
|
||||
socket.close()
|
||||
exit()
|
||||
} else {
|
||||
socket.send(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(program.opts().session)
|
||||
// main()
|
||||
|
||||
const sessionId =
|
||||
program.opts().session ||
|
||||
(program.opts().create ? (await createdSession())._id : null) ||
|
||||
(await lastSession())._id
|
||||
|
||||
await connect(sessionId)
|
||||
}
|
||||
|
||||
program
|
||||
.option('-s, --session <string>', 'Session ID')
|
||||
.option('-c, --create', 'Create a new session')
|
||||
.option(
|
||||
'-h, --host <string>',
|
||||
'Server host',
|
||||
get('API_ENDPOINT', 'http://localhost:3000')
|
||||
)
|
||||
.action(main)
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('List all sessions')
|
||||
.action(async () => {
|
||||
const sessions = await getSessionList()
|
||||
sessions.forEach(({ _id, quest, createdAt }) => {
|
||||
console.log(`${_id} ${quest} ${createdAt}`)
|
||||
})
|
||||
exit()
|
||||
})
|
||||
|
||||
program.parse()
|
||||
@@ -1,13 +1,10 @@
|
||||
function get(key, defaultValue) {
|
||||
const value = process.env[key]
|
||||
if (value != null) {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
import { get } from './utils/env.js'
|
||||
|
||||
export default {
|
||||
host: get('HOST', 'localhost'),
|
||||
port: get('PORT', 3000),
|
||||
db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'),
|
||||
jwtSecret: get('JWT_SECRET', 'secret'),
|
||||
dockerNetwork: get('DOCKER_NETWORK', 'linux-odyssey-players'),
|
||||
isProduction: process.env.NODE_ENV === 'production',
|
||||
}
|
||||
+2
-1
@@ -6,6 +6,7 @@ export default async function connectDB() {
|
||||
await mongoose.connect(config.db)
|
||||
console.log('Connected to MongoDB')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import connectDB from './db.js'
|
||||
import apiRouter from './api/routes/index.js'
|
||||
import loadAndUpdateQuests from './utils/quest.js'
|
||||
import config from './config.js'
|
||||
import errorHandler from './middleware/error.js'
|
||||
|
||||
async function main() {
|
||||
const app = express()
|
||||
@@ -28,6 +29,8 @@ 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}`)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export default function errorHandler(err, req, res, next) {
|
||||
console.error(err.stack) // Log error stack trace to the console
|
||||
res.status(500).send({ message: err.message }) // Send error response
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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,
|
||||
},
|
||||
exit_code: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
+25
-20
@@ -1,9 +1,12 @@
|
||||
import Docker from 'dockerode'
|
||||
import config from '../config.js'
|
||||
|
||||
const engine = new Docker()
|
||||
|
||||
const questPath = '/home/zeko/src/linux-odyssey/server/quests'
|
||||
|
||||
const containerOptions = {
|
||||
Image: 'ubuntu',
|
||||
Image: 'lancatlin/linux-odyssey:helloworld',
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
@@ -11,42 +14,43 @@ const containerOptions = {
|
||||
Cmd: ['/bin/bash'],
|
||||
OpenStdin: true,
|
||||
StdinOnce: false,
|
||||
HostConfig: {
|
||||
Binds: config.isProduction
|
||||
? []
|
||||
: [`${questPath}/helloworld/home:/home/rudeus`],
|
||||
},
|
||||
}
|
||||
|
||||
export async function createContainer() {
|
||||
const container = await engine.createContainer({
|
||||
...containerOptions,
|
||||
})
|
||||
await container.start()
|
||||
return container
|
||||
}
|
||||
|
||||
export async function getContainer(id) {
|
||||
const container = await engine.getContainer(id)
|
||||
await container.start()
|
||||
return container
|
||||
}
|
||||
const network = engine.getNetwork(config.dockerNetwork)
|
||||
|
||||
export async function getOrCreateContainer(id) {
|
||||
console.log(`Getting container: ${id}`)
|
||||
let container
|
||||
try {
|
||||
const container = engine.getContainer(id)
|
||||
container = engine.getContainer(id)
|
||||
if (!(await container.inspect()).State.Running) {
|
||||
await container.start()
|
||||
}
|
||||
return container
|
||||
} catch (error) {
|
||||
const container = await engine.createContainer({
|
||||
...containerOptions,
|
||||
name: id,
|
||||
})
|
||||
try {
|
||||
container = await engine.createContainer({
|
||||
...containerOptions,
|
||||
name: id,
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
await network.connect({ Container: container.id })
|
||||
|
||||
await container.start()
|
||||
console.log(container.id)
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
||||
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 +58,7 @@ export async function attachContainer(container) {
|
||||
AttachStderr: true,
|
||||
Cmd: ['/bin/bash'],
|
||||
Tty: true,
|
||||
Env: [`TOKEN=${token}`, `API_ENDPOINT=http://app:3000`],
|
||||
})
|
||||
|
||||
const execOutput = await exec.start({
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export function get(key, defaultValue) {
|
||||
const value = process.env[key]
|
||||
if (value != null) {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
@@ -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"
|
||||
@@ -1481,6 +1486,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==
|
||||
|
||||
commander@^11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67"
|
||||
integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==
|
||||
|
||||
commander@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||
@@ -1698,6 +1708,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 +3235,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 +4054,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 +4085,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