diff --git a/server/app/config/index.js b/server/app/config/index.js index a96bbea..3af972b 100644 --- a/server/app/config/index.js +++ b/server/app/config/index.js @@ -1,6 +1,6 @@ -const path = require('path') +import path from 'node:path' -module.exports = { +const config = { httpPort: process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT) : 8082, httpsPort: process.env.HTTPS_PORT ? parseInt(process.env.HTTPS_PORT) : 8092, enableHttps: process.env.ENABLE_HTTPS ? parseInt(process.env.ENABLE_HTTPS) : 0, // 0:关闭 1:自签证书 2:传入证书路径 @@ -32,5 +32,39 @@ module.exports = { logConfig: { outDir: path.join(process.cwd(),'./app/db/logs'), DEBUG: process.env.DEBUG !== 'false' - } -} + } +} + +export const { + httpPort, + httpsPort, + enableHttps, + sslCertPath, + sslKeyPath, + uploadDir, + staticDir, + sftpCacheDir, + credentialsDBPath, + keyDBPath, + hostListDBPath, + groupConfDBPath, + scriptsDBPath, + scriptGroupDBPath, + notifyDBPath, + notifyConfigDBPath, + onekeyDBPath, + plusDBPath, + aiConfigDBPath, + chatHistoryDBPath, + favoriteSftpDBPath, + proxyDBPath, + fileTransferDBPath, + terminalConfigDBPath, + serverListDBPath, + sessionDBPath, + terminalSessionDBPath, + apiPrefix, + logConfig +} = config + +export default config diff --git a/server/app/controller/chat.js b/server/app/controller/chat.js index 419385f..1f2c114 100644 --- a/server/app/controller/chat.js +++ b/server/app/controller/chat.js @@ -1,9 +1,11 @@ -const path = require('path') -const decryptAndExecuteAsync = require('../utils/decrypt-file') -const { AIConfigDB, ChatHistoryDB } = require('../utils/db-class') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' +import { AIConfigDB, ChatHistoryDB } from '../utils/db-class.js' const aiConfigDB = new AIConfigDB().getInstance() const chatHistoryDB = new ChatHistoryDB().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) async function getAIConfig({ res }) { try { @@ -18,7 +20,7 @@ async function getAIConfig({ res }) { } async function getAIModels({ res, request }) { - let { getAIModels } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { getAIModels } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (getAIModels) { await getAIModels({ res, request }) } else { @@ -27,7 +29,7 @@ async function getAIModels({ res, request }) { } async function saveAIConfig({ res, request }) { - let { saveAIConfig } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { saveAIConfig } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (saveAIConfig) { await saveAIConfig({ res, request }) } else { @@ -71,7 +73,7 @@ async function removeChatHistory({ res, request }) { res.success({ data: true }) } -module.exports = { +export { getAIConfig, saveAIConfig, getAIModels, diff --git a/server/app/controller/group.js b/server/app/controller/group.js index 728b083..cda6a6d 100644 --- a/server/app/controller/group.js +++ b/server/app/controller/group.js @@ -1,4 +1,4 @@ -const { HostListDB, GroupDB } = require('../utils/db-class') +import { HostListDB, GroupDB } from '../utils/db-class.js' const hostListDB = new HostListDB().getInstance() const groupDB = new GroupDB().getInstance() @@ -45,7 +45,7 @@ const removeGroup = async ({ res, request }) => { res.success({ data: '移除成功' }) } -module.exports = { +export { addGroupList, getGroupList, updateGroupList, diff --git a/server/app/controller/host.js b/server/app/controller/host.js index 66c4789..401508f 100644 --- a/server/app/controller/host.js +++ b/server/app/controller/host.js @@ -1,8 +1,10 @@ -const path = require('path') -const decryptAndExecuteAsync = require('../utils/decrypt-file') -const { RSADecryptAsync, AESEncryptAsync, AESDecryptAsync } = require('../utils/encrypt') -const { HostListDB } = require('../utils/db-class') -const hostListDB = new HostListDB().getInstance() +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' +import { RSADecryptAsync, AESEncryptAsync, AESDecryptAsync } from '../utils/encrypt.js' +import { HostListDB } from '../utils/db-class.js' +const hostListDB = new HostListDB().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) async function getHostList({ res }) { let data = await hostListDB.findAsync({}) @@ -58,7 +60,7 @@ async function updateHost({ res, request }) { } async function batchUpdateHost({ res, request }) { - let { updateHosts } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { updateHosts } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (updateHosts) { await updateHosts({ res, request }) } else { @@ -121,7 +123,7 @@ async function updateLastConnectTime({ res, request }) { } } -module.exports = { +export { getHostList, addHost, updateHost, diff --git a/server/app/controller/native.js b/server/app/controller/native.js index 059802d..9315e30 100644 --- a/server/app/controller/native.js +++ b/server/app/controller/native.js @@ -1,7 +1,10 @@ -const crypto = require('crypto') -const path = require('path') -const { RSADecryptAsync } = require('../utils/encrypt') -const decryptAndExecuteAsync = require('../utils/decrypt-file') +import crypto from 'node:crypto' +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { RSADecryptAsync } from '../utils/encrypt.js' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' + +const currentDir = dirname(fileURLToPath(import.meta.url)) function encryptJsonForNative(payload, key) { if (!Buffer.isBuffer(key) || key.length !== 32) { @@ -46,13 +49,12 @@ async function buildNativeTopology(hostInfo = {}) { return { proxyType: '', proxy: null, jumpHosts: [] } } - let { getConnectionHelper } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { getConnectionHelper } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (getConnectionHelper) { const config = await getConnectionHelper(proxyType, hostInfo, normalizeNativeAuthPayload) return config - } else { - throw new Error('跳板机&代理服务为Plus功能') } + throw new Error('跳板机&代理服务为Plus功能') } async function getNativeSshConnection({ request, res }) { @@ -64,7 +66,7 @@ async function getNativeSshConnection({ request, res }) { const tempKeyText = await RSADecryptAsync(encryptedKey) const tempKey = Buffer.from(tempKeyText, 'base64') - const { getConnectionOptions } = require('../socket/terminal') + const { getConnectionOptions } = await import('../socket/terminal.js') const { authInfo, name, hostInfo } = await getConnectionOptions(hostId) const payload = { ...normalizeNativeAuthPayload(hostId, name, authInfo), @@ -79,6 +81,6 @@ async function getNativeSshConnection({ request, res }) { } } -module.exports = { +export { getNativeSshConnection } diff --git a/server/app/controller/notify.js b/server/app/controller/notify.js index 1456005..1ad209a 100644 --- a/server/app/controller/notify.js +++ b/server/app/controller/notify.js @@ -1,5 +1,5 @@ -const { sendServerChan, sendEmail, sendTg, sendWebhook } = require('../utils/notify') -const { NotifyConfigDB, NotifyDB } = require('../utils/db-class') +import { sendServerChan, sendEmail, sendTg, sendWebhook } from '../utils/notify.js' +import { NotifyConfigDB, NotifyDB } from '../utils/db-class.js' const notifyDB = new NotifyDB().getInstance() const notifyConfigDB = new NotifyConfigDB().getInstance() @@ -46,7 +46,7 @@ async function updateNotifyList({ res, request }) { res.success() } -module.exports = { +export { getNotifyConfig, updateNotifyConfig, getNotifyList, diff --git a/server/app/controller/onekey.js b/server/app/controller/onekey.js index 7488146..5786567 100644 --- a/server/app/controller/onekey.js +++ b/server/app/controller/onekey.js @@ -1,4 +1,4 @@ -const { OnekeyDB } = require('../utils/db-class') +import { OnekeyDB } from '../utils/db-class.js' const onekeyDB = new OnekeyDB().getInstance() async function getOnekeyRecord({ res }) { @@ -21,7 +21,7 @@ const removeOnekeyRecord = async ({ res, request }) => { } } -module.exports = { +export { getOnekeyRecord, removeOnekeyRecord } diff --git a/server/app/controller/plus.js b/server/app/controller/plus.js index c96b7f8..c2597f8 100644 --- a/server/app/controller/plus.js +++ b/server/app/controller/plus.js @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/server/app/controller/proxy.js b/server/app/controller/proxy.js index ce8afc3..1a6d106 100644 --- a/server/app/controller/proxy.js +++ b/server/app/controller/proxy.js @@ -1,4 +1,4 @@ -const { ProxyDB, HostListDB } = require('../utils/db-class') +import { ProxyDB, HostListDB } from '../utils/db-class.js' const proxyDB = new ProxyDB().getInstance() const hostListDB = new HostListDB().getInstance() @@ -109,7 +109,7 @@ const removeProxy = async ({ res, request }) => { } } -module.exports = { +export { getProxyList, addProxy, updateProxy, diff --git a/server/app/controller/script-group.js b/server/app/controller/script-group.js index c06badf..5bc84f8 100644 --- a/server/app/controller/script-group.js +++ b/server/app/controller/script-group.js @@ -1,6 +1,9 @@ -const path = require('path') -const { ScriptGroupDB } = require('../utils/db-class') -const decryptAndExecuteAsync = require('../utils/decrypt-file') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { ScriptGroupDB } from '../utils/db-class.js' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' + +const currentDir = dirname(fileURLToPath(import.meta.url)) const scriptGroupDB = new ScriptGroupDB().getInstance() async function getScriptGroupList({ res }) { @@ -11,7 +14,7 @@ async function getScriptGroupList({ res }) { } const addScriptGroup = async ({ res, request }) => { - let { addScriptGroup } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { addScriptGroup } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (addScriptGroup) { await addScriptGroup({ res, request }) } else { @@ -20,7 +23,7 @@ const addScriptGroup = async ({ res, request }) => { } const updateScriptGroup = async ({ res, request }) => { - let { updateScriptGroup } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { updateScriptGroup } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (updateScriptGroup) { await updateScriptGroup({ res, request }) } else { @@ -29,7 +32,7 @@ const updateScriptGroup = async ({ res, request }) => { } const removeScriptGroup = async ({ res, request }) => { - let { removeScriptGroup } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { removeScriptGroup } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (removeScriptGroup) { await removeScriptGroup({ res, request }) } else { @@ -37,9 +40,9 @@ const removeScriptGroup = async ({ res, request }) => { } } -module.exports = { +export { addScriptGroup, getScriptGroupList, updateScriptGroup, removeScriptGroup -} \ No newline at end of file +} diff --git a/server/app/controller/scripts.js b/server/app/controller/scripts.js index e1c1476..54a49b0 100644 --- a/server/app/controller/scripts.js +++ b/server/app/controller/scripts.js @@ -1,9 +1,11 @@ -const path = require('path') -const decryptAndExecuteAsync = require('../utils/decrypt-file') -const { randomStr } = require('../utils/tools') -const { ScriptsDB } = require('../utils/db-class') -const localShellJson = require('../config/shell.json') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' +import { randomStr } from '../utils/tools.js' +import { ScriptsDB } from '../utils/db-class.js' +import localShellJson from '../config/shell.json' with { type: 'json' } const scriptsDB = new ScriptsDB().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) let localShell = JSON.parse(JSON.stringify(localShellJson)).map((item) => { return { ...item, id: randomStr(10), index: '--', description: item.description, group: 'builtin' } @@ -56,7 +58,7 @@ const batchRemoveScript = async ({ res, request }) => { } const importScript = async ({ res, request }) => { - let { impScript } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { impScript } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (impScript) { await impScript({ res, request }) } else { @@ -64,7 +66,7 @@ const importScript = async ({ res, request }) => { } } -module.exports = { +export { addScript, getScriptList, getLocalScriptList, diff --git a/server/app/controller/server-list-config.js b/server/app/controller/server-list-config.js index 247eb47..beaa82b 100644 --- a/server/app/controller/server-list-config.js +++ b/server/app/controller/server-list-config.js @@ -1,4 +1,4 @@ -const { ServerListDB } = require('../utils/db-class') +import { ServerListDB } from '../utils/db-class.js' const serverListDB = new ServerListDB().getInstance() // 获取服务器列表配置 @@ -63,7 +63,7 @@ async function saveServerListConfig({ res, request }) { } } -module.exports = { +export { getServerListConfig, saveServerListConfig } diff --git a/server/app/controller/sessionLog.js b/server/app/controller/sessionLog.js index a970282..85fbae8 100644 --- a/server/app/controller/sessionLog.js +++ b/server/app/controller/sessionLog.js @@ -1,4 +1,4 @@ -const { KeyDB, SessionDB } = require('../utils/db-class') +import { KeyDB, SessionDB } from '../utils/db-class.js' const keyDB = new KeyDB().getInstance() const sessionDB = new SessionDB().getInstance() @@ -50,7 +50,7 @@ const revokeLoginSid = async (ctx) => { res.success({ msg: '注销凭证成功' }) } -module.exports = { +export { getLog, saveIpWhiteList, removeSomeLoginRecords, diff --git a/server/app/controller/sftp.js b/server/app/controller/sftp.js index 047d801..52ed8f2 100644 --- a/server/app/controller/sftp.js +++ b/server/app/controller/sftp.js @@ -1,4 +1,4 @@ -const { HostListDB, CredentialsDB, FavoriteSftpDB } = require('../utils/db-class') +import { HostListDB, CredentialsDB, FavoriteSftpDB } from '../utils/db-class.js' const favoriteSftpDB = new FavoriteSftpDB().getInstance() @@ -19,6 +19,6 @@ async function getSftpFavorites({ params, request, res }) { } } -module.exports = { +export { getSftpFavorites } diff --git a/server/app/controller/ssh.js b/server/app/controller/ssh.js index 3dae4a4..8b772e5 100644 --- a/server/app/controller/ssh.js +++ b/server/app/controller/ssh.js @@ -1,10 +1,12 @@ -const path = require('path') -const crypto = require('crypto') -const { RSADecryptAsync, AESEncryptAsync, AESDecryptAsync } = require('../utils/encrypt') -const { HostListDB, CredentialsDB } = require('../utils/db-class') -const decryptAndExecuteAsync = require('../utils/decrypt-file') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import crypto from 'node:crypto' +import { RSADecryptAsync, AESEncryptAsync, AESDecryptAsync } from '../utils/encrypt.js' +import { HostListDB, CredentialsDB } from '../utils/db-class.js' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' const hostListDB = new HostListDB().getInstance() -const credentialsDB = new CredentialsDB().getInstance() +const credentialsDB = new CredentialsDB().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) async function getSSHList({ res }) { let data = await credentialsDB.findAsync({}) @@ -118,7 +120,7 @@ const decryptPrivateKey = async ({ res, request }) => { } const getRdpToken = async ({ res, request }) => { - let { genRdpToken } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { genRdpToken } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (genRdpToken) { await genRdpToken({ res, request }) } else { @@ -126,7 +128,7 @@ const getRdpToken = async ({ res, request }) => { } } -module.exports = { +export { getSSHList, addSSH, updateSSH, diff --git a/server/app/controller/terminal-config.js b/server/app/controller/terminal-config.js index 49dc89f..7cc728f 100644 --- a/server/app/controller/terminal-config.js +++ b/server/app/controller/terminal-config.js @@ -1,4 +1,4 @@ -const { TerminalConfigDB } = require('../utils/db-class') +import { TerminalConfigDB } from '../utils/db-class.js' // 终端默认配置 const DEFAULT_TERMINAL_CONFIG = { @@ -74,7 +74,7 @@ async function saveTerminalConfig({ res, request }) { } } -module.exports = { +export { getTerminalConfig, saveTerminalConfig } \ No newline at end of file diff --git a/server/app/controller/terminal.js b/server/app/controller/terminal.js index ee4dc39..007b502 100644 --- a/server/app/controller/terminal.js +++ b/server/app/controller/terminal.js @@ -1,5 +1,5 @@ -const { sessionManager, getDefaultSessionConfig } = require('../utils/terminal-session') -const { KeyDB, HostListDB, TerminalSessionDB } = require('../utils/db-class') +import { sessionManager, getDefaultSessionConfig } from '../utils/terminal-session.js' +import { KeyDB, HostListDB, TerminalSessionDB } from '../utils/db-class.js' const keyDB = new KeyDB().getInstance() const hostListDB = new HostListDB().getInstance() @@ -101,7 +101,7 @@ async function updateTerminalSessionConfig({ request, res }) { } } -module.exports = { +export { getSuspendedSessions, getTerminalSessionConfig, updateTerminalSessionConfig diff --git a/server/app/controller/user.js b/server/app/controller/user.js index b68df3f..2c44cbe 100644 --- a/server/app/controller/user.js +++ b/server/app/controller/user.js @@ -1,16 +1,17 @@ -const jwt = require('jsonwebtoken') -const axios = require('axios') -const speakeasy = require('speakeasy') -const QRCode = require('qrcode') -const uap = require('ua-parser-js') -const { v4: uuidv4 } = require('uuid') -const version = require('../../package.json').version -const getLicenseInfo = require('../utils/get-plus') -const { sendNoticeAsync } = require('../utils/notify') -const { RSADecryptAsync, AESEncryptAsync, SHA1Encrypt, SHA256Encrypt } = require('../utils/encrypt') -const { getNetIPInfo, requestWithFailover, timingSafeEqual } = require('../utils/tools') -const { KeyDB, PlusDB, SessionDB } = require('../utils/db-class') -const { RuntimeState } = require('../utils/runtime-state') +import jwt from 'jsonwebtoken' +import axios from 'axios' +import speakeasy from 'speakeasy' +import QRCode from 'qrcode' +import { UAParser as uap } from 'ua-parser-js' +import { v4 as uuidv4 } from 'uuid' +import PackageJsonModule from '../../package.json' with { type: 'json' } +const version = PackageJsonModule.version +import getLicenseInfo from '../utils/get-plus.js' +import { sendNoticeAsync } from '../utils/notify.js' +import { RSADecryptAsync, AESEncryptAsync, SHA1Encrypt, SHA256Encrypt } from '../utils/encrypt.js' +import { getNetIPInfo, requestWithFailover, timingSafeEqual } from '../utils/tools.js' +import { KeyDB, PlusDB, SessionDB } from '../utils/db-class.js' +import { RuntimeState } from '../utils/runtime-state.js' const keyDB = new KeyDB().getInstance() const sessionDB = new SessionDB().getInstance() @@ -320,7 +321,7 @@ const releasePlusDevice = async ({ res, request }) => { } } -module.exports = { +export { login, getpublicKey, updatePwd, diff --git a/server/app/db.js b/server/app/db.js index 9285c6a..5e4a0a2 100644 --- a/server/app/db.js +++ b/server/app/db.js @@ -1,7 +1,7 @@ -const NodeRSA = require('node-rsa') -const { randomStr } = require('./utils/tools') -const { AESEncryptAsync, SHA1Encrypt } = require('./utils/encrypt') -const { KeyDB, GroupDB, NotifyDB, NotifyConfigDB, ScriptGroupDB } = require('./utils/db-class') +import NodeRSA from 'node-rsa' +import { randomStr } from './utils/tools.js' +import { AESEncryptAsync, SHA1Encrypt } from './utils/encrypt.js' +import { KeyDB, GroupDB, NotifyDB, NotifyConfigDB, ScriptGroupDB } from './utils/db-class.js' async function initKeyDB() { const keyDB = new KeyDB().getInstance() @@ -153,7 +153,7 @@ async function initScriptGroupDB() { return Promise.resolve() } -module.exports = async () => { +export default async () => { await initKeyDB() await initNotifyDB() await initGroupDB() diff --git a/server/app/logs.js b/server/app/logs.js index 6f543f4..86c0f53 100644 --- a/server/app/logs.js +++ b/server/app/logs.js @@ -1,5 +1,7 @@ -const log4js = require('log4js') -const { outDir } = require('./config').logConfig +import log4js from 'log4js' +import { logConfig } from './config/index.js' + +const { outDir } = logConfig log4js.configure({ appenders: { console: { @@ -26,4 +28,4 @@ log4js.configure({ } }) const logger = log4js.getLogger() -global.logger = logger \ No newline at end of file +global.logger = logger diff --git a/server/app/main.js b/server/app/main.js index af1954d..1b53ffa 100644 --- a/server/app/main.js +++ b/server/app/main.js @@ -1,8 +1,8 @@ -require('./logs') -const { createServer } = require('./server') -const initDB = require('./db') -const scheduleJob = require('./schedule') -const { startActivation } = require('./utils/get-plus') +import './logs.js' +import { createServer } from './server.js' +import initDB from './db.js' +import scheduleJob from './schedule/index.js' +import { startActivation } from './utils/get-plus.js' async function main() { await initDB() diff --git a/server/app/middlewares/auth.js b/server/app/middlewares/auth.js index a5389db..a332067 100644 --- a/server/app/middlewares/auth.js +++ b/server/app/middlewares/auth.js @@ -1,5 +1,5 @@ -const { apiPrefix } = require('../config') -const { verifyAuthSync, enumLoginCode } = require('../utils/verify-auth') +import { apiPrefix } from '../config/index.js' +import { verifyAuthSync, enumLoginCode } from '../utils/verify-auth.js' let whitePath = [ '/login', @@ -48,4 +48,4 @@ const useAuth = async (ctx, next) => { return res.fail({ msg: failMsg || '身份验证失败(未知错误!)', status: 403 }) } -module.exports = useAuth +export default useAuth diff --git a/server/app/middlewares/body.js b/server/app/middlewares/body.js index 2057836..844d4ea 100644 --- a/server/app/middlewares/body.js +++ b/server/app/middlewares/body.js @@ -1,5 +1,7 @@ -const { koaBody } = require('koa-body') +import koaBodyModule from 'koa-body' -module.exports = koaBody({ +const { koaBody } = koaBodyModule + +export default koaBody({ multipart: false }) diff --git a/server/app/middlewares/compress.js b/server/app/middlewares/compress.js index 0595dad..be2d5f1 100644 --- a/server/app/middlewares/compress.js +++ b/server/app/middlewares/compress.js @@ -1,6 +1,6 @@ // 响应压缩模块,自适应头部压缩方式 -const compress = require('koa-compress') +import compress from 'koa-compress' const options = { threshold: 2048 } -module.exports = compress(options) +export default compress(options) diff --git a/server/app/middlewares/cors.js b/server/app/middlewares/cors.js index 81f55a1..3660c74 100644 --- a/server/app/middlewares/cors.js +++ b/server/app/middlewares/cors.js @@ -1,4 +1,4 @@ -const cors = require('@koa/cors') +import cors from '@koa/cors' // 跨域处理 const useCors = cors({ @@ -9,4 +9,4 @@ const useCors = cors({ allowMethods: [ 'GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH' ] }) -module.exports = useCors +export default useCors diff --git a/server/app/middlewares/history.js b/server/app/middlewares/history.js index a40258f..686b1cf 100644 --- a/server/app/middlewares/history.js +++ b/server/app/middlewares/history.js @@ -1,3 +1,4 @@ -const { historyApiFallback } = require('koa2-connect-history-api-fallback') +import koa2ConnectHistoryApiFallbackModule from 'koa2-connect-history-api-fallback' +const { historyApiFallback } = koa2ConnectHistoryApiFallbackModule -module.exports = historyApiFallback({ whiteList: ['/api'] }) +export default historyApiFallback({ whiteList: ['/api'] }) diff --git a/server/app/middlewares/index.js b/server/app/middlewares/index.js index b116917..395f260 100644 --- a/server/app/middlewares/index.js +++ b/server/app/middlewares/index.js @@ -1,16 +1,16 @@ -const ipFilter = require('./ipFilter') // IP过滤 -const responseHandler = require('./response') // 统一返回格式, 错误捕获 -const useAuth = require('./auth') // 鉴权 -// const useCors = require('./cors') // 处理跨域[暂时禁止] -const useLog = require('./useLog') // 记录日志,需要等待路由处理完成,所以得放路由前 -const useKoaBody = require('./body') // 处理body参数 【请求需先走该中间件】 -const { useRoutes, useAllowedMethods } = require('./router') // 路由管理 -const useStatic = require('./static') // 静态目录 -const useSftpCacheStatic = require('./sftp-cache') // SFTP缓存文件服务 -const compress = require('./compress') // br/gzip压缩 -const history = require('./history') // vue-router的history模式 +import ipFilter from './ipFilter.js' // IP过滤 +import responseHandler from './response.js' // 统一返回格式, 错误捕获 +import useAuth from './auth.js' // 鉴权 +// import useCors from './cors.js' // 处理跨域[暂时禁止] +import useLog from './useLog.js' // 记录日志,需要等待路由处理完成,所以得放路由前 +import useKoaBody from './body.js' // 处理body参数 【请求需先走该中间件】 +import { useRoutes, useAllowedMethods } from './router.js' // 路由管理 +import useStatic from './static.js' // 静态目录 +import useSftpCacheStatic from './sftp-cache.js' // SFTP缓存文件服务 +import compress from './compress.js' // br/gzip压缩 +import history from './history.js' // vue-router的history模式 -module.exports = [ +export default [ ipFilter, useSftpCacheStatic, // SFTP缓存文件服务 compress, diff --git a/server/app/middlewares/ipFilter.js b/server/app/middlewares/ipFilter.js index 448b49c..35a63ff 100644 --- a/server/app/middlewares/ipFilter.js +++ b/server/app/middlewares/ipFilter.js @@ -1,9 +1,11 @@ // 白名单IP -const fs = require('fs') -const path = require('path') -const { isAllowedIp, getClientIP } = require('../utils/tools') +import fs from 'node:fs' +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { isAllowedIp, getClientIP } from '../utils/tools.js' -const htmlPath = path.join(__dirname, '../template/ipForbidden.html') +const currentDir = dirname(fileURLToPath(import.meta.url)) +const htmlPath = path.join(currentDir, '../template/ipForbidden.html') const ipForbiddenHtml = fs.readFileSync(htmlPath, 'utf8') const ipFilter = async (ctx, next) => { @@ -13,4 +15,4 @@ const ipFilter = async (ctx, next) => { ctx.body = ipForbiddenHtml } -module.exports = ipFilter +export default ipFilter diff --git a/server/app/middlewares/rdp-proxy.js b/server/app/middlewares/rdp-proxy.js index 1b0b71d..2dab7a1 100644 --- a/server/app/middlewares/rdp-proxy.js +++ b/server/app/middlewares/rdp-proxy.js @@ -1,4 +1,5 @@ -const { createProxyMiddleware } = require('http-proxy-middleware') +import httpProxyMiddlewareModule from 'http-proxy-middleware' +const { createProxyMiddleware } = httpProxyMiddlewareModule /** * RDP代理中间件 @@ -25,4 +26,4 @@ const createRdpProxyMiddleware = () => { return wsProxy } -module.exports = createRdpProxyMiddleware \ No newline at end of file +export default createRdpProxyMiddleware \ No newline at end of file diff --git a/server/app/middlewares/response.js b/server/app/middlewares/response.js index 1c54d7d..388e0ff 100644 --- a/server/app/middlewares/response.js +++ b/server/app/middlewares/response.js @@ -31,4 +31,4 @@ const responseHandler = async (ctx, next) => { } } -module.exports = responseHandler +export default responseHandler diff --git a/server/app/middlewares/router.js b/server/app/middlewares/router.js index 3389a14..9d26a59 100644 --- a/server/app/middlewares/router.js +++ b/server/app/middlewares/router.js @@ -1,4 +1,4 @@ -const router = require('../router') +import router from '../router/index.js' // 路由中间件 const useRoutes = router.routes() @@ -7,7 +7,7 @@ const useRoutes = router.routes() // 使用该中间件后请求方法错误会提示405 Method Not Allowed【get list ✔200 post /list ❌405】 const useAllowedMethods = router.allowedMethods() -module.exports = { +export { useRoutes, useAllowedMethods } diff --git a/server/app/middlewares/sftp-cache.js b/server/app/middlewares/sftp-cache.js index 0073a76..7db8dd9 100644 --- a/server/app/middlewares/sftp-cache.js +++ b/server/app/middlewares/sftp-cache.js @@ -1,6 +1,6 @@ -const koaStatic = require('koa-static') -const path = require('path') -const { sftpCacheDir } = require('../config') +import koaStatic from 'koa-static' +import path from 'node:path' +import { sftpCacheDir } from '../config/index.js' const useSftpCacheStatic = async (ctx, next) => { // 只处理 /sftp-cache 路径 @@ -29,4 +29,4 @@ const useSftpCacheStatic = async (ctx, next) => { } } -module.exports = useSftpCacheStatic \ No newline at end of file +export default useSftpCacheStatic \ No newline at end of file diff --git a/server/app/middlewares/static.js b/server/app/middlewares/static.js index 590fa89..71e0918 100644 --- a/server/app/middlewares/static.js +++ b/server/app/middlewares/static.js @@ -1,5 +1,5 @@ -const koaStatic = require('koa-static') -const { staticDir } = require('../config') +import koaStatic from 'koa-static' +import { staticDir } from '../config/index.js' const useStatic = koaStatic(staticDir, { maxage: 1000 * 60 * 60 * 24 * 30, @@ -11,4 +11,4 @@ const useStatic = koaStatic(staticDir, { } }) -module.exports = useStatic +export default useStatic diff --git a/server/app/middlewares/useLog.js b/server/app/middlewares/useLog.js index ff67893..96d950b 100644 --- a/server/app/middlewares/useLog.js +++ b/server/app/middlewares/useLog.js @@ -1,5 +1,7 @@ -const { DEBUG } = require('../config').logConfig -const { getClientIP } = require('../utils/tools') +import { logConfig } from '../config/index.js' +import { getClientIP } from '../utils/tools.js' + +const { DEBUG } = logConfig // ------------------ 脱敏 ------------------ // 可能包含敏感信息的 header key(小写比较) @@ -109,7 +111,7 @@ const useLog = () => { origin, query, body, - headers, + headers } = ctx.request const ip = getClientIP(ctx.socket.remoteAddress, ctx.get('x-forwarded-for')) @@ -169,4 +171,4 @@ const useLog = () => { } } -module.exports = useLog() +export default useLog() diff --git a/server/app/rdp-server.js b/server/app/rdp-server.js index 7b71b45..8ec1212 100644 --- a/server/app/rdp-server.js +++ b/server/app/rdp-server.js @@ -1,5 +1,5 @@ -const http = require('http') -const GuacamoleLite = require('guacamole-lite') +import http from 'node:http' +import GuacamoleLite from 'guacamole-lite' const RDP_PORT = process.env.RDP_PORT || 8083 // 使用独立端口 const GUACD_HOST = process.env.GUACD_HOST || 'guacd' @@ -45,4 +45,4 @@ const startRdpServer = () => { } } -module.exports = { startRdpServer } +export { startRdpServer } diff --git a/server/app/router/index.js b/server/app/router/index.js index 6a9149f..8aef8ff 100644 --- a/server/app/router/index.js +++ b/server/app/router/index.js @@ -1,8 +1,8 @@ -const { apiPrefix } = require('../config') -const koaRouter = require('koa-router') +import { apiPrefix } from '../config/index.js' +import koaRouter from 'koa-router' const router = new koaRouter({ prefix: apiPrefix }) -const routeList = require('./routes') +import routeList from './routes.js' // 统一注册路由 routeList.forEach(item => { @@ -10,4 +10,4 @@ routeList.forEach(item => { router[method](path, controller) }) -module.exports = router +export default router diff --git a/server/app/router/routes.js b/server/app/router/routes.js index 33be44e..68a91ca 100644 --- a/server/app/router/routes.js +++ b/server/app/router/routes.js @@ -1,19 +1,19 @@ -const { getSSHList, addSSH, updateSSH, removeSSH, getCommand, decryptPrivateKey, getRdpToken } = require('../controller/ssh') -const { getSftpFavorites } = require('../controller/sftp') -const { getHostList, addHost, updateHost, batchUpdateHost, removeHost, importHost, updateLastConnectTime } = require('../controller/host') -const { login, getpublicKey, updatePwd, getEasynodeVersion, getMFA2Status, getMFA2Code, enableMFA2, disableMFA2, getPlusInfo, getPlusDiscount, getPlusConf, updatePlusKey, getPlusDevices, releasePlusDevice } = require('../controller/user') -const { getNotifyConfig, updateNotifyConfig, getNotifyList, updateNotifyList } = require('../controller/notify') -const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group') -const { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript, batchRemoveScript, importScript } = require('../controller/scripts') -const { getScriptGroupList, addScriptGroup, removeScriptGroup, updateScriptGroup } = require('../controller/script-group') -const { getOnekeyRecord, removeOnekeyRecord } = require('../controller/onekey') -const { getLog, saveIpWhiteList, removeSomeLoginRecords, revokeLoginSid } = require('../controller/sessionLog') -const { getAIConfig, saveAIConfig, getAIModels, getChatHistory, saveChatHistory, removeChatHistory } = require('../controller/chat') -const { getProxyList, addProxy, updateProxy, removeProxy } = require('../controller/proxy') -const { getTerminalConfig, saveTerminalConfig } = require('../controller/terminal-config') -const { getServerListConfig, saveServerListConfig } = require('../controller/server-list-config') -const { getSuspendedSessions, getTerminalSessionConfig, updateTerminalSessionConfig } = require('../controller/terminal') -const { getNativeSshConnection } = require('../controller/native') +import { getSSHList, addSSH, updateSSH, removeSSH, getCommand, decryptPrivateKey, getRdpToken } from '../controller/ssh.js' +import { getSftpFavorites } from '../controller/sftp.js' +import { getHostList, addHost, updateHost, batchUpdateHost, removeHost, importHost, updateLastConnectTime } from '../controller/host.js' +import { login, getpublicKey, updatePwd, getEasynodeVersion, getMFA2Status, getMFA2Code, enableMFA2, disableMFA2, getPlusInfo, getPlusDiscount, getPlusConf, updatePlusKey, getPlusDevices, releasePlusDevice } from '../controller/user.js' +import { getNotifyConfig, updateNotifyConfig, getNotifyList, updateNotifyList } from '../controller/notify.js' +import { getGroupList, addGroupList, updateGroupList, removeGroup } from '../controller/group.js' +import { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript, batchRemoveScript, importScript } from '../controller/scripts.js' +import { getScriptGroupList, addScriptGroup, removeScriptGroup, updateScriptGroup } from '../controller/script-group.js' +import { getOnekeyRecord, removeOnekeyRecord } from '../controller/onekey.js' +import { getLog, saveIpWhiteList, removeSomeLoginRecords, revokeLoginSid } from '../controller/sessionLog.js' +import { getAIConfig, saveAIConfig, getAIModels, getChatHistory, saveChatHistory, removeChatHistory } from '../controller/chat.js' +import { getProxyList, addProxy, updateProxy, removeProxy } from '../controller/proxy.js' +import { getTerminalConfig, saveTerminalConfig } from '../controller/terminal-config.js' +import { getServerListConfig, saveServerListConfig } from '../controller/server-list-config.js' +import { getSuspendedSessions, getTerminalSessionConfig, updateTerminalSessionConfig } from '../controller/terminal.js' +import { getNativeSshConnection } from '../controller/native.js' const ssh = [ { @@ -420,7 +420,7 @@ const sftp = [ } ] -module.exports = [].concat( +export default [].concat( ssh, host, user, diff --git a/server/app/schedule/index.js b/server/app/schedule/index.js index da85aa1..e84a2e2 100644 --- a/server/app/schedule/index.js +++ b/server/app/schedule/index.js @@ -1,7 +1,7 @@ -const schedule = require('node-schedule') -const { sendNoticeAsync } = require('../utils/notify') -const { formatTimestamp } = require('../utils/tools') -const { HostListDB } = require('../utils/db-class') +import schedule from 'node-schedule' +import { sendNoticeAsync } from '../utils/notify.js' +import { formatTimestamp } from '../utils/tools.js' +import { HostListDB } from '../utils/db-class.js' const hostListDB = new HostListDB().getInstance() const expiredNotifyJob = async () => { @@ -27,6 +27,6 @@ const expiredNotifyJob = async () => { } } -module.exports = () => { +export default () => { schedule.scheduleJob('0 0 12 1/1 * ?', expiredNotifyJob) } diff --git a/server/app/server.js b/server/app/server.js index 13e2acf..d86e66a 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -1,22 +1,22 @@ -const Koa = require('koa') -const compose = require('koa-compose') // 组合中间件,简化写法 -const http = require('http') -const https = require('https') -const fs = require('fs') -const { httpPort, httpsPort, enableHttps, sslCertPath, sslKeyPath } = require('./config') -const middlewares = require('./middlewares') -const { startRdpServer } = require('./rdp-server') -const wsTerminal = require('./socket/terminal') -const wsSftpV2 = require('./socket/sftp-v2') -const wsDocker = require('./socket/docker') -const wsOnekey = require('./socket/onekey') -const wsServerStatus = require('./socket/server-status') -const wsFileTransfer = require('./socket/file-transfer') -const { throwError, isAllowedIp, getClientIP } = require('./utils/tools') -const { SessionDB } = require('./utils/db-class') -const { parseCookies } = require('./utils/verify-auth') -const { generateSelfSignedCert } = require('./utils/ssl-cert') -const createRdpProxyMiddleware = require('./middlewares/rdp-proxy') +import Koa from 'koa' +import compose from 'koa-compose' // 组合中间件,简化写法 +import http from 'node:http' +import https from 'node:https' +import fs from 'node:fs' +import { httpPort, httpsPort, enableHttps, sslCertPath, sslKeyPath } from './config/index.js' +import middlewares from './middlewares/index.js' +import { startRdpServer } from './rdp-server.js' +import wsTerminal from './socket/terminal.js' +import wsSftpV2 from './socket/sftp-v2.js' +import wsDocker from './socket/docker.js' +import wsOnekey from './socket/onekey.js' +import wsServerStatus from './socket/server-status.js' +import wsFileTransfer from './socket/file-transfer.js' +import { throwError, isAllowedIp, getClientIP } from './utils/tools.js' +import { SessionDB } from './utils/db-class.js' +import { parseCookies } from './utils/verify-auth.js' +import { generateSelfSignedCert } from './utils/ssl-cert.js' +import createRdpProxyMiddleware from './middlewares/rdp-proxy.js' const sessionDB = new SessionDB().getInstance() @@ -177,6 +177,6 @@ function serverHandler(app, server, httpsServer) { }) } -module.exports = { +export { createServer } diff --git a/server/app/socket/docker.js b/server/app/socket/docker.js index dda0680..4f21fe4 100644 --- a/server/app/socket/docker.js +++ b/server/app/socket/docker.js @@ -1,8 +1,12 @@ -const path = require('path') -const { Client: SSHClient } = require('ssh2') -const { createTerminal } = require('./terminal') -const decryptAndExecuteAsync = require('../utils/decrypt-file') -const { createSecureWs } = require('../utils/ws-tool') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import ssh2Module from 'ssh2' +const { Client: SSHClient } = ssh2Module +import { createTerminal } from './terminal.js' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' +import { createSecureWs } from '../utils/ws-tool.js' + +const currentDir = dirname(fileURLToPath(import.meta.url)) let executeCommand = () => { return new Promise((resolve) => { @@ -129,7 +133,7 @@ async function deleteDockerContainer(targetSSHClient, containerId) { } } -module.exports = (httpServer) => { +export default (httpServer) => { const serverIo = createSecureWs(httpServer, '/docker') let connectionCount = 0 @@ -144,7 +148,7 @@ module.exports = (httpServer) => { targetSSHClient = new SSHClient() let { jumpSshClients: dockerJumpSshClients } = await createTerminal(hostId, socket, targetSSHClient, false) jumpSshClients.push(...dockerJumpSshClients) - let { getDockerContainers = null, executeCommand: dockerExecuteCommand } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + let { getDockerContainers = null, executeCommand: dockerExecuteCommand } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} executeCommand = dockerExecuteCommand if (!getDockerContainers) { socket.emit('docker_not_plus') diff --git a/server/app/socket/file-transfer.js b/server/app/socket/file-transfer.js index 1b86ec9..05c0f9e 100644 --- a/server/app/socket/file-transfer.js +++ b/server/app/socket/file-transfer.js @@ -1,13 +1,16 @@ -const path = require('path') -const { Client: SSHClient } = require('ssh2') -const { fileTransferThrottle } = require('../utils/tools') -const { createSecureWs } = require('../utils/ws-tool') -const { getConnectionOptions } = require('./terminal') -const { FileTransferDB, HostListDB } = require('../utils/db-class') -const decryptAndExecuteAsync = require('../utils/decrypt-file') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import ssh2Module from 'ssh2' +const { Client: SSHClient } = ssh2Module +import { fileTransferThrottle } from '../utils/tools.js' +import { createSecureWs } from '../utils/ws-tool.js' +import { getConnectionOptions } from './terminal.js' +import { FileTransferDB, HostListDB } from '../utils/db-class.js' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' const fileTransferDB = new FileTransferDB().getInstance() const hostListDB = new HostListDB().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) // 全局传输任务管理 const activeTasks = new Map() // taskId -> { process, sshClient, status, ... } @@ -53,7 +56,7 @@ async function getSortedTasksList() { return sortTasks(tasksWithStatus) } -module.exports = (httpServer) => { +export default (httpServer) => { const serverIo = createSecureWs(httpServer, '/file-transfer') let connectionCount = 0 @@ -83,7 +86,7 @@ module.exports = (httpServer) => { // 启动传输任务 socket.on('start_transfer', async (transferConfig) => { try { - const { createTransferTask = null } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + const { createTransferTask = null } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (!createTransferTask) throw new Error('Plus功能解锁失败: createTransferTask') const task = await createTransferTask(transferConfig, socket, hostListDB, fileTransferDB, executeTransfer) socket.emit('task_started', { taskId: task.taskId, message: '传输任务已启动' }) @@ -926,4 +929,3 @@ function stopProgressBroadcast(socket) { logger.info('已停止进度广播定时器') } } - diff --git a/server/app/socket/onekey.js b/server/app/socket/onekey.js index 0a53fce..b99afcf 100644 --- a/server/app/socket/onekey.js +++ b/server/app/socket/onekey.js @@ -1,10 +1,11 @@ -const { Server } = require('socket.io') -const { Client: SSHClient } = require('ssh2') -const { sendNoticeAsync } = require('../utils/notify') -const { shellThrottle } = require('../utils/tools') -const { createSecureWs } = require('../utils/ws-tool') -const { HostListDB, OnekeyDB } = require('../utils/db-class') -const { getConnectionOptions, handleProxyAndJumpHostConnection } = require('./terminal') +import { Server } from 'socket.io' +import ssh2Module from 'ssh2' +const { Client: SSHClient } = ssh2Module +import { sendNoticeAsync } from '../utils/notify.js' +import { shellThrottle } from '../utils/tools.js' +import { createSecureWs } from '../utils/ws-tool.js' +import { HostListDB, OnekeyDB } from '../utils/db-class.js' +import { getConnectionOptions, handleProxyAndJumpHostConnection } from './terminal.js' const hostListDB = new HostListDB().getInstance() const onekeyDB = new OnekeyDB().getInstance() @@ -88,7 +89,7 @@ function execShell(socket, sshClient, curRes, resolve) { }) } -module.exports = (httpServer) => { +export default (httpServer) => { const serverIo = createSecureWs(httpServer, '/onekey') serverIo.on('connection', async (socket) => { diff --git a/server/app/socket/plus.js b/server/app/socket/plus.js index 9189326..76ab4a7 100644 --- a/server/app/socket/plus.js +++ b/server/app/socket/plus.js @@ -1 +1 @@ -U2FsdGVkX1+diXqIlwIsiAlSe+LA3RijqHy5utre2tx9IsWEMkD2QuIJvsxQHs1ybJ5Kgf+iFV3mgFndPXUQrpsgeYfzc3UDi/6gXwOwnx8+9jlRsSNyFDvCaVBkqd9a9muh2qXH57bPH6J68BUPOcSFExxCk+zCyuNdXGYQ+HvUIHPCr/Zeec91cgrK6Gag7JNui04CC73ChVrH1HNxpmzCzQBNdMbGj3/m7wno6ipx993ewQiR/YGVTIiq1skpGtgmysr2GfFvRUn/6NdP9cW0P9DIjmGGXzCpbA8XU+0cmqF825Kie1RMhO8BbCczCOmYpi+/VS8oEwAb5O2jDkq6C934suuprOhWqgZgS9Qj9rf1cSoR8w19UKzjjbFCqakXW5NyIxx4ImDu+MLEzTQz1uqy/Fx3X5maaDy1eCm7dF4HrP8/cuqK2tBoDPDboG8dQIheIA/zh92opA0U1qqiCtlO2k9ZKPGZkhREN0wYcRG/UkFtF3LS/cIcW4jbNKK7RjMO6Q0Flr9Yah6CJan1uKIQ8MUFkxLQWLrwNXhS55pDgfJ9xzSCPhUIfhMYdcF78jmLHHk21VD56n4qs6W6sxlxgY8BEwjUPpvL/6t+6Sq2JeW6p7h5RH1kZ1noER4TZS/tGPy+VA+YkgXFQXZzmAOzzAsSSr3+Pwkk5xrM+32jZAuUOQT+43pM3vhftrK6AzLuo8NHn3fr5o4Q5f/oc21YH0eHa+v4QpznRrKcrKPJudAQGnkjSwumGpdeVhT86n4WPe0yD6AFOSKCQHFFvtyS6KzI1lIad5Q4A4JNp73oWa7/cFjvpWxsHsQtDGeFoJapshbMvJ+ftGOWx5KlFEjHEWC40aXWFkoNMVovBxbd+wgrjOGYd3RhxgGKlTW7Ck3IGwXP6DtP6Llq/jMc0qIX1ZEjEzsZAicpUrJQBZbxP5+CsQZLOmQUIMXtKGKKHRHTXL6VGCd0vzcXUrET8Gdp4oFyYU+s79Ok9exuOzjIahROpLwgs1ZWGKYjMQ+RkrdWoQbA5d5ft3jKH5TpMGClHqDJBMtfNrDBDokvyTk1slrVJBpKJH1VApjGrVrZ7OTF60yUhe+2YbRKzgthF7S9weyYLtEteHtDlf+pdnFNfHCMEguQ9XoWmbhrWnwSHy7lJ25AEWmZB0Ocb6DVFeJY01hbqSZF+ZA/L8p0gV33EOJAX5bd+6p5SZz/Aja8C9fTKjYnGAX8EJYFt6VV+V/6ObS0P+N/eAXAZMuaMb3DM8RFKZmleSdyojrq4LjtFSKZdce5QZpyfJAl6WL9qJ8bKTi7X6FcUcdNo/J/C1V864iIcSWLUONvYai//DAbj8aAbIFox9ARUldHIzO8SOZduJqEQFcsQiOlNlu7FJ+pJZB8oAxAZ8iw5e8EYkdI/wPlvjmlLq2fgqljv8jVw2nthYFDC0pio4L99JrLAluo2peBaCKoPuuRJ4RSViXVNAN9aeXPa7/53pn9YEUnhyEKThPCwZ9d1RN2rIxN5QaiI4OX76ZTBLQR0wpmiU9GyRviT4LMcYL2vcOXTDxqZ+mYARe1HDZO0V8ihIh9y8DW1lCvQQlv34U+4ZG3UrOQZFG2R0XLzmxiTZRpsUvmM+lRYVQVWpW5V6MU6E+R/ChW6KjjeRABBd/9Lx5X6t+07WfYL2SnlK/2sSjgbStO7RlLwtn3Vn5Brp4WCdcVhs84iO5P6IFYulCb70Sa3V1xVpXQgcjvVHgMq8x3L6pf5/YG5F/io84OFaE7l+mhSWIZjMp4mqrg+g9aHVrRX9HWFpBXzkOlImmf206fjWjkZT/OAQUPAkgpka8f5zz1OSEGaXXE6N/buuMoM0hDdtWu3+6HyxgLyFn8ER9khHGtD1BEh7twU1AePhf6Nj46pUb+dsEIZ53L+wczJQcqc0VCTQWn2SXEz+e1cfhj0pKCm5VC09LZmVIcAYK5mqZ/Wgjrg7ZAqGb5SgUiNgBP5LpdjTak8JGi6BGjwQFzUl8MCa/XEbru4JkpdyZ3jzHT0XytE2utXywAB5eCBHLLekXPcbKlr0DKUhkoAhXvJzL0KqYeh24KN6kFo1podmreuHoNJW04sxcIMmRPVnYPBFBuz/ZfezsH3spfFmtrRtSPIGHtaWmy4P2S/q+k3cZj5IodWznVzrHh2I4aSM4GnqcSHHLQicaz/GXXk/XJp+kf0Q5QdY6lj39NPOUt6PQApTfo2p8pQCy95C0rOgxg+CEVdHonjfmupTre17YSxA7c+oK/Vb19LanX7HiaSF6UrhVCek8Q31LNSsLXEMOQxSSZ/sVhYb6P+xDPDzKaBTFJqj9jwass5yvy0u4y6kiNyauJrdqwNYBrG8FwJ3u7ma9kbrpZcnUA8S+7Hk8LfeCNdEqnj83zYnHVc+HIrSDzXiZ/bJvZS8AVLHqjafYvI14zRntmSoImuX/ftuinno4SBdp5XNdPenhamEzQ8YY+Ypx0pG4H9jZpBi4mcH2BP414HxFu4aMyKvAJ9E1Xmw5mh/imdSl8cgNr6xjlgzaUJZEZdgRfkWRf2IDWueQu+xczZ8ThTu50Ae4rpTmnkhDFI2gA+xDzXB5MmuvB1WLlF2Z/JrTSkwGMOXoS7k0nQnl8dbe+3u80DDWPMWgpSS2dvjLxULI+EHmSdhqxEW3GdadDcQRZBMGjg12y/jtwKg6acPGVlvIMoQ8noi1XB6RKn8pe9dUsAW19LWIm0jSrGVfUGRnL2h9dOz8CSKHtpcg5VvlDPx+JvjdRDK4KBVpm58wmy+w+DMW3zOVSKLsznq8u9VUpNH99+FT/KSETO4ooSW7ge+0copS1S4v/DIQDNJus24qofW7J/qRv7DCW2xyoecA1l9ENfGchCPxLhFnckT0RBBnflJSy/IP38rrUVdeKp/6dfZhyTcz1kl2doskND+yDZkjZBEKYABdu2SmCS7qfP2UwDdo63Cm+oBn4+/rASVX8PZfzAY9ngfHeE4vDMEEi66ZYCoyHjbFI13YEL0v8hPRoMDqv0dZOQhwjhU97HKgRM9j/zGMj9M5ncnhvfCRk52l9mv4vVAyTxFGDmn/3m9CLynf9l/2bKbYrQqpoP07Hd0jwtKfy+xHzGZ5jNSzByiQJpa6vWkzJdIMKHR/rDl85qBeAwCSruEy2V7RUNJdN9CY0WxP1NoPKggjVIAQcp2MWgRdnTtsmLOPS//Vu2wrLX9CksWSuGsFQIMJuHmboKanHOdYSFTL2XPXqrUq7Xi5FPuTLPFMIo0JWbVwSv2cFvI/+Eb8eJG20cisIy2E3XvrO2QZCzwaQ5aHqCFjRixSPo4C7UAcm/OrX4w8dSxiOJ6De+n1c1A0pW5tGfY+KvdZ+Yn71qqWWb4V9hDFwBnkIPtOb8Tr55gdW5V/CsacCcoxP0pXob1/bRa5c+Ek21PuDiemZ0p93Y9rimRPbAt6cMDEaMDREB6HqqiVUhswa9t7azVPYtrPUOnj4kLtDTtrvKN13aGDUn7HOYes4FYXcuAolDUwboQJtU/psSL6/xECV4vZhRp03gAyl4s29FvKwXosKR3xyDny4jn9uAryQyuWxkL1uBAbhOBz59/twzIW0ddyewyraQTn18b4NLeo+jLn4W5Z/4rgaX2jLnGKnY2UPR+CdE6fjBpWlhlXPvRy09PeuZIYSgGXEleCJl1asdj22a05iLhgahonwAOfLdvf0dMlWILCM220FAvk6xAirHyvDTQ/iG1USh+2DN/+iHn4guysx4NbuvIVfCDdV3yf1I6PJXy8D/aXbPmCUlHoKNWCda3fgNDli3/6+pVr9RsA5qu0WkgkjDa1bm8jeVG0cpFrmJNQW7jv+JcUNCnYrAKzwjKnjLik0E+wsxBIjztHqiOlmACa2I1h/neurdb9OUUfZ2WzhBJMmI6I5KD9PDzLl2xmISmM2XGBAa9E5IbPF7+evWch86CZigNPIy0Wqyf4++3l3s/7vjFnn+8R1IgX+IE9hRdcHr3mVGYQuPPVMsMHF+64lUznu7lvTcZ8vOorgHTYNOXyPrfory8+pXksvr8vBtRh+5DgAS18WVL9q0R1zqN5kYytA/Mz/AniX5cXYeYFyGVLv9YhG9ZekL3uN/u9vqPm/Xi42wOiq4x8UjOyxE2fioL84TWW/3CpbeFhqPZOPFsfFyNBPQ8S1/Zubl5M6vsmlxO93DF+w22RI1fwkP8/VWnK0YHaG0GhqDl420Rb+NZHbuBU/25kUG8MuDCS0YWCCFZ/jqirqE69ntt8i3M6TI6247nyccqhh91GesLnOST7QkVkuRLg7reRXKFlutl/fiLB7SR6uD1hisRmDlSPydxDB0H5ritT/RsZErhkqEbClM213SY2EK94e1tee6asFEvgPGtXF3J6hOIiOcP0+scGfEdLs/eKZrSmeCgNclZDs2Ic3NXxBFJt33KWA23VDbBB9B6sGvkmNpIsmWj4ddfQnmiAtjIoa2mmyA4NQ4zRzRmTLhvyHj0jE6w4TzsNPmR2sp7aS9Rrvt9ZLwz4UTsT+Ja6vxivOh9en+ebycRWEnTxOuvuZucHIcFRVObYOs8Iuwu/hE1iKa0R3cxcXsxQMD0gwzGbNCnY10mE1nYGK1NTLZVG/sc6N9oiaTSOemi7BAqDET7TGw8irFRNa6fioD7RUCaOnlUXtWdJna9fWOecta+gSO6vkHEKDKuY4GtArEixCoqdamHU+4IlFgYkEvr0wNTkzfPl5eV7KLNPIOlGnB0vsSP61A0dRKOJrC46y1JaQnfC7rzvkg+ueFHxQ+txyyusNcavxYKY8ahjT3SNKpCWDz3rG/crflvKyOqa4zHlhMT/S80ZyuJxHAtAbzaMzNzpsjgeKLViTnV+CzWTJZ3w2E4Ov+M+YHITk7JJOhxvM0a6pTIV/JtPddelfaJ/fhYHAFlk3OPqRqf2PeTChUphzGsAy2U215hrHzb7gFQLHQdrL4aaD3Hv5AjPNRM4Prs+upJT1LevO8LHYr1RF7uDOLn3H1wD6mqlo35X1jTWZq/U3HxO2MUre07fgVpSLNMdeA/1uwhzT3dK/U7z1F/okXitZvOZ0LhuihWtNUvZnSzWs3LbE7lFoSgdvcu+0idlssmHpBO0cUIAHKpE5bMHApN+LCvlo+m7rknQ11pSnuAzX5ERrrYlByQ32fS/+hTY1uOjG0jduuekA1eI6LtYNobJbf0V08IcA6O3diiSpTkCMgqedoSjlHRbasCV2jV3TDjJc5cZ9rhPWglc0RtAtrQfYV85aVP4q9fktmXqHi9yZh6KQRcn1U5qr4L+GxYZoTvcv1Sda/Qtdb7PgxeG7Wuo5GBx8JxBVTMahEsKGue67PmZQLxirTuruYKX9/CcIo1a5Z+VHHR5H2S16xkVyVB2KI92E34XB6noP0h1aAR6b1U32CV4pjBvZmjhWXR731pYaFBNRMS7WjCgTXfGAfah5TEIq2QG4eV8LPfnM+figU6Pk0Mt+CBYwCYr5SFDlWeGSw+AMUSdgtq3GT5XFqYSmH37G9ab8hLQbDFN/2a63PYYLDgtRRreoGaMpud5TzzG/t6g4X1tOzgbW1YhSv5bSFIhle7AumcMJpMP7kJujZFAxCOlqAEtX7YnYTxPf4JOWPUgOhy34SJ0QfktE7IFO/SvYv4Gu09RGwJOim5mfkVkCGjY/NYO9ZW4ktcgW+Vgc+Bp8YU8yW69mGhS+J9kPxyRTBeV3KBNRIo8/3jJd9Mi8Qn1RUWeA40hu6eKWa5UXU82c/VzzzKpZeNRotI1bNX6JkmnRAF7n8fe9opIE+5JA3DsEfzTWwP+TBVThGoFOn24rW8OCBW3etTt+gzqsFUorplHUXbl8OPbO006BNnjS1vTjUlBtYvhUguBbjbw6JKJ5vs7/A14kAlbKQ1bgNypILo4z7xbHoWsB9FZ2flucMXicTxWjmlai2/pSbuw89dfVAgbXZHpSUmhBrc+F84NjR0vlVhhokKihyK64l0M57QvqCjHiKARkP1b41RLiCeit3fg1Ffh9le3MHzL4wHjsckJ1IgWXXIQdu+N2WGi3BOfpclWtPU0maC8fLUrG+95oEGmT7Dg185YzLRqI7Ba3kzGrX1Epj3bySVRrrZaOQx23QNBhorLtG/6dt5Bh6WRn3+M4/lF4qHl6erZCre4e2YXRTjtvS/FFhMRwyiYpYEdby9L5kYurAWiLqark8DApJ0YPt9SkSr4AM02XO9omppLP2ERrQKrN4I1eDSAimwMeuS8vyaYziKX5JJ5/BA/s0QVf7mVLpHXph8o1ceTVOERGIoTziSiUoktMMcIe6dj7Sso0NBuSaWBAVLO4uxHYjKRINHVq5UBRuOdh2/NgFycUn3lL9YvaZInnnmPkFr2O03OoRQXax3y78kNUawAbprTcZ0hrLo7Ol942yNaCzdYmG2LqhsmPr5vqdHIjZnY7gzD45cr+dpECif+TnAuYgrlxQP9ZA5aYtYcfMsGwRsbgV9XRQ5VyXGp9GyP+1LpPLLIDVOvVKKggojYE291NhxETzCuQZRP6P7Cw02PF9oZa55zn7aWOsg7UuvWAfFZ6d7ww7I2ag3wAJrJyGK7VCMdngcwxtTj0Q4kOwknQ1JqdI31EMJX3qbnSEWBN7aMQ1u76sSyz1U9rQfQfZ3t0XNkPCo4JG2FMlCOf5DUXlIj4E0CJcfxavNeN9w1uSVenugBOSXmTw8odbKqyRuiYOstreN1Z00OcdneMvC6fl3ClvW5kKytbGwI4zMXlwwn95Gesm+1qGmdwruKoFfcpyJPONYOMYzgYpmYz7964C+5UKBLaPCUwQRevSzmQfUs9HXY/ceEiT7biCjnHZCeWbRQYX910akO8yBA0QrvMhmIqmsAatQM07gIdz4JkhUwhLirGEDJN4dCTv4+kVVPfRl904PMOV2YwSyOZa5qQ1r83k4KVBc1nO0YaBu8I19nS9QicjwCWLlFi5utKMxkG9Y+Ku4hWaVo9O4yTO7wj49TKJpT6nDJzrI6qcVOLeF8lP/MIJJFUJY00gROliWmLfr4Mbo4AN96EkOGPBdtabTLLw4ffPx4FfNSsPbZhkUWzbj8hRCjnRN/jFQjzV0ApLnxbZrNQWUN30ubklTwp0TFuTjR3NHY4SgGo1vc3LxlDTcsBuuZWJCdziARqch+bv68QK0iZ9bVPBaKio7G7jm3538acesuSSvpJeUvsnu4P3gsqsMilKqnnS+wVJIbwZ7PmuWR1atUv5UM0WiT9+WMbarVRl5MaLgKGk9HbVrlX+RKKlDpoZg+e/mxS4gLSjUzq7C+ZIrrmvBEH7DLwuweioa1KaWBHm+dQUZyoaOWR2986qHCSyYLMudiw9WjXMYQCMAfyQhLtZmPyt6UCB/V8PfmEGKIq03spCKl+3x2hcIteiS0ZH3l6WTMQyEL0JL6GlQ+sPbOUrxP/Ohc9quvRAhgNYraaP4u92XgX3pfCX+VRvfWVx6ez6mJmXeJqhwf34qI+ECR1Ed+/YW90siy5+NGquHij6DgV10O4H7mmrDKozv8RZNHJgXjFUiPO7hfXsUDvBNkKY9acU2yXGC/mXVvABf7BQCok8fDCj2KmDGlz88cR6e3EbfpX8XcKbv6nejRvLnP8aa82N3xwVb3NfeBjySHtr7WI5Vfwu4xoyjqBFxhN48l0rBkDM1HLHj/K4gigspFn0rXYuAZDELXzdOcAJm0QvH71N++aQRPf/DgKh0e1Y/f8XM1LYf1B8YqFrcJizctIzX7UkLT1s23wblnhDounWtBXOuC4ZZGhCB86AtrNKMGEPxLJDD1Jx/PVfJdwEB3A0MRf+iz74bQVRDDdyHQV8leJKBF0FzOJv2iHwK7yr6nnRNElBXOAgxdFJPaZxwEYF/uEo1dQArlnpmQWsF2at1Z693mgS9ZfIUGQ8WiLtPxp6Ct3l9GSyWfZ76DQxAapjF2h0+DZcPNIvVbOyRQeZ8Z1WmvVGGcVYQkz4hnicJGLcmYXWFOG4w7VWvPEYdcVUOJ2lPgAHGQmb08LGqp9Lu8/uIWDm9uIGQdwL7dnYYMkaSgWPU+tSaMXdOR94T3iAqqjpdkb8nYRCY9VfGBR85gyghhldc/20DEy2Y0VfGQYV4S4Es0kK6Myi0qJG7V1fb6IUeyk+CId5Jv55njJTtANxPGW/Io7sw5E98/HTX0A262/Nz2YmuRDLOOY9KPfinWemKsp9jlsVmyQkXeabidKiVyc6/zil/QQ1GRM/UIPk9ei8v7LTJqLNL0TMU5am8WelI0SVmUiMYvJjBVCo/83bAsWJzCI9vIw3d+HR6BoCwe/9WGPrC8Wx4DDOdKnoxIojr6hzgnwTcTqY+W8sQkraFRMgpbrXZMMN4ml6xW4NcgNM1qnh5GsFAwx4IfXpZyEQS5NKytjSvHY5CBJrq6QcyeIrYQT/+OfgS/dEHzK9r75Vw7pyCgbTtL9/UL+Fs4mOP9V5992bEjqC0wORobRVY7C+a+p9wy3dtPEQZxhUrrHwibyoHaG21vBkIgq4EFg/VXIJguWCLSX+GDNN3nZaI9suaj58iogCaad00AODKpKMmA+nuFoFKOF/LIUFWZwMh77anss4sCIoUGNErOStq0oa0eJklWZQRRTm0KATuFk00E3D88pJouYW3P29BirR3DmqpZoX3z2jj7mSnf6efJPUjToRT23/2a8KmlJalZsyMbnQMp+u9CowVl0faCU3m7zFUDxfGOfiQf0Sj1p06G4pZVnyLQRIkT0X4fEypMhay0PNsRJDKHkF/EB/MohcbmzDPRRBivkSAJ2tc9uc5a5IuJ5RIKaIwPxbP/WBM7x/EvTlANABwDcQv8HfrgFj0e+O11dmbBVB6uRjSfTf/IPSpVnAkbvnjvbytqpBmG57wyPd53biA/CW/t3vY7hF/2ebWOa74B9EpnxgStNGwnq/FF4Rv8coQq9887me3jhahb8WCxQrnwfLYjTOt1IUGOliQPbBBAZHu628GAS5S8W/+t1g+7dRvUOxeEFws+5iFmFzSSeYqmKBiwvRUTuBQ+C2p42NcyrC15wg0kGl85usT5w0YIVw8o5vTHZhlqGDO5DrpMNEXl4cJvkrvJq58wsNH/Lp9miPkNieKiqBjuV9HJlAuGpGCkFHq3neghXZnZR8Y1J9BPOV3yZKfitjgF+2mnu+i63Oa90gpV9PAyI+Nw/B9uT55Jtp9rdKva9UkG9N79JAELmKcMZ8gHgDZUEok+ucBwi00XYfuTxL6iDOLFDy+ljHWHSPgLd3VyUl7N0A0gtqGw5e6af07aTRXNwjz1uAclWUlAEkgKMz2Ek6yO8q7hpFdDkaAcdVAd00Q+Ndy7q2zqQ4vdYNYslsHDV9SpPmFBhyH2TNXd5fXw30yluKsr2gf1x00QKDBl6Pbt9E0KH+VReDNp/aRU+A4YwPUquRrEFlDKCN/EkgukrCIl9A3ZwigD3ud0hJ91a+4at8OrAb7Bp871BLUeteogyAfcQZ57ldV+o5OZzLC0P5M0kNmuD4rX3wGqmoEfSbzSwSUS9T2qtKb3xFcUZYj3u9UkeWkltUEjCiTvDNZ8jZGnofjWbl3PjyPCmc8QQ7y+bMsqaUDwxnOPhtUaSsbl3nNZgu++KcBykzXplgomJSqUmPK2r6oWhAhq8+rI+IVlSc1Erm4PIBClQAXduDzqXfY0MU5uHatPsljHTGprXcZVb0T0g2upRVHzXMOsLQ+Dy+SlUQRup22YYnewiWKM9/bVrIDFpmzJxbjsN8Sh7wr1wj7JST+UpZJGmKRrFSpqitFMoNjE5wVjX+68ErrtOkXgEmhN9bkkdM09d9kCuspbNyqZXdg7N1jM109FCTOVsd+cHaD8M77x75b46yoK7uZRo8O1PgxtGXJ2Dv6+79PgStgxVbRZFJiRX8+ZsMXlFOynCFBhOC47inew6eiWLJ5Sz/nDxvoqvSSywoN9b0YzgDU8rxNQXLUxZs7mo75TOB0T+v6u1TmxJVV1aaAF03PQZ26XTpBL/3v3xiA5piGJE3Az6UHgOCINB9udMQ/TEVuaXa7eKJYwMIzjKTP2EZwqkXVZSGfbX8NaxxufmyfvbGKpGuDYm+x4RQFNJXTXD9cKNySdvlv//jtouO1yswLSgzO06DLGfhXj4/Rmoss1zoQC/mQMnbR7rra5l7GXL0ZBXOEQ69bQ5eDckdOde7W0s9l8seenC+f1YakBfXS7lqKWH9l7Nkm8WXEFp+FtpMY7BCLSqwBnfsHiVzFnGrh5n0rP4znzw2NJnt1qs7xaM61jFXUGOsdWoDHbOutZyPR4Jhk4+deCuMsgIRhzpsnkAV3YHPgCnCiOWojWBdnfFE43IhQikZjqqbpBaIqFwkg7cXw4Qd5SkjkA874MYfVkitYLDe1PxsRfkvq8G727eryxzdH9skVqnA14M2bFjw+kFOE0agM0jtkBCywBWWDgVLIB0MWS8RxqDhxyQ2uXa4yQ0METCMJrq4Hxvr0lhvHf86GOA6Rqe4e4KmBquEF9KFdgDLu1xdEJmmy4GAzKYbTfldxgExtVUnWxdUX3f5M1d1ui+NE362Jz18D2ByyS428I0ABtuOms3RgR4U2UD1U5UKX9VvSbg3CfMoNlXpUYCzTiYyeoT3jeNotTSzxIvIVH3JRtE4RXZFztJfWMMVXZQSXSVPGBIAXLE1zDt7eNS/iGmCXNzOva5HhDmvZgwEQ4vSS7svcIw3fLKmVDiIEp/cY5mWYazFJ+OfG0DZs3ZnlhRgJDtXFK6RBQWIEBJhnw75dmsqjtFJdTxDyA0llHmxKokt33zglR+y+zVWLyUdNV7anCr6CwOFVjPYTspOEMYlwIVmj8VW4F+dR0zv3Qt6maVOSy7tIE8UTXeOt88qmxLWywB3mu0cCzS784mk6JMqa0j8ydlTBubcg7gNg8jd8SN0K2NUWlnOtXb+dCXQxn4wp4lkB3xpISXUBHnopSMYj9jX73WKLE2aHdbHsXqPUIA8oY4LRue7IdzIcMhhdZtj2jLvuohL6Nk67X3k+ByKdKEPwYB1GoMp7x0NWF1Z4wc5qkFbe2ZzyG/0efAY2AGwnfa8mLcn+dI5+d0GRdaT+oDR6RwPQLUPT0Ogc6ORLh/lBZ/f9Rz/KC6+7u1Y1csMiTf8GWZ3E06rDDwXyqDBQdymB4ELNywkVyGomef3nLxQfqsc41i883tiAW2nRhvB/CnSiMjtaIqZf72Psd6ZXhERigfMWb0Fi/ymHd4e/ADPtdC6Yo9bkDffAuyfjNUMNJhML3Evj20FVp6ab+s8Zq7ygyVRRjVSgBf318v9C8zL3IEAR32wzE+97heMDUUJbTNYVwwWE3nFS+fGPjWEQz76k7OukSnlV0ugAG4xDhXjAwc/AIhxXexL9Yuxf2xYOYv5V3R09I3ROEpbfVHfnLvuw87mZegYYgdVrXUWE0y/eCOGsrWu2kxAhP1FKs+eM/B4+C1JFIYtLAa7EXDahG/j1OKyrOAtuZuA3iCv3COEltSCGMw85w2HFp/Tsxm5Rl34SEo4zxUg78LeL0WAuBe9lvTRM9CEWcVA4SWW2wnS3mt1Z54Cw9ZTlzgc5ho1uunqrFfbtZn6M4SDElqACCVVdisFehaT2gOkXiRALEvLDfIZ2j0410QiqpVoX4+tCu4XOzEzia8Yspby/wpqm429VMN4YEMymQTLLpa2yRI4BY2W6LxfoDfR3VSdC6DumLzeQNCm//Iz5jFcknQz95TAsUANxlJJ1cjcF7dpiAdJPbBotWAaQzX0i23Sz+tMZcoHRDz4SiFtyOvQsIRRz5zte7oLoLdPdljtbVGvAaMhDXM/PjnhKpPiGgBeReNrLRY2G9VfTRPVNMFlhHEU7NX51cRnwEHRDZyVxCIeCU1WPVRy61OLOMPzH4r0wgYoC/yKjoCAdmcR9xXmMz5Pz3kdwJsEDuzPF+M3jp6l5NvjsrltzUAzUTGLb9bxfEq4FdGA7kZ+erHXsqzRlcTS/RDnKWsz2k1/AeT3kfXz4In8q+f/wCOm4P3IEmMRH+xQh7k/kd4FzCLA6T+sbJMa9kCRlUn/nQ8Xvf2GmiYCcAXdf9hNgjcRACPACKpyJRGUSm3FVGq+qbJHbku9xNgKSNMsqTnPKlzse6e6CpINr6txwUmUPPzGB7LpheU5jwSx9mNF1tRaMKpKN285Qrz4Yx2bdfYFPWAcJSL/Ya3Jfjy14hZ5+/njknckggqkB6QiaqoH3UxP3wBAcGzZZkJSxqNjJeUVsNI36flCo9MneOvkfY+T5ZbqWhLYzn3L439puOJQHQmD64H8G516Jan5r7KIhG9jGIQkGcMxjP7TlQRZUKsUxdoA97Iu6wiW76BWM1F5AHsD0E9cB+X5gNKUiJB+B+LKpgbOfJP29AmDewti/qNJe8er2IHtf5p1TRFWnBRXgg/PZzEALz0HWQX/M1G2bXJXeHWbdLUoy6AW1HXNDBOVF7P6gtEm3zwIkVkCsN4vTasIA6j2JBzHJ67aeVdevywwL+n7Hfmct6bfOhFzI/10S++tW5D6RTwcdWnBNen2+LNkTT6qgGihE+qMsACYTApAuYKjd2WG5AxiCJufwDvIlWLr6ka/CZnpX86JHP85LL9d7kyvFF2IbGj1hVG28Jk/wX3hZ4dYGd7F2W4p522uqazSBSySXfxWZKQDgJ3jgi2pz1kJfAw1dbpG/EFufsyD4I2nFZEWuUvZSkrGRDVfTw+JZUDae5hgnQ9Sw06iEg20xSgeDvPXCUkJXLPtuOLeT4zcJiXW1C42WFCgXbLY+HB+sIwqjY+Pn+pWpX4bNjwvj5zLXtzdczfQKfF5pHnMviG2xmJJmTapGuCv67Kxn74yKMuwD5KBneYbCY+k6gDVkZHITmo+q+uo+02Jgr1TFrVCQS9B8cmnSkCMRJQJsopRdNLnaUr9ZOJEMLfFDAX30fl7cpRy1UjpzY+VGtseRFmBt7T+DsBBd/CAOisXpNiRI2jE+igAG/0nmbFeIn2Iv7F02JDoTV8hSBf8E/9ungpyVQTgycnQ5IpXI5lIREGAhVGlraVGBcST8Pd+GTO03TUSAmRpQRmYN7Ak2jIopPaR53ROR3JlAXlQSVareu7n4B697fBNQ2OY0ibfrTaF5LzlJBDS9LrE70nq9SafsieclrGFUz4cLJOzX5sxtmFF62y5iBiebvv5y2+JXPu4qW3UirZDUKZHpcTccz2Yyf2Oy140uz2m0NvXLWC8KgiizF1p5FLFTqzYqIG8FXYIutqLjgE+XxK2wdMof7zrF4P7TxKgVDu0s+irkJF80ikgHd9HDs6hRC4DlL4+rICyay1MjS3POeS/cI+CSKvzz/AFdhi0FFfM2lScT48AH21ixYXGp0D+CSqvKwtTinB9ytG64jLR1sqbzZHLLh0H4JeUrAC3e+C9DrVeNue1WQVePRSvQJy7p2hV06kxMlRtG5nu81qgxkxus8qTU2eCc7Fv32xlvY4lOiud5atAzG1wwIyUymIE6E3rL+VwQqdm+ZKZYhACJAj2QqFwUP1QLEBq/1nnu5nFjFvD9cy31rj \ No newline at end of file +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 \ No newline at end of file diff --git a/server/app/socket/server-status.js b/server/app/socket/server-status.js index 5f881d8..fe27ce9 100644 --- a/server/app/socket/server-status.js +++ b/server/app/socket/server-status.js @@ -1,12 +1,12 @@ -const { Client: SSHClient } = require('ssh2') -const { createTerminal } = require('./terminal') -const { createSecureWs } = require('../utils/ws-tool') +import ssh2Module from 'ssh2' +const { Client: SSHClient } = ssh2Module +import { createTerminal } from './terminal.js' +import { createSecureWs } from '../utils/ws-tool.js' const monitorMap = new Map() // key -> { sockets: Set, statusData, stop } const pendingConnections = new Map() // key -> Promise,跟踪正在创建的连接 -const { HostListDB } = require('../utils/db-class') +import { HostListDB } from '../utils/db-class.js' const hostListDB = new HostListDB().getInstance() -/* eslint-disable no-control-regex */ function stripAnsi(s = '') { return s .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '') // CSI @@ -16,7 +16,7 @@ function stripAnsi(s = '') { const safeDelta = (curr, prev) => (curr >= prev ? curr - prev : 0) -module.exports = (httpServer) => { +export default (httpServer) => { const serverIo = createSecureWs(httpServer, '/server-status') let connectionCount = 0 @@ -593,7 +593,7 @@ module.exports = (httpServer) => { try { const cgroupMem = await getCgroupMemoryInfo() if (cgroupMem) { - if (cgroupMem.memTotalBytes != null) { + if (cgroupMem.memTotalBytes !== null) { const cgroupTotalMb = Math.round(cgroupMem.memTotalBytes / 1024 / 1024) if (cgroupTotalMb > 0 && cgroupTotalMb < memInfo.totalMemMb) { const cgroupUsedMb = Math.max(0, Math.min(Math.round(cgroupMem.memUsedBytes / 1024 / 1024), cgroupTotalMb)) @@ -607,7 +607,7 @@ module.exports = (httpServer) => { } } } - if (cgroupMem.swapTotalBytes != null) { + if (cgroupMem.swapTotalBytes !== null) { const cgroupSwapTotalMb = Math.round(cgroupMem.swapTotalBytes / 1024 / 1024) if (cgroupSwapTotalMb > 0 && (swapInfo.swapTotal === 0 || cgroupSwapTotalMb < swapInfo.swapTotal)) { const cgroupSwapUsedMb = Math.max(0, Math.min(Math.round(cgroupMem.swapUsedBytes / 1024 / 1024), cgroupSwapTotalMb)) diff --git a/server/app/socket/sftp-v2.js b/server/app/socket/sftp-v2.js index 55e83a4..2509c0f 100644 --- a/server/app/socket/sftp-v2.js +++ b/server/app/socket/sftp-v2.js @@ -1,16 +1,17 @@ -const rawPath = require('path') -const fs = require('fs-extra') -const SFTPClient = require('ssh2-sftp-client') -const iconv = require('iconv-lite') -const chardet = require('chardet') -const { v4: uuidv4 } = require('uuid') -const { sftpCacheDir } = require('../config') -const { createSecureWs } = require('../utils/ws-tool') -const { HostListDB, FavoriteSftpDB } = require('../utils/db-class') -const { getConnectionOptions, handleProxyAndJumpHostConnection } = require('./terminal') +import rawPath from 'node:path' +import fs from 'fs-extra' +import SFTPClient from 'ssh2-sftp-client' +import iconv from 'iconv-lite' +import chardet from 'chardet' +import { v4 as uuidv4 } from 'uuid' +import { sftpCacheDir } from '../config/index.js' +import { createSecureWs } from '../utils/ws-tool.js' +import { HostListDB, FavoriteSftpDB } from '../utils/db-class.js' +import { getConnectionOptions, handleProxyAndJumpHostConnection } from './terminal.js' const hostListDB = new HostListDB().getInstance() const favoriteSftpDB = new FavoriteSftpDB().getInstance() -const { Client: SSHClient } = require('ssh2') +import ssh2Module from 'ssh2' +const { Client: SSHClient } = ssh2Module function shellEscape(s) { // eslint-disable-next-line quotes @@ -1559,7 +1560,7 @@ const listenAction = (sftpClient, socket) => { } } -module.exports = (httpServer) => { +export default (httpServer) => { const serverIo = createSecureWs(httpServer, '/sftp-v2', { // 增加消息大小限制,支持大文件上传(默认1MB) maxHttpBufferSize: 10 * 1024 * 1024, // 10MB diff --git a/server/app/socket/terminal.js b/server/app/socket/terminal.js index f7162fb..b6238a2 100644 --- a/server/app/socket/terminal.js +++ b/server/app/socket/terminal.js @@ -1,17 +1,20 @@ -const path = require('path') -const { Client: SSHClient } = require('ssh2') -const { sendNoticeAsync } = require('../utils/notify') -const { ping } = require('../utils/tools') -const { AESDecryptAsync } = require('../utils/encrypt') -const { KeyDB, HostListDB, CredentialsDB, ProxyDB } = require('../utils/db-class') -const decryptAndExecuteAsync = require('../utils/decrypt-file') -const { sessionManager, SessionStatus } = require('../utils/terminal-session') -const { createSecureWs } = require('../utils/ws-tool') +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import ssh2Module from 'ssh2' +const { Client: SSHClient } = ssh2Module +import { sendNoticeAsync } from '../utils/notify.js' +import { ping } from '../utils/tools.js' +import { AESDecryptAsync } from '../utils/encrypt.js' +import { KeyDB, HostListDB, CredentialsDB, ProxyDB } from '../utils/db-class.js' +import decryptAndExecuteAsync from '../utils/decrypt-file.js' +import { sessionManager, SessionStatus } from '../utils/terminal-session.js' +import { createSecureWs } from '../utils/ws-tool.js' const hostListDB = new HostListDB().getInstance() const credentialsDB = new CredentialsDB().getInstance() const proxyDB = new ProxyDB().getInstance() const keyDB = new KeyDB().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) async function getConnectionOptions(hostId) { const hostInfo = await hostListDB.findOneAsync({ _id: hostId }) @@ -137,11 +140,11 @@ async function handleProxyAndJumpHostConnection(options) { let proxySocket if (proxyConfig.type === 'socks5') { - const { createSocks5Connection = null } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + const { createSocks5Connection = null } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (!createSocks5Connection) throw new Error('Plus功能解锁失败: createSocks5Connection') proxySocket = await createSocks5Connection(proxyConfig, targetConnectionOptions.host, targetConnectionOptions.port) } else if (proxyConfig.type === 'http') { - const { createHttpConnection = null } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + const { createHttpConnection = null } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (!createHttpConnection) throw new Error('Plus功能解锁失败: createHttpConnection') proxySocket = await createHttpConnection(proxyConfig, targetConnectionOptions.host, targetConnectionOptions.port) } else { @@ -163,7 +166,7 @@ async function handleProxyAndJumpHostConnection(options) { } // 跳板机连接 else if (proxyType === 'jumpHosts' && Array.isArray(jumpHosts) && jumpHosts.length > 0) { - const { connectByJumpHosts = null } = (await decryptAndExecuteAsync(path.join(__dirname, 'plus.js'))) || {} + const { connectByJumpHosts = null } = (await decryptAndExecuteAsync(path.join(currentDir, 'plus.js'))) || {} if (!connectByJumpHosts) throw new Error('Plus功能解锁失败: connectByJumpHosts') const jumpHostResult = await connectByJumpHosts(jumpHosts, targetConnectionOptions.host, targetConnectionOptions.port, socket) if (jumpHostResult) { @@ -538,13 +541,15 @@ function createServerIo(serverIo) { }) } -module.exports = (httpServer) => { +export default (httpServer) => { const serverIo = createSecureWs(httpServer, '/terminal') createServerIo(serverIo) } -module.exports.getConnectionOptions = getConnectionOptions -module.exports.createTerminal = createTerminal -module.exports.createServerIo = createServerIo -module.exports.getProxyConfig = getProxyConfig -module.exports.handleProxyAndJumpHostConnection = handleProxyAndJumpHostConnection +export { + getConnectionOptions, + createTerminal, + createServerIo, + getProxyConfig, + handleProxyAndJumpHostConnection +} diff --git a/server/app/template/commonTemp.js b/server/app/template/commonTemp.js index 385ded9..ce29368 100644 --- a/server/app/template/commonTemp.js +++ b/server/app/template/commonTemp.js @@ -1,4 +1,4 @@ -module.exports = (content) => { +export default (content) => { return ` diff --git a/server/app/utils/db-class.js b/server/app/utils/db-class.js index 11a6b3f..1bcf4d2 100644 --- a/server/app/utils/db-class.js +++ b/server/app/utils/db-class.js @@ -1,5 +1,5 @@ -const Datastore = require('@seald-io/nedb') -const { +import Datastore from '@seald-io/nedb' +import { credentialsDBPath, hostListDBPath, keyDBPath, @@ -19,9 +19,9 @@ const { serverListDBPath, sessionDBPath, terminalSessionDBPath -} = require('../config') +} from '../config/index.js' -module.exports.KeyDB = class KeyDB { +export class KeyDB { constructor() { if (!KeyDB.instance) { KeyDB.instance = new Datastore({ filename: keyDBPath, autoload: true }) @@ -33,7 +33,7 @@ module.exports.KeyDB = class KeyDB { } } -module.exports.HostListDB = class HostListDB { +export class HostListDB { constructor() { if (!HostListDB.instance) { HostListDB.instance = new Datastore({ filename: hostListDBPath, autoload: true }) @@ -45,7 +45,7 @@ module.exports.HostListDB = class HostListDB { } } -module.exports.CredentialsDB = class CredentialsDB { +export class CredentialsDB { constructor() { if (!CredentialsDB.instance) { CredentialsDB.instance = new Datastore({ filename: credentialsDBPath, autoload: true }) @@ -57,7 +57,7 @@ module.exports.CredentialsDB = class CredentialsDB { } } -module.exports.NotifyDB = class NotifyDB { +export class NotifyDB { constructor() { if (!NotifyDB.instance) { NotifyDB.instance = new Datastore({ filename: notifyDBPath, autoload: true }) @@ -69,7 +69,7 @@ module.exports.NotifyDB = class NotifyDB { } } -module.exports.NotifyConfigDB = class NotifyConfigDB { +export class NotifyConfigDB { constructor() { if (!NotifyConfigDB.instance) { NotifyConfigDB.instance = new Datastore({ filename: notifyConfigDBPath, autoload: true }) @@ -81,7 +81,7 @@ module.exports.NotifyConfigDB = class NotifyConfigDB { } } -module.exports.GroupDB = class GroupDB { +export class GroupDB { constructor() { if (!GroupDB.instance) { GroupDB.instance = new Datastore({ filename: groupConfDBPath, autoload: true }) @@ -93,7 +93,7 @@ module.exports.GroupDB = class GroupDB { } } -module.exports.ScriptsDB = class ScriptsDB { +export class ScriptsDB { constructor() { if (!ScriptsDB.instance) { ScriptsDB.instance = new Datastore({ filename: scriptsDBPath, autoload: true }) @@ -105,7 +105,7 @@ module.exports.ScriptsDB = class ScriptsDB { } } -module.exports.ScriptGroupDB = class ScriptGroupDB { +export class ScriptGroupDB { constructor() { if (!ScriptGroupDB.instance) { ScriptGroupDB.instance = new Datastore({ filename: scriptGroupDBPath, autoload: true }) @@ -116,7 +116,7 @@ module.exports.ScriptGroupDB = class ScriptGroupDB { } } -module.exports.OnekeyDB = class OnekeyDB { +export class OnekeyDB { constructor() { if (!OnekeyDB.instance) { OnekeyDB.instance = new Datastore({ filename: onekeyDBPath, autoload: true }) @@ -128,7 +128,7 @@ module.exports.OnekeyDB = class OnekeyDB { } } -module.exports.PlusDB = class PlusDB { +export class PlusDB { constructor() { if (!PlusDB.instance) { PlusDB.instance = new Datastore({ filename: plusDBPath, autoload: true }) @@ -139,7 +139,7 @@ module.exports.PlusDB = class PlusDB { } } -module.exports.AIConfigDB = class AIConfigDB { +export class AIConfigDB { constructor() { if (!AIConfigDB.instance) { AIConfigDB.instance = new Datastore({ filename: aiConfigDBPath, autoload: true }) @@ -150,7 +150,7 @@ module.exports.AIConfigDB = class AIConfigDB { } } -module.exports.ChatHistoryDB = class ChatHistoryDB { +export class ChatHistoryDB { constructor() { if (!ChatHistoryDB.instance) { ChatHistoryDB.instance = new Datastore({ filename: chatHistoryDBPath, autoload: true }) @@ -161,7 +161,7 @@ module.exports.ChatHistoryDB = class ChatHistoryDB { } } -module.exports.FavoriteSftpDB = class FavoriteSftpDB { +export class FavoriteSftpDB { constructor() { if (!FavoriteSftpDB.instance) { FavoriteSftpDB.instance = new Datastore({ filename: favoriteSftpDBPath, autoload: true }) @@ -172,7 +172,7 @@ module.exports.FavoriteSftpDB = class FavoriteSftpDB { } } -module.exports.ProxyDB = class ProxyDB { +export class ProxyDB { constructor() { if (!ProxyDB.instance) { ProxyDB.instance = new Datastore({ filename: proxyDBPath, autoload: true }) @@ -183,7 +183,7 @@ module.exports.ProxyDB = class ProxyDB { } } -module.exports.FileTransferDB = class FileTransferDB { +export class FileTransferDB { constructor() { if (!FileTransferDB.instance) { FileTransferDB.instance = new Datastore({ filename: fileTransferDBPath, autoload: true }) @@ -194,7 +194,7 @@ module.exports.FileTransferDB = class FileTransferDB { } } -module.exports.TerminalConfigDB = class TerminalConfigDB { +export class TerminalConfigDB { constructor() { if (!TerminalConfigDB.instance) { TerminalConfigDB.instance = new Datastore({ filename: terminalConfigDBPath, autoload: true }) @@ -205,7 +205,7 @@ module.exports.TerminalConfigDB = class TerminalConfigDB { } } -module.exports.ServerListDB = class ServerListDB { +export class ServerListDB { constructor() { if (!ServerListDB.instance) { ServerListDB.instance = new Datastore({ filename: serverListDBPath, autoload: true }) @@ -216,7 +216,7 @@ module.exports.ServerListDB = class ServerListDB { } } -module.exports.SessionDB = class SessionDB { +export class SessionDB { constructor() { if (!SessionDB.instance) { SessionDB.instance = new Datastore({ filename: sessionDBPath, autoload: true }) @@ -227,7 +227,7 @@ module.exports.SessionDB = class SessionDB { } } -module.exports.TerminalSessionDB = class TerminalSessionDB { +export class TerminalSessionDB { constructor() { if (!TerminalSessionDB.instance) { TerminalSessionDB.instance = new Datastore({ filename: terminalSessionDBPath, autoload: true }) diff --git a/server/app/utils/decrypt-file.js b/server/app/utils/decrypt-file.js index f46e3e9..1e71c26 100644 --- a/server/app/utils/decrypt-file.js +++ b/server/app/utils/decrypt-file.js @@ -1,56 +1,143 @@ -const fs = require('fs-extra') -const path = require('path') -const CryptoJS = require('crypto-js') -const { AESDecryptAsync } = require('./encrypt') -const { RuntimeState } = require('./runtime-state') +import fs from 'fs-extra' +import { createRequire } from 'node:module' +import path from 'node:path' +import { pathToFileURL } from 'node:url' +import CryptoJS from 'crypto-js' +import { init, parse } from 'es-module-lexer' +import { AESDecryptAsync, SHA256Encrypt } from './encrypt.js' +import { RuntimeState } from './runtime-state.js' const runtimeState = new RuntimeState().getInstance() +const plusModuleCache = new Map() +const dataModuleUrlPattern = /data:text\/javascript;base64,[A-Za-z0-9+/]+={0,2}(?:#[A-Za-z0-9._-]+)?/g +const moduleLexerReady = init -function decryptAndExecuteAsync(plusPath) { - return new Promise(async (resolve) => { - try { - const decryptKeyCipher = runtimeState.getDecryptKey() - if (!decryptKeyCipher) { - throw new Error('缺少解密密钥') - } - const decryptKey = await AESDecryptAsync(decryptKeyCipher) - if (!decryptKey) { - throw new Error('解密密钥解析失败') - } - const encryptedContent = fs.readFileSync(plusPath, 'utf-8') - const bytes = CryptoJS.AES.decrypt(encryptedContent, decryptKey) - const decryptedContent = bytes.toString(CryptoJS.enc.Utf8) - if (!decryptedContent) { - throw new Error('解密失败,请检查密钥是否正确') - } - const customRequire = (modulePath) => { - if (modulePath.startsWith('.')) { - const absolutePath = path.resolve(path.dirname(plusPath), modulePath) - return require(absolutePath) - } - return require(modulePath) - } - const module = { - exports: {}, - require: customRequire, - __filename: plusPath, - __dirname: path.dirname(plusPath) - } - const wrapper = Function('module', 'exports', 'require', '__filename', '__dirname', - decryptedContent + '\n return module.exports;' - ) - const exports = wrapper( - module, - module.exports, - customRequire, - module.__filename, - module.__dirname - ) - resolve(exports) - } catch (error) { - logger.info('解锁plus功能失败: ', error.message) - resolve(null) - } - }) +function isPlusAvailable() { + return Boolean(runtimeState.getDecryptKey()) && !runtimeState.getPlusKicked() } -module.exports = decryptAndExecuteAsync +function sanitizeLoaderError(error) { + const message = error?.message || String(error) + return message.replace(dataModuleUrlPattern, '[Plus ESM module]') +} + +async function resolveImportSpecifiers(source, plusPath) { + await moduleLexerReady + const moduleUrl = pathToFileURL(path.resolve(plusPath)) + const resolver = createRequire(moduleUrl) + const resolveSpecifier = (specifier) => { + if (specifier.startsWith('node:')) return specifier + if (specifier.startsWith('.') || specifier.startsWith('/')) { + return new URL(specifier, moduleUrl).href + } + const resolvedPath = resolver.resolve(specifier) + return resolvedPath.startsWith('node:') ? resolvedPath : pathToFileURL(resolvedPath).href + } + + const [imports] = parse(source) + let resolvedSource = source + // 从后向前替换,保证 lexer 返回的源码下标不会因前面的替换而偏移。 + for (let index = imports.length - 1; index >= 0; index -= 1) { + const importRecord = imports[index] + // 非字面量动态导入(如 import(moduleName))在运行时才能确定,保持原样。 + if (typeof importRecord.n !== 'string') continue + const resolvedSpecifier = resolveSpecifier(importRecord.n) + const quote = importRecord.d >= 0 ? source[importRecord.s] : '' + const replacement = quote === '\'' || quote === '"' || quote === '`' + ? `${ quote }${ resolvedSpecifier }${ quote }` + : resolvedSpecifier + resolvedSource = `${ resolvedSource.slice(0, importRecord.s) }${ replacement }${ resolvedSource.slice(importRecord.e) }` + } + return resolvedSource +} + +async function resolveDecryptKey(decryptKeyCipher) { + const decryptKey = await AESDecryptAsync(decryptKeyCipher) + if (!decryptKey) { + throw new Error('解密密钥解析失败') + } + return { + decryptKey, + fingerprint: SHA256Encrypt(decryptKey) + } +} + +async function decryptAndImportOnce(plusPath, decryptKey) { + const encryptedContent = await fs.readFile(plusPath, 'utf-8') + const bytes = CryptoJS.AES.decrypt(encryptedContent, decryptKey) + const decryptedContent = bytes.toString(CryptoJS.enc.Utf8) + if (!decryptedContent) { + throw new Error('解密失败,请检查密钥是否正确') + } + const esmContent = await resolveImportSpecifiers(decryptedContent, plusPath) + const moduleId = path.basename(path.dirname(plusPath)) + const sourceLabel = `easynode-plus-${ moduleId }.mjs` + const encodedContent = Buffer.from(`${ esmContent }\n//# sourceURL=${ sourceLabel }\n`).toString('base64') + return import(`data:text/javascript;base64,${ encodedContent }#plus-${ moduleId }`) +} + +function createCacheEntry(absolutePath, decryptKeyCipher) { + const entry = { + keyFingerprint: null, + validatedCipher: decryptKeyCipher, + modulePromise: null + } + + entry.modulePromise = resolveDecryptKey(decryptKeyCipher) + .then(({ decryptKey, fingerprint }) => { + entry.keyFingerprint = fingerprint + return decryptAndImportOnce(absolutePath, decryptKey) + }) + .catch((error) => { + if (plusModuleCache.get(absolutePath) === entry) { + plusModuleCache.delete(absolutePath) + } + throw error + }) + plusModuleCache.set(absolutePath, entry) + return entry +} + +async function validateCachedAuthorization(entry, decryptKeyCipher) { + if (entry.validatedCipher === decryptKeyCipher) return + + const [, currentAuth] = await Promise.all([ + entry.modulePromise, + resolveDecryptKey(decryptKeyCipher) + ]) + if (entry.keyFingerprint !== currentAuth.fingerprint) { + throw new Error('Plus解密密钥已变化') + } + + // 同一明文密钥重新激活时密文会变化,仅在状态仍有效时记录本次校验结果。 + if (isPlusAvailable() && runtimeState.getDecryptKey() === decryptKeyCipher) { + entry.validatedCipher = decryptKeyCipher + } +} + +async function decryptAndExecuteAsync(plusPath) { + try { + if (!isPlusAvailable()) { + throw new Error(runtimeState.getPlusKicked() ? 'Plus授权已失效' : '缺少解密密钥') + } + + const absolutePath = path.resolve(plusPath) + const decryptKeyCipher = runtimeState.getDecryptKey() + const entry = plusModuleCache.get(absolutePath) + || createCacheEntry(absolutePath, decryptKeyCipher) + + await validateCachedAuthorization(entry, decryptKeyCipher) + const plusModule = await entry.modulePromise + + // 加载过程中授权可能被清除,返回模块前再次校验。 + if (!isPlusAvailable() || runtimeState.getDecryptKey() !== decryptKeyCipher) { + throw new Error('Plus授权状态已变化') + } + + return plusModule + } catch (error) { + logger.info('解锁plus功能失败: ', sanitizeLoaderError(error)) + return null + } +} + +export default decryptAndExecuteAsync diff --git a/server/app/utils/encrypt.js b/server/app/utils/encrypt.js index d446c6d..e96dbcc 100644 --- a/server/app/utils/encrypt.js +++ b/server/app/utils/encrypt.js @@ -1,7 +1,7 @@ -const CryptoJS = require('crypto-js') -const rawCrypto = require('crypto') -const NodeRSA = require('node-rsa') -const { KeyDB } = require('./db-class') +import CryptoJS from 'crypto-js' +import rawCrypto from 'node:crypto' +import NodeRSA from 'node-rsa' +import { KeyDB } from './db-class.js' const keyDB = new KeyDB().getInstance() // rsa非对称 私钥解密 @@ -41,7 +41,7 @@ const SHA256Encrypt = (clearText) => { return rawCrypto.createHash('sha256').update(clearText).digest('hex') } -module.exports = { +export { RSADecryptAsync, AESEncryptAsync, AESDecryptAsync, diff --git a/server/app/utils/get-plus.js b/server/app/utils/get-plus.js index ca4e7da..3b65428 100644 --- a/server/app/utils/get-plus.js +++ b/server/app/utils/get-plus.js @@ -1,20 +1,23 @@ -const crypto = require('crypto') -const path = require('path') -const { requestWithFailover } = require('./tools') -const { AESEncryptAsync } = require('./encrypt') -const decryptAndExecuteAsync = require('./decrypt-file') -const version = require('../../package.json').version -const { PlusDB } = require('./db-class') -const { RuntimeState } = require('./runtime-state') +import crypto from 'node:crypto' +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { requestWithFailover } from './tools.js' +import { AESEncryptAsync } from './encrypt.js' +import decryptAndExecuteAsync from './decrypt-file.js' +import PackageJsonModule from '../../package.json' with { type: 'json' } +const version = PackageJsonModule.version +import { PlusDB } from './db-class.js' +import { RuntimeState } from './runtime-state.js' const plusDB = new PlusDB().getInstance() const runtimeState = new RuntimeState().getInstance() +const currentDir = dirname(fileURLToPath(import.meta.url)) const RETRY_INTERVAL_MS = 30 * 1000 let maxRetryCount = 3 let retryTimer = null async function startHeartbeatIfAvailable() { - const plusModule = await decryptAndExecuteAsync(path.join(__dirname, '../controller/plus.js')) + const plusModule = await decryptAndExecuteAsync(path.join(currentDir, '../controller/plus.js')) if (typeof plusModule?.startHeartbeat === 'function') { plusModule.startHeartbeat() } else { @@ -102,5 +105,5 @@ function scheduleNextActivation() { if (retryTimer.unref) retryTimer.unref() } -module.exports = getLicenseInfo -module.exports.startActivation = activateOrRetry +export default getLicenseInfo +export { activateOrRetry as startActivation } diff --git a/server/app/utils/notify.js b/server/app/utils/notify.js index c4be6ce..45a56a1 100644 --- a/server/app/utils/notify.js +++ b/server/app/utils/notify.js @@ -1,9 +1,9 @@ -const nodemailer = require('nodemailer') -const TelegramBot = require('node-telegram-bot-api') -const axios = require('axios') -const FormData = require('form-data') -const commonTemp = require('../template/commonTemp') -const { NotifyDB, NotifyConfigDB } = require('./db-class') +import nodemailer from 'nodemailer' +import TelegramBot from 'node-telegram-bot-api' +import axios from 'axios' +import FormData from 'form-data' +import commonTemp from '../template/commonTemp.js' +import { NotifyDB, NotifyConfigDB } from './db-class.js' const notifyConfigDB = new NotifyConfigDB().getInstance() const notifyDB = new NotifyDB().getInstance() @@ -251,7 +251,7 @@ async function sendNoticeAsync(noticeAction, title, content) { } } -module.exports = { +export { sendNoticeAsync, sendServerChan, sendEmail, diff --git a/server/app/utils/plus-server.js b/server/app/utils/plus-server.js index 63d09f6..2db1d99 100644 --- a/server/app/utils/plus-server.js +++ b/server/app/utils/plus-server.js @@ -1,6 +1,6 @@ -module.exports = { - plusServers: [ - 'https://easynode-auth1.chaoszhu.com', - 'https://easynode-auth2.chaoszhu.com' - ] -} +const plusServers = [ + 'https://easynode-auth1.chaoszhu.com', + 'https://easynode-auth2.chaoszhu.com' +] + +export { plusServers } diff --git a/server/app/utils/runtime-state.js b/server/app/utils/runtime-state.js index 23f8c21..eeb5d13 100644 --- a/server/app/utils/runtime-state.js +++ b/server/app/utils/runtime-state.js @@ -70,4 +70,4 @@ class RuntimeState { } } -module.exports = { RuntimeState } +export { RuntimeState } diff --git a/server/app/utils/ssl-cert.js b/server/app/utils/ssl-cert.js index 7f76370..ce1ef2f 100644 --- a/server/app/utils/ssl-cert.js +++ b/server/app/utils/ssl-cert.js @@ -1,4 +1,4 @@ -const selfsigned = require('selfsigned') +import selfsigned from 'selfsigned' /** * 生成自签名证书 @@ -53,6 +53,6 @@ function generateSelfSignedCert() { } } -module.exports = { +export { generateSelfSignedCert } diff --git a/server/app/utils/terminal-session.js b/server/app/utils/terminal-session.js index 3beb984..0b6bb26 100644 --- a/server/app/utils/terminal-session.js +++ b/server/app/utils/terminal-session.js @@ -200,7 +200,7 @@ class TerminalSession { } // 创建新的SSH连接 - const { Client: SSHClient } = require('ssh2') + const { Client: SSHClient } = (await import('ssh2')).default const newClient = new SSHClient() await new Promise((resolve, reject) => { @@ -299,7 +299,7 @@ class SessionManager { */ async loadConfigFromDB() { try { - const { TerminalSessionDB } = require('./db-class') + const { TerminalSessionDB } = await import('./db-class.js') const terminalSessionDB = new TerminalSessionDB().getInstance() const config = await terminalSessionDB.findOneAsync({}) @@ -327,7 +327,7 @@ class SessionManager { // 数据库中存储的是KB,需要转换为字节 this.maxBufferSize = config.maxBufferSize * 1024 } - logger.info(`已从数据库加载终端会话配置: maxSuspendedPerUser=${this.maxSuspendedPerUser}, maxSuspendTime=${this.maxSuspendTime / 1000 / 60 / 60}小时`) + logger.info(`已从数据库加载终端会话配置: maxSuspendedPerUser=${ this.maxSuspendedPerUser }, maxSuspendTime=${ this.maxSuspendTime / 1000 / 60 / 60 }小时`) } else { logger.info('数据库中无终端会话配置,使用默认配置') } @@ -644,7 +644,7 @@ class SessionManager { // 导出单例 const sessionManager = new SessionManager() -module.exports = { +export { SessionStatus, TerminalSession, sessionManager, diff --git a/server/app/utils/tools.js b/server/app/utils/tools.js index ef58081..e9216fc 100644 --- a/server/app/utils/tools.js +++ b/server/app/utils/tools.js @@ -1,11 +1,11 @@ -const { exec } = require('child_process') -const crypto = require('crypto') -const os = require('os') -const net = require('net') -const iconv = require('iconv-lite') -const axios = require('axios') +import { exec } from 'node:child_process' +import crypto from 'node:crypto' +import os from 'node:os' +import net from 'node:net' +import iconv from 'iconv-lite' +import axios from 'axios' const request = axios.create({ timeout: 3000 }) -const { plusServers } = require('./plus-server') +import { plusServers } from './plus-server.js' // 为空时请求本地IP const getNetIPInfo = async (searchIp = '') => { @@ -438,7 +438,7 @@ const timingSafeEqual = (a, b) => { return crypto.timingSafeEqual(bufA, bufB) } -module.exports = { +export { getNetIPInfo, getLocalNetIP, throwError, diff --git a/server/app/utils/verify-auth.js b/server/app/utils/verify-auth.js index 7a246a3..f5af040 100644 --- a/server/app/utils/verify-auth.js +++ b/server/app/utils/verify-auth.js @@ -1,8 +1,8 @@ -const jwt = require('jsonwebtoken') -const { AESDecryptAsync } = require('./encrypt') -const { isAllowedIp, getClientIP } = require('../utils/tools') -const { SHA256Encrypt } = require('../utils/encrypt') -const { KeyDB, SessionDB } = require('./db-class') +import jwt from 'jsonwebtoken' +import { AESDecryptAsync } from './encrypt.js' +import { isAllowedIp, getClientIP } from '../utils/tools.js' +import { SHA256Encrypt } from '../utils/encrypt.js' +import { KeyDB, SessionDB } from './db-class.js' const keyDB = new KeyDB().getInstance() const sessionDB = new SessionDB().getInstance() @@ -95,7 +95,7 @@ const verifyWsAuthSync = async (socket, next) => { next() } -module.exports = { +export { enumLoginCode, verifyAuthSync, verifyWsAuthSync, diff --git a/server/app/utils/ws-tool.js b/server/app/utils/ws-tool.js index d248088..6f430a4 100644 --- a/server/app/utils/ws-tool.js +++ b/server/app/utils/ws-tool.js @@ -1,5 +1,5 @@ -const { Server } = require('socket.io') -const { verifyWsAuthSync } = require('./verify-auth') +import { Server } from 'socket.io' +import { verifyWsAuthSync } from './verify-auth.js' const createSecureWs = (httpServer, path, otherConfig = {}) => { const serverIo = new Server(httpServer, { @@ -16,6 +16,6 @@ const createSecureWs = (httpServer, path, otherConfig = {}) => { return serverIo } -module.exports = { +export { createSecureWs -} \ No newline at end of file +} diff --git a/server/bin/www b/server/bin/www index be8f123..5ecdd79 100755 --- a/server/bin/www +++ b/server/bin/www @@ -1,3 +1,3 @@ -#!/usr/bin/env node -console.log('start time', new Date()) -require('../index.js') +#!/usr/bin/env node +console.log('start time', new Date()) +await import('../index.js') diff --git a/server/dev-local.js b/server/dev-local.js index b3ad05f..67d10f1 100644 --- a/server/dev-local.js +++ b/server/dev-local.js @@ -1,7 +1,11 @@ -const { spawn } = require('child_process') -const treeKill = require('tree-kill') +import { spawn } from 'node:child_process' +import { createRequire } from 'node:module' +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import treeKill from 'tree-kill' -const serverRoot = __dirname +const serverRoot = dirname(fileURLToPath(import.meta.url)) +const require = createRequire(import.meta.url) const nodemonBin = require.resolve('nodemon/bin/nodemon.js') const child = spawn(process.execPath, [nodemonBin, 'index.js'], { cwd: serverRoot, diff --git a/server/index.js b/server/index.js index 0694170..9b70d98 100644 --- a/server/index.js +++ b/server/index.js @@ -1,5 +1,8 @@ // AES-256-CBC requires a 32-byte key. Keep the random bytes as a Buffer; // converting them to hex produces a 64-character value and breaks RDP token encryption. -global.rpdToken = require('crypto').randomBytes(32) -require('dotenv').config() -require('./app/main.js') +import crypto from 'node:crypto' +import dotenv from 'dotenv' + +global.rpdToken = crypto.randomBytes(32) +dotenv.config() +await import('./app/main.js') diff --git a/server/package.json b/server/package.json index 5939bc4..a082b37 100644 --- a/server/package.json +++ b/server/package.json @@ -2,6 +2,7 @@ "name": "server", "version": "3.7.1", "description": "easynode-server", + "type": "module", "bin": "./bin/www", "scripts": { "local": "cross-env EXEC_ENV=local node dev-local.js", @@ -30,6 +31,7 @@ "cross-env": "^7.0.3", "crypto-js": "4.2.0", "dotenv": "^16.4.5", + "es-module-lexer": "^2.3.1", "eslint": "^10.4.1", "fs-extra": "^11.2.0", "global": "^4.4.0", @@ -37,7 +39,7 @@ "http-proxy-middleware": "^3.0.5", "iconv-lite": "^0.6.3", "jsonwebtoken": "9.0.2", - "koa": "2.16.2", + "koa": "2.16.4", "koa-body": "^6.0.1", "koa-compose": "^4.1.0", "koa-compress": "^5.1.1", diff --git a/server/test/test-rest-api-auth.js b/server/test/test-rest-api-auth.js index 6d4bf64..46fba4c 100644 --- a/server/test/test-rest-api-auth.js +++ b/server/test/test-rest-api-auth.js @@ -1,4 +1,4 @@ -const axios = require('axios') +import axios from 'axios' const BASE_URL = 'http://localhost:8082/api/v1' diff --git a/server/test/test-ws-comprehensive.js b/server/test/test-ws-comprehensive.js index bb305d4..25d1f04 100644 --- a/server/test/test-ws-comprehensive.js +++ b/server/test/test-ws-comprehensive.js @@ -1,4 +1,4 @@ -const io = require('socket.io-client') +import io from 'socket.io-client' const SERVER_URL = 'http://localhost:8082' const VALID_TOKEN = 'U2FsdGVkX1+26QCTo4JlGxKnQFcDjOxHyZthx/zucoUMh4CVGc3d4DdBPG2LK52gLuO5RRtmnclYoZESSyQrP7RDoJxh4PLkF6Dm7m3PiO112i1DWxp/xANsRQBzotJ1soPa1ym82BNQRNoziZHbG2RmC/wM953x1zEBgK0wD6LW0I9cQMNIaiO+TV8f/qYt9m4QEoG3UBnLWCN5+oHU0DTOMJQgXBl7zFVwj/9YENt0rN5tBLZSSZv6krM94lmTO7V/mXycUqa/fWJuz7+B0ThlxFNFwzHdizGB+iqTHLoFrDL9Nw0d4oHnBOEkzSmwjIzNysVttfESpjAjNZ6gfmpV7+qERGxt9VHTogWP2cIaT5Dqjkc1ldG63yfYzXCx' diff --git a/yarn.lock b/yarn.lock index b14a8f5..bc35a01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2706,6 +2706,11 @@ es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-module-lexer@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.3.1.tgz#5bf2df06999dbbe5f006a5f46a11fb9f5b7b391b" + integrity sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -4053,10 +4058,10 @@ koa2-connect-history-api-fallback@^0.1.3: resolved "https://registry.yarnpkg.com/koa2-connect-history-api-fallback/-/koa2-connect-history-api-fallback-0.1.3.tgz#dd07b6183ab111d48a6e14f0896d39397c2b879b" integrity sha512-6SucVC8sn7ffaAKOhpNn1OJUqZDK37Jg/PSBAGSribQIikU/Et1aOkUcPH34dm55Kl94RrH4d1snOG6EErzE1Q== -koa@2.16.2: - version "2.16.2" - resolved "https://registry.yarnpkg.com/koa/-/koa-2.16.2.tgz#1292a3b415a9b9f3dd6872c1835229630a4ecae3" - integrity sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q== +koa@2.16.4: + version "2.16.4" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.16.4.tgz#303b996f5c3f2a3bb771c7db5e4303ee05f2265f" + integrity sha512-3An0GCLDSR34tsCO4H8Tef8Pp2ngtaZDAZnsWJYelqXUK5wyiHvGItgK/xcSkmHLSTn1Jcho1mRQs2ehRzvKKw== dependencies: accepts "^1.3.5" cache-content-type "^1.0.0" @@ -4547,6 +4552,11 @@ object-inspect@^1.13.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== +object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -4858,7 +4868,14 @@ qrcode@^1.5.4: pngjs "^5.0.0" yargs "^15.3.1" -qs@6.13.1, qs@^6.11.0, qs@^6.14.1, qs@^6.5.2: +qs@6.13.1: + version "6.13.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" + integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== + dependencies: + side-channel "^1.0.6" + +qs@^6.11.0, qs@^6.5.2: version "6.15.2" resolved "https://registry.npmmirror.com/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9" integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw== @@ -5178,6 +5195,14 @@ side-channel-list@^1.0.0: es-errors "^1.3.0" object-inspect "^1.13.3" +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-map@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" @@ -5199,6 +5224,17 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" +side-channel@^1.0.6: + version "1.1.1" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"