feat: connect terminal service to backend

feat: put terminal service into container

refactor: separate main into terminal and websocket

feat: create zsh using commander in home

feat: pass backendUrl to container

feat: pass token to ws server to cmd-hook

feat: gen terminal access token for frontend

feat: success to connect through traefik

fix: handle invalid jwt

feat: connect to session container via traefik
This commit is contained in:
Justin Lin committed 2025-05-05 19:53:33 +08:00
1 parent 0b08a9a5d9
commit 5e81db2759
20 files changed
+281 -209

No files matched your search

@@ -7,11 +7,29 @@ import { asyncHandler } from '../../middleware/error'
import { questManager } from '../../models/quest'
import { CLIFileExistenceChecker } from '../../containers/cli'
import config from '../../config'
import { verifyJWT } from '../../utils/auth'
export const newCommand = asyncHandler(async (req: Request, res: Response) => {
if (!config.isProduction) {
console.log('newCommand', JSON.stringify(req.body, null, 2))
}
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
res.status(401).json({ message: 'Unauthorized' })
return
}
let sessionId: string
try {
const decoded = await verifyJWT(token)
sessionId = decoded.sessionId
if (!sessionId) {
res.status(401).json({ message: 'Unauthorized' })
return
}
} catch (error) {
res.status(401).json({ message: 'Unauthorized' })
return
}
const body = commandSchema.safeParse(req.body)
if (!body.success) {
res.status(400).json({ message: 'Invalid command', errors: body.error })
@@ -19,7 +37,6 @@ export const newCommand = asyncHandler(async (req: Request, res: Response) => {
}
const command = body.data
const { sessionId } = req.user as any
const session = await Session.findById(sessionId)
if (!session) {
res.status(404).json({ message: 'session not found' })
+1 -6
View File
@@ -17,12 +17,7 @@ if (!config.isProduction) {
})
}
router.use(
'/commands',
passport.authenticate('jwt', { session: false }),
authRequired,
commands
)
router.use('/commands', commands)
router.use('/auth', authRouter)
router.get('/leaderboard', leaderboardController)
+4 -34
View File
@@ -1,12 +1,9 @@
import { Server } from 'socket.io'
import type { Socket } from 'socket.io'
import { Duplex } from 'stream'
import validator from 'validator'
import type { Server as HttpServer } from 'http'
import { ISession, Session } from '../../../packages/models'
import { getAndStartContainer, attachContainer } from '../containers/docker.js'
import SessionMiddleware from '../middleware/session.js'
import { genJWT } from '../utils/auth.js'
import logger from '../utils/logger.js'
type SocketCallback = (event: string, ...args: any[]) => void
@@ -61,41 +58,16 @@ async function connectContainer(socket: Socket, next: (err?: Error) => void) {
return
}
const token = await genJWT({
sessionId: session.id,
})
if (!session.containerId) {
next(new Error('Session has no container.'))
return
}
try {
const container = await getAndStartContainer(session.containerId)
const stream = await attachContainer(container, { token })
socket.data.context = {
session,
stream,
}
next()
} catch (err) {
logger.error(err)
next(new Error('Failed to connect to container.'))
socket.data.context = {
session,
}
next()
}
function onConnect(socket: Socket) {
const { session, stream } = socket.data.context as {
const { session } = socket.data.context as {
session: ISession
stream: Duplex
}
stream.on('data', (chunk: Buffer) => {
socket.emit('terminal', chunk.toString())
})
socket.on('terminal', function incoming(message) {
stream.write(message)
})
const socketCallback = (event: string, ...data: any[]) => {
socket.emit(event, ...data)
@@ -104,9 +76,7 @@ function onConnect(socket: Socket) {
listenToSession(session._id.toString(), socketCallback)
socket.on('disconnect', () => {
stream.write('exit\n')
removeFromSession(session._id.toString(), socketCallback)
stream.destroy()
})
}