✨ 支持IP白名单访问限制
This commit is contained in:
parent
997761f2fc
commit
9f04c8adbb
@ -1,2 +1,5 @@
|
|||||||
# 启动debug日志 0:关闭 1:开启
|
# 启动debug日志 0:关闭 1:开启
|
||||||
DEBUG=1
|
DEBUG=1
|
||||||
|
|
||||||
|
# 访问IP限制
|
||||||
|
allowedIPs=['127.0.0.1']
|
@ -10,11 +10,11 @@ consola.info('路由白名单:', whitePath)
|
|||||||
const useAuth = async ({ request, res }, next) => {
|
const useAuth = async ({ request, res }, next) => {
|
||||||
const { path, headers: { token } } = request
|
const { path, headers: { token } } = request
|
||||||
consola.info('verify path: ', path)
|
consola.info('verify path: ', path)
|
||||||
if(whitePath.includes(path)) return next()
|
if (whitePath.includes(path)) return next()
|
||||||
if(!token) return res.fail({ msg: '未登录', status: 403 })
|
if (!token) return res.fail({ msg: '未登录', status: 403 })
|
||||||
// 验证token
|
// 验证token
|
||||||
const { code, msg } = await verifyAuthSync(token, request.ip)
|
const { code, msg } = await verifyAuthSync(token, request.ip)
|
||||||
switch(code) {
|
switch (code) {
|
||||||
case 1:
|
case 1:
|
||||||
return await next()
|
return await next()
|
||||||
case -1:
|
case -1:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const ipFilter = require('./ipFilter') // IP过滤
|
||||||
const responseHandler = require('./response') // 统一返回格式, 错误捕获
|
const responseHandler = require('./response') // 统一返回格式, 错误捕获
|
||||||
const useAuth = require('./auth') // 鉴权
|
const useAuth = require('./auth') // 鉴权
|
||||||
// const useCors = require('./cors') // 处理跨域[暂时禁止]
|
// const useCors = require('./cors') // 处理跨域[暂时禁止]
|
||||||
@ -8,8 +9,8 @@ const useStatic = require('./static') // 静态目录
|
|||||||
const compress = require('./compress') // br/gzip压缩
|
const compress = require('./compress') // br/gzip压缩
|
||||||
const history = require('./history') // vue-router的history模式
|
const history = require('./history') // vue-router的history模式
|
||||||
|
|
||||||
// 注意注册顺序
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
|
ipFilter,
|
||||||
compress,
|
compress,
|
||||||
history,
|
history,
|
||||||
useStatic, // staic先注册,不然会被jwt拦截
|
useStatic, // staic先注册,不然会被jwt拦截
|
||||||
|
16
server/app/middlewares/ipFilter.js
Normal file
16
server/app/middlewares/ipFilter.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 白名单IP
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const { isAllowedIp } = require('../utils/tools')
|
||||||
|
|
||||||
|
const htmlPath = path.join(__dirname, '../template/ipForbidden.html')
|
||||||
|
const ipForbiddenHtml = fs.readFileSync(htmlPath, 'utf8')
|
||||||
|
|
||||||
|
const ipFilter = async (ctx, next) => {
|
||||||
|
// console.log('requestIP:', ctx.request.ip)
|
||||||
|
if (isAllowedIp(ctx.request.ip)) return await next()
|
||||||
|
ctx.status = 403
|
||||||
|
ctx.body = ipForbiddenHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ipFilter
|
@ -5,7 +5,7 @@ const useStatic = koaStatic(staticDir, {
|
|||||||
maxage: 1000 * 60 * 60 * 24 * 30,
|
maxage: 1000 * 60 * 60 * 24 * 30,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
setHeaders: (res, path) => {
|
setHeaders: (res, path) => {
|
||||||
if(path && path.endsWith('.html')) {
|
if (path && path.endsWith('.html')) {
|
||||||
res.setHeader('Cache-Control', 'max-age=0')
|
res.setHeader('Cache-Control', 'max-age=0')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ const { httpPort } = require('./config')
|
|||||||
const middlewares = require('./middlewares')
|
const middlewares = require('./middlewares')
|
||||||
const wsTerminal = require('./socket/terminal')
|
const wsTerminal = require('./socket/terminal')
|
||||||
const wsSftp = require('./socket/sftp')
|
const wsSftp = require('./socket/sftp')
|
||||||
// const wsHostStatus = require('./socket/host-status')
|
|
||||||
const wsClientInfo = require('./socket/clients')
|
const wsClientInfo = require('./socket/clients')
|
||||||
const wsOnekey = require('./socket/onekey')
|
const wsOnekey = require('./socket/onekey')
|
||||||
const { throwError } = require('./utils/tools')
|
const { throwError } = require('./utils/tools')
|
||||||
@ -25,7 +24,6 @@ function serverHandler(app, server) {
|
|||||||
app.proxy = true // 用于nginx反代时获取真实客户端ip
|
app.proxy = true // 用于nginx反代时获取真实客户端ip
|
||||||
wsTerminal(server) // 终端
|
wsTerminal(server) // 终端
|
||||||
wsSftp(server) // sftp
|
wsSftp(server) // sftp
|
||||||
// wsHostStatus(server) // 终端侧边栏host信息(单个host)
|
|
||||||
wsOnekey(server) // 一键指令
|
wsOnekey(server) // 一键指令
|
||||||
wsClientInfo(server) // 客户端信息
|
wsClientInfo(server) // 客户端信息
|
||||||
app.context.throwError = throwError // 常用方法挂载全局ctx上
|
app.context.throwError = throwError // 常用方法挂载全局ctx上
|
||||||
|
@ -3,6 +3,7 @@ const { io: ClientIO } = require('socket.io-client')
|
|||||||
const { readHostList } = require('../utils/storage')
|
const { readHostList } = require('../utils/storage')
|
||||||
const { clientPort } = require('../config')
|
const { clientPort } = require('../config')
|
||||||
const { verifyAuthSync } = require('../utils/verify-auth')
|
const { verifyAuthSync } = require('../utils/verify-auth')
|
||||||
|
const { isAllowedIp } = require('../utils/tools')
|
||||||
|
|
||||||
let clientSockets = []
|
let clientSockets = []
|
||||||
let clientsData = {}
|
let clientsData = {}
|
||||||
@ -66,9 +67,14 @@ module.exports = (httpServer) => {
|
|||||||
|
|
||||||
serverIo.on('connection', (socket) => {
|
serverIo.on('connection', (socket) => {
|
||||||
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
||||||
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
||||||
|
if (!isAllowedIp(requestIP)) {
|
||||||
|
socket.emit('ip_forbidden', 'IP地址不在白名单中')
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
socket.on('init_clients_data', async ({ token }) => {
|
socket.on('init_clients_data', async ({ token }) => {
|
||||||
const { code, msg } = await verifyAuthSync(token, clientIp)
|
const { code, msg } = await verifyAuthSync(token, requestIP)
|
||||||
if (code !== 1) {
|
if (code !== 1) {
|
||||||
socket.emit('token_verify_fail', msg || '鉴权失败')
|
socket.emit('token_verify_fail', msg || '鉴权失败')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
const { Server: ServerIO } = require('socket.io')
|
|
||||||
const { io: ClientIO } = require('socket.io-client')
|
|
||||||
const { clientPort } = require('../config')
|
|
||||||
const { verifyAuthSync } = require('../utils/verify-auth')
|
|
||||||
|
|
||||||
let hostSockets = {}
|
|
||||||
|
|
||||||
function getHostInfo(serverSocket, host) {
|
|
||||||
let hostSocket = ClientIO(`http://${ host }:${ clientPort }`, {
|
|
||||||
path: '/client/os-info',
|
|
||||||
forceNew: false,
|
|
||||||
timeout: 5000,
|
|
||||||
reconnectionDelay: 3000,
|
|
||||||
reconnectionAttempts: 3
|
|
||||||
})
|
|
||||||
// 将与客户端连接的socket实例保存起来,web端断开时关闭与客户端的连接
|
|
||||||
hostSockets[serverSocket.id] = hostSocket
|
|
||||||
|
|
||||||
hostSocket
|
|
||||||
.on('connect', () => {
|
|
||||||
consola.success('host-status-socket连接成功:', host)
|
|
||||||
hostSocket.on('client_data', (data) => {
|
|
||||||
serverSocket.emit('host_data', data)
|
|
||||||
})
|
|
||||||
hostSocket.on('client_error', () => {
|
|
||||||
serverSocket.emit('host_data', null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.on('connect_error', (error) => {
|
|
||||||
consola.error('host-status-socket连接[失败]:', host, error.message)
|
|
||||||
serverSocket.emit('host_data', null)
|
|
||||||
})
|
|
||||||
.on('disconnect', () => {
|
|
||||||
consola.info('host-status-socket连接[断开]:', host)
|
|
||||||
serverSocket.emit('host_data', null)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = (httpServer) => {
|
|
||||||
const serverIo = new ServerIO(httpServer, {
|
|
||||||
path: '/host-status',
|
|
||||||
cors: {
|
|
||||||
origin: '*' // 需配置跨域
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
serverIo.on('connection', (serverSocket) => {
|
|
||||||
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
|
||||||
let clientIp = serverSocket.handshake.headers['x-forwarded-for'] || serverSocket.handshake.address
|
|
||||||
serverSocket.on('init_host_data', async ({ token, host }) => {
|
|
||||||
// 校验登录态
|
|
||||||
const { code, msg } = await verifyAuthSync(token, clientIp)
|
|
||||||
if(code !== 1) {
|
|
||||||
serverSocket.emit('token_verify_fail', msg || '鉴权失败')
|
|
||||||
serverSocket.disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取客户端数据
|
|
||||||
getHostInfo(serverSocket, host)
|
|
||||||
|
|
||||||
consola.info('host-status-socket连接socketId: ', serverSocket.id, 'host-status-socket已连接数: ', Object.keys(hostSockets).length)
|
|
||||||
|
|
||||||
// 关闭连接
|
|
||||||
serverSocket.on('disconnect', () => {
|
|
||||||
// 当web端与服务端断开连接时, 服务端与每个客户端的socket也应该断开连接
|
|
||||||
let socket = hostSockets[serverSocket.id]
|
|
||||||
socket.close && socket.close()
|
|
||||||
delete hostSockets[serverSocket.id]
|
|
||||||
consola.info('host-status-socket剩余连接数: ', Object.keys(hostSockets).length)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ const { readSSHRecord, readHostList, writeOneKeyRecord } = require('../utils/sto
|
|||||||
const { verifyAuthSync } = require('../utils/verify-auth')
|
const { verifyAuthSync } = require('../utils/verify-auth')
|
||||||
const { shellThrottle } = require('../utils/tools')
|
const { shellThrottle } = require('../utils/tools')
|
||||||
const { AESDecryptSync } = require('../utils/encrypt')
|
const { AESDecryptSync } = require('../utils/encrypt')
|
||||||
|
const { isAllowedIp } = require('../utils/tools')
|
||||||
|
|
||||||
const execStatusEnum = {
|
const execStatusEnum = {
|
||||||
connecting: '连接中',
|
connecting: '连接中',
|
||||||
@ -90,7 +91,12 @@ module.exports = (httpServer) => {
|
|||||||
})
|
})
|
||||||
serverIo.on('connection', (socket) => {
|
serverIo.on('connection', (socket) => {
|
||||||
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
||||||
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
||||||
|
if (!isAllowedIp(requestIP)) {
|
||||||
|
socket.emit('ip_forbidden', 'IP地址不在白名单中')
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
consola.success('onekey-terminal websocket 已连接')
|
consola.success('onekey-terminal websocket 已连接')
|
||||||
if (isExecuting) {
|
if (isExecuting) {
|
||||||
socket.emit('create_fail', '正在执行中, 请稍后再试')
|
socket.emit('create_fail', '正在执行中, 请稍后再试')
|
||||||
@ -99,7 +105,7 @@ module.exports = (httpServer) => {
|
|||||||
}
|
}
|
||||||
isExecuting = true
|
isExecuting = true
|
||||||
socket.on('create', async ({ hosts, token, command, timeout }) => {
|
socket.on('create', async ({ hosts, token, command, timeout }) => {
|
||||||
const { code } = await verifyAuthSync(token, clientIp)
|
const { code } = await verifyAuthSync(token, requestIP)
|
||||||
if (code !== 1) {
|
if (code !== 1) {
|
||||||
socket.emit('token_verify_fail')
|
socket.emit('token_verify_fail')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
|
@ -7,6 +7,7 @@ const { sftpCacheDir } = require('../config')
|
|||||||
const { verifyAuthSync } = require('../utils/verify-auth')
|
const { verifyAuthSync } = require('../utils/verify-auth')
|
||||||
const { AESDecryptSync } = require('../utils/encrypt')
|
const { AESDecryptSync } = require('../utils/encrypt')
|
||||||
const { readSSHRecord, readHostList } = require('../utils/storage')
|
const { readSSHRecord, readHostList } = require('../utils/storage')
|
||||||
|
const { isAllowedIp } = require('../utils/tools')
|
||||||
|
|
||||||
// 读取切片
|
// 读取切片
|
||||||
const pipeStream = (path, writeStream) => {
|
const pipeStream = (path, writeStream) => {
|
||||||
@ -23,7 +24,7 @@ const pipeStream = (path, writeStream) => {
|
|||||||
function listenInput(sftpClient, socket) {
|
function listenInput(sftpClient, socket) {
|
||||||
socket.on('open_dir', async (path, tips = true) => {
|
socket.on('open_dir', async (path, tips = true) => {
|
||||||
const exists = await sftpClient.exists(path)
|
const exists = await sftpClient.exists(path)
|
||||||
if(!exists) return socket.emit('not_exists_dir', tips ? '目录不存在或当前不可访问' : '')
|
if (!exists) return socket.emit('not_exists_dir', tips ? '目录不存在或当前不可访问' : '')
|
||||||
try {
|
try {
|
||||||
let dirLs = await sftpClient.list(path)
|
let dirLs = await sftpClient.list(path)
|
||||||
socket.emit('dir_ls', dirLs, path)
|
socket.emit('dir_ls', dirLs, path)
|
||||||
@ -34,7 +35,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
})
|
})
|
||||||
socket.on('rm_dir', async (path) => {
|
socket.on('rm_dir', async (path) => {
|
||||||
const exists = await sftpClient.exists(path)
|
const exists = await sftpClient.exists(path)
|
||||||
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
if (!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
||||||
try {
|
try {
|
||||||
let res = await sftpClient.rmdir(path, true) // 递归删除
|
let res = await sftpClient.rmdir(path, true) // 递归删除
|
||||||
socket.emit('rm_success', res)
|
socket.emit('rm_success', res)
|
||||||
@ -45,7 +46,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
})
|
})
|
||||||
socket.on('rm_file', async (path) => {
|
socket.on('rm_file', async (path) => {
|
||||||
const exists = await sftpClient.exists(path)
|
const exists = await sftpClient.exists(path)
|
||||||
if(!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
|
if (!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
|
||||||
try {
|
try {
|
||||||
let res = await sftpClient.delete(path)
|
let res = await sftpClient.delete(path)
|
||||||
socket.emit('rm_success', res)
|
socket.emit('rm_success', res)
|
||||||
@ -65,13 +66,13 @@ function listenInput(sftpClient, socket) {
|
|||||||
socket.on('down_file', async ({ path, name, size, target = 'down' }) => {
|
socket.on('down_file', async ({ path, name, size, target = 'down' }) => {
|
||||||
// target: down or preview
|
// target: down or preview
|
||||||
const exists = await sftpClient.exists(path)
|
const exists = await sftpClient.exists(path)
|
||||||
if(!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
|
if (!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
|
||||||
try {
|
try {
|
||||||
const localPath = rawPath.join(sftpCacheDir, name)
|
const localPath = rawPath.join(sftpCacheDir, name)
|
||||||
let timer = null
|
let timer = null
|
||||||
let res = await sftpClient.fastGet(path, localPath, {
|
let res = await sftpClient.fastGet(path, localPath, {
|
||||||
step: step => {
|
step: step => {
|
||||||
if(timer) return
|
if (timer) return
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
const percent = Math.ceil((step / size) * 100) // 下载进度为服务器下载到服务端的进度,前端无需*2
|
const percent = Math.ceil((step / size) * 100) // 下载进度为服务器下载到服务端的进度,前端无需*2
|
||||||
console.log(`从服务器下载进度:${ percent }%`)
|
console.log(`从服务器下载进度:${ percent }%`)
|
||||||
@ -83,7 +84,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
consola.success('sftp下载成功: ', res)
|
consola.success('sftp下载成功: ', res)
|
||||||
let buffer = fs.readFileSync(localPath)
|
let buffer = fs.readFileSync(localPath)
|
||||||
let data = { buffer, name }
|
let data = { buffer, name }
|
||||||
switch(target) {
|
switch (target) {
|
||||||
case 'down':
|
case 'down':
|
||||||
socket.emit('down_file_success', data)
|
socket.emit('down_file_success', data)
|
||||||
break
|
break
|
||||||
@ -102,7 +103,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
socket.on('up_file', async ({ targetPath, fullPath, name, file }) => {
|
socket.on('up_file', async ({ targetPath, fullPath, name, file }) => {
|
||||||
// console.log({ targetPath, fullPath, name, file })
|
// console.log({ targetPath, fullPath, name, file })
|
||||||
const exists = await sftpClient.exists(targetPath)
|
const exists = await sftpClient.exists(targetPath)
|
||||||
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
if (!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
||||||
try {
|
try {
|
||||||
const localPath = rawPath.join(sftpCacheDir, name)
|
const localPath = rawPath.join(sftpCacheDir, name)
|
||||||
fs.writeFileSync(localPath, file)
|
fs.writeFileSync(localPath, file)
|
||||||
@ -137,7 +138,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
socket.on('create_cache_dir', async ({ targetDirPath, name }) => {
|
socket.on('create_cache_dir', async ({ targetDirPath, name }) => {
|
||||||
// console.log({ targetDirPath, name })
|
// console.log({ targetDirPath, name })
|
||||||
const exists = await sftpClient.exists(targetDirPath)
|
const exists = await sftpClient.exists(targetDirPath)
|
||||||
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
if (!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
||||||
md5List = []
|
md5List = []
|
||||||
const localPath = rawPath.join(sftpCacheDir, name)
|
const localPath = rawPath.join(sftpCacheDir, name)
|
||||||
fs.emptyDirSync(localPath) // 不存在会创建,存在则清空
|
fs.emptyDirSync(localPath) // 不存在会创建,存在则清空
|
||||||
@ -178,7 +179,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
let timer = null
|
let timer = null
|
||||||
let res = await sftpClient.fastPut(resultFilePath, targetFilePath, {
|
let res = await sftpClient.fastPut(resultFilePath, targetFilePath, {
|
||||||
step: step => {
|
step: step => {
|
||||||
if(timer) return
|
if (timer) return
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
const percent = Math.ceil((step / size) * 100)
|
const percent = Math.ceil((step / size) * 100)
|
||||||
console.log(`上传服务器进度:${ percent }%`)
|
console.log(`上传服务器进度:${ percent }%`)
|
||||||
@ -210,13 +211,18 @@ module.exports = (httpServer) => {
|
|||||||
})
|
})
|
||||||
serverIo.on('connection', (socket) => {
|
serverIo.on('connection', (socket) => {
|
||||||
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
||||||
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
||||||
|
if (!isAllowedIp(requestIP)) {
|
||||||
|
socket.emit('ip_forbidden', 'IP地址不在白名单中')
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
let sftpClient = new SFTPClient()
|
let sftpClient = new SFTPClient()
|
||||||
consola.success('terminal websocket 已连接')
|
consola.success('terminal websocket 已连接')
|
||||||
|
|
||||||
socket.on('create', async ({ host: ip, token }) => {
|
socket.on('create', async ({ host: ip, token }) => {
|
||||||
const { code } = await verifyAuthSync(token, clientIp)
|
const { code } = await verifyAuthSync(token, requestIP)
|
||||||
if(code !== 1) {
|
if (code !== 1) {
|
||||||
socket.emit('token_verify_fail')
|
socket.emit('token_verify_fail')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,7 @@ const { verifyAuthSync } = require('../utils/verify-auth')
|
|||||||
const { AESDecryptSync } = require('../utils/encrypt')
|
const { AESDecryptSync } = require('../utils/encrypt')
|
||||||
const { readSSHRecord, readHostList } = require('../utils/storage')
|
const { readSSHRecord, readHostList } = require('../utils/storage')
|
||||||
const { asyncSendNotice } = require('../utils/notify')
|
const { asyncSendNotice } = require('../utils/notify')
|
||||||
|
const { isAllowedIp } = require('../utils/tools')
|
||||||
|
|
||||||
function createInteractiveShell(socket, sshClient) {
|
function createInteractiveShell(socket, sshClient) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@ -113,11 +114,16 @@ module.exports = (httpServer) => {
|
|||||||
})
|
})
|
||||||
serverIo.on('connection', (socket) => {
|
serverIo.on('connection', (socket) => {
|
||||||
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
||||||
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
||||||
|
if (!isAllowedIp(requestIP)) {
|
||||||
|
socket.emit('ip_forbidden', 'IP地址不在白名单中')
|
||||||
|
socket.disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
consola.success('terminal websocket 已连接')
|
consola.success('terminal websocket 已连接')
|
||||||
let sshClient = null
|
let sshClient = null
|
||||||
socket.on('create', async ({ host: ip, token }) => {
|
socket.on('create', async ({ host: ip, token }) => {
|
||||||
const { code } = await verifyAuthSync(token, clientIp)
|
const { code } = await verifyAuthSync(token, requestIP)
|
||||||
if (code !== 1) {
|
if (code !== 1) {
|
||||||
socket.emit('token_verify_fail')
|
socket.emit('token_verify_fail')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
|
43
server/app/template/ipForbidden.html
Normal file
43
server/app/template/ipForbidden.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>403 禁止访问</title>
|
||||||
|
<link rel="icon" href="data:;base64,=">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #d9534f;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>403 禁止访问</h1>
|
||||||
|
<p>抱歉,您没有权限访问此页面。</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -36,37 +36,37 @@ const getNetIPInfo = async (searchIp = '') => {
|
|||||||
let [ipApi, ipwho, ipdata, ipinfo, ipgeolocation, ipApi01, ip138] = result
|
let [ipApi, ipwho, ipdata, ipinfo, ipgeolocation, ipApi01, ip138] = result
|
||||||
|
|
||||||
let searchResult = []
|
let searchResult = []
|
||||||
if(ipApi.status === 'fulfilled') {
|
if (ipApi.status === 'fulfilled') {
|
||||||
let { query: ip, country, regionName, city } = ipApi.value?.data || {}
|
let { query: ip, country, regionName, city } = ipApi.value?.data || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ipwho.status === 'fulfilled') {
|
if (ipwho.status === 'fulfilled') {
|
||||||
let { ip, country, region: regionName, city } = ipwho.value?.data || {}
|
let { ip, country, region: regionName, city } = ipwho.value?.data || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ipdata.status === 'fulfilled') {
|
if (ipdata.status === 'fulfilled') {
|
||||||
let { ip, country_name: country, region: regionName, city } = ipdata.value?.data || {}
|
let { ip, country_name: country, region: regionName, city } = ipdata.value?.data || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ipinfo.status === 'fulfilled') {
|
if (ipinfo.status === 'fulfilled') {
|
||||||
let { ip, country, region: regionName, city } = ipinfo.value?.data || {}
|
let { ip, country, region: regionName, city } = ipinfo.value?.data || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ipgeolocation.status === 'fulfilled') {
|
if (ipgeolocation.status === 'fulfilled') {
|
||||||
let { ip, country_name: country, state_prov: regionName, city } = ipgeolocation.value?.data || {}
|
let { ip, country_name: country, state_prov: regionName, city } = ipgeolocation.value?.data || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ipApi01.status === 'fulfilled') {
|
if (ipApi01.status === 'fulfilled') {
|
||||||
let { ip, country_name: country, region: regionName, city } = ipApi01.value?.data || {}
|
let { ip, country_name: country, region: regionName, city } = ipApi01.value?.data || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ip138.status === 'fulfilled') {
|
if (ip138.status === 'fulfilled') {
|
||||||
let [res] = ip138.value?.data?.data || []
|
let [res] = ip138.value?.data?.data || []
|
||||||
let { origip: ip, location: country, city = '', regionName = '' } = res || {}
|
let { origip: ip, location: country, city = '', regionName = '' } = res || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
@ -175,7 +175,7 @@ const getUTCDate = (num = 8) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatTimestamp = (timestamp = Date.now(), format = 'time') => {
|
const formatTimestamp = (timestamp = Date.now(), format = 'time') => {
|
||||||
if(typeof(timestamp) !== 'number') return '--'
|
if (typeof(timestamp) !== 'number') return '--'
|
||||||
let date = new Date(timestamp)
|
let date = new Date(timestamp)
|
||||||
let padZero = (num) => String(num).padStart(2, '0')
|
let padZero = (num) => String(num).padStart(2, '0')
|
||||||
let year = date.getFullYear()
|
let year = date.getFullYear()
|
||||||
@ -231,6 +231,13 @@ const isProd = () => {
|
|||||||
return EXEC_ENV === 'production'
|
return EXEC_ENV === 'production'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allowedIPs = process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(',') : ''
|
||||||
|
if (allowedIPs) consola.warn('allowedIPs:', allowedIPs)
|
||||||
|
const isAllowedIp = (requestIP) => {
|
||||||
|
if (allowedIPs.length === 0) return true
|
||||||
|
return allowedIPs.some(item => item.includes(requestIP))
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNetIPInfo,
|
getNetIPInfo,
|
||||||
throwError,
|
throwError,
|
||||||
@ -240,5 +247,6 @@ module.exports = {
|
|||||||
formatTimestamp,
|
formatTimestamp,
|
||||||
resolvePath,
|
resolvePath,
|
||||||
shellThrottle,
|
shellThrottle,
|
||||||
isProd
|
isProd,
|
||||||
|
isAllowedIp
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user