fix: remove webshell

This commit is contained in:
Innei
2023-08-11 00:37:18 +08:00
parent a44842a9b0
commit 2b36d9a301
11 changed files with 0 additions and 305 deletions

View File

@@ -39,7 +39,6 @@ import { PageModule } from './modules/page/page.module'
import { PageProxyModule } from './modules/pageproxy/pageproxy.module'
import { PostModule } from './modules/post/post.module'
import { ProjectModule } from './modules/project/project.module'
import { PTYModule } from './modules/pty/pty.module'
import { RecentlyModule } from './modules/recently/recently.module'
import { RenderEjsModule } from './modules/render/render.module'
import { SayModule } from './modules/say/say.module'
@@ -85,7 +84,6 @@ import { RedisModule } from './processors/redis/redis.module'
PageModule,
PostModule,
ProjectModule,
PTYModule,
RecentlyModule,
SayModule,
SearchModule,

View File

@@ -93,9 +93,6 @@ export async function bootstrap() {
return
}
if (isDev) {
consola.debug(`[${prefix + pid}] OpenApi: ${url}/api-docs`)
}
consola.success(`[${prefix + pid}] Server listen on: ${url}`)
consola.success(`[${prefix + pid}] Admin Dashboard: ${url}/qaqdmin`)
consola.success(

View File

@@ -42,9 +42,6 @@ export enum BusinessEvents {
STDOUT = 'STDOUT',
PTY = 'pty',
PTY_MESSAGE = 'pty_message',
// activity
ACTIVITY_LIKE = 'activity_like',
}

View File

@@ -56,11 +56,6 @@ export const generateDefaultConfig: () => IConfig = () => ({
background: '',
gaodemapKey: null!,
},
terminalOptions: {
enable: false,
password: null!,
script: null!,
},
textOptions: {
macros: true,
},

View File

@@ -252,35 +252,6 @@ export class AdminExtraDto {
gaodemapKey?: string
}
@JSONSchema({ title: '终端设定' })
export class TerminalOptionsDto {
@IsOptional()
@IsBoolean()
@JSONSchemaToggleField('开启 WebShell')
enable: boolean
@IsOptional()
@IsString()
@Transform(({ value }) =>
typeof value == 'string' && value.length == 0 ? null : value,
)
@Exclude({ toPlainOnly: true })
@JSONSchemaPasswordField('设定密码', {
description: '密码为空则不启用密码验证',
})
@Encrypt
password?: string
@IsOptional()
@IsString()
@JSONSchemaPlainField('前置脚本', {
'ui:options': {
type: 'textarea',
},
})
script?: string
}
@JSONSchema({ title: '友链设定' })
export class FriendLinkOptionsDto {
@IsBoolean()

View File

@@ -13,7 +13,6 @@ import {
FriendLinkOptionsDto,
MailOptionsDto,
SeoDto,
TerminalOptionsDto,
TextOptionsDto,
ThirdPartyServiceIntegrationDto,
UrlDto,
@@ -66,10 +65,6 @@ export abstract class IConfig {
@Type(() => AlgoliaSearchOptionsDto)
algoliaSearchOptions: Required<AlgoliaSearchOptionsDto>
@Type(() => TerminalOptionsDto)
@ValidateNested()
terminalOptions: Required<TerminalOptionsDto>
@Type(() => FeatureListDto)
@ValidateNested()
featureList: Required<FeatureListDto>

View File

@@ -1,17 +0,0 @@
import { Get } from '@nestjs/common'
import { ApiController } from '~/common/decorators/api-controller.decorator'
import { Auth } from '~/common/decorators/auth.decorator'
import { PTYService } from './pty.service'
@Auth()
@ApiController({ path: 'pty' })
export class PTYController {
constructor(private readonly service: PTYService) {}
@Get('/record')
async getPtyLoginRecord() {
return this.service.getLoginRecord()
}
}

View File

@@ -1,186 +0,0 @@
import { isNil, pick } from 'lodash'
import { nanoid } from 'nanoid'
import { spawn } from 'node-pty'
import { Socket } from 'socket.io'
import { quiet } from 'zx-cjs'
import type {
GatewayMetadata,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets'
import type { IPty } from 'node-pty'
import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets'
import { DEMO_MODE } from '~/app.config'
import { BusinessEvents } from '~/constants/business-event.constant'
import { RedisKeys } from '~/constants/cache.constant'
import { DATA_DIR } from '~/constants/path.constant'
import { AuthService } from '~/modules/auth/auth.service'
import { ConfigsService } from '~/modules/configs/configs.service'
import { createAuthGateway } from '~/processors/gateway/shared/auth.gateway'
import { JWTService } from '~/processors/helper/helper.jwt.service'
import { CacheService } from '~/processors/redis/cache.service'
import { getIp, getRedisKey } from '~/utils'
const AuthGateway = createAuthGateway({ namespace: 'pty', authway: 'jwt' })
@WebSocketGateway<GatewayMetadata>({ namespace: 'pty' })
export class PTYGateway
extends AuthGateway
implements OnGatewayConnection, OnGatewayDisconnect
{
constructor(
protected readonly jwtService: JWTService,
protected readonly authService: AuthService,
protected readonly cacheService: CacheService,
protected readonly configService: ConfigsService,
) {
super(jwtService, authService)
}
socket2ptyMap = new WeakMap<Socket, IPty>()
@SubscribeMessage('pty')
async pty(
client: Socket,
data?: { password?: string; cols: number; rows: number },
) {
if (DEMO_MODE) {
client.send(
this.gatewayMessageFormat(
BusinessEvents.PTY_MESSAGE,
'PTY 在演示模式下不可用',
),
)
return
}
const password = data?.password
const terminalOptions = await this.configService.get('terminalOptions')
if (!terminalOptions.enable) {
client.send(
this.gatewayMessageFormat(BusinessEvents.PTY_MESSAGE, 'PTY 已禁用'),
)
return
}
const isValidPassword = isNil(terminalOptions.password)
? true
: password === terminalOptions.password
if (!isValidPassword) {
if (typeof password === 'undefined' || password === '') {
client.send(
this.gatewayMessageFormat(
BusinessEvents.PTY_MESSAGE,
'PTY 验证未通过:需要密码验证',
10000,
),
)
} else {
client.send(
this.gatewayMessageFormat(
BusinessEvents.PTY_MESSAGE,
'PTY 验证未通过:密码错误',
10001,
),
)
}
return
}
const zsh = await quiet(nothrow($`zsh --version`))
const fish = await quiet(nothrow($`fish --version`))
const pty = spawn(
os.platform() === 'win32'
? 'powershell.exe'
: zsh.exitCode == 0
? 'zsh'
: fish.exitCode == 0
? 'fish'
: 'bash',
[],
{
cwd: DATA_DIR,
cols: data?.cols || 30,
rows: data?.rows || 80,
env: pick(process.env, [
'PATH',
'EDITOR',
'SHELL',
'USER',
'VISUAL',
'LANG',
'TERM',
'LANGUAGE',
// other
'N_PREFIX',
'N_PRESERVE_NPM',
]) as any,
},
)
const nid = nanoid()
const ip =
client.handshake.headers['x-forwarded-for'] ||
client.handshake.address ||
getIp(client.request) ||
client.conn.remoteAddress
this.cacheService.getClient().hset(
getRedisKey(RedisKeys.PTYSession),
nid,
`${new Date().toISOString()},${ip}`,
)
pty.onExit(async () => {
const hvalue = await this.cacheService
.getClient()
.hget(getRedisKey(RedisKeys.PTYSession), nid)
if (hvalue) {
this.cacheService
.getClient()
.hset(
getRedisKey(RedisKeys.PTYSession),
nid,
`${hvalue},${new Date().toISOString()}`,
)
}
})
if (terminalOptions.script) {
pty.write(terminalOptions.script)
pty.write('\n')
}
pty.onData((data) => {
client.send(this.gatewayMessageFormat(BusinessEvents.PTY, data))
})
this.socket2ptyMap.set(client, pty)
}
@SubscribeMessage('pty-input')
async ptyInput(client: Socket, data: string) {
const pty = this.socket2ptyMap.get(client)
if (pty) {
pty.write(data)
}
}
@SubscribeMessage('pty-exit')
async ptyExit(client: Socket) {
const pty = this.socket2ptyMap.get(client)
if (pty) {
pty.kill()
}
this.socket2ptyMap.delete(client)
}
handleDisconnect(client: Socket) {
this.ptyExit(client)
super.handleDisconnect(client)
}
}

View File

@@ -1,13 +0,0 @@
import { Module } from '@nestjs/common'
import { AuthModule } from '../auth/auth.module'
import { PTYController } from './pty.controller'
import { PTYGateway } from './pty.gateway'
import { PTYService } from './pty.service'
@Module({
imports: [AuthModule],
controllers: [PTYController],
providers: [PTYService, PTYGateway],
})
export class PTYModule {}

View File

@@ -1,38 +0,0 @@
import { Injectable } from '@nestjs/common'
import { RedisKeys } from '~/constants/cache.constant'
import { CacheService } from '~/processors/redis/cache.service'
import { getRedisKey } from '~/utils'
@Injectable()
export class PTYService {
constructor(private readonly cacheService: CacheService) {}
async getLoginRecord() {
const redis = this.cacheService.getClient()
const keys = await redis.hkeys(getRedisKey(RedisKeys.PTYSession))
const values = await Promise.all(
keys.map(async (key) => {
return redis.hget(getRedisKey(RedisKeys.PTYSession), key)
}),
)
return values
.filter(Boolean)
.map((value: string) => {
const [startTime, ip, endTime] = value.split(',') as [
string,
string,
string | undefined,
]
return {
startTime: new Date(startTime),
ip,
endTime: endTime === '' ? null : endTime,
}
})
.sort((a, b) => b.startTime.getTime() - a.startTime.getTime())
}
}

View File

@@ -37,8 +37,6 @@ services:
image: mongo
volumes:
- ./data/db:/data/db
ports:
- '127.0.0.1:3344:27017'
networks:
- app-network
restart: always
@@ -46,8 +44,6 @@ services:
image: redis
container_name: redis
ports:
- '127.0.0.1:3333:6379'
networks:
- app-network
restart: always