From de206bf85a948e7aa76f7aaad0131c2d8dde7668 Mon Sep 17 00:00:00 2001 From: Flik Date: Mon, 5 Jan 2026 21:17:09 +0800 Subject: [PATCH] feat: add authentication options for plugins including AuthEnabled, AuthUsername, and AuthPassword --- internal/server/db/interface.go | 11 ++-- internal/server/router/handler/store.go | 6 ++ internal/server/tunnel/server.go | 11 ++-- pkg/plugin/script/js.go | 5 +- web/src/api/index.ts | 26 ++++++++- web/src/views/ClientView.vue | 75 ++++++++++++++++++++++++- web/src/views/PluginsView.vue | 40 +++++++++++-- 7 files changed, 155 insertions(+), 19 deletions(-) diff --git a/internal/server/db/interface.go b/internal/server/db/interface.go index 03c59ca..16de3cf 100644 --- a/internal/server/db/interface.go +++ b/internal/server/db/interface.go @@ -15,14 +15,17 @@ type ConfigField struct { // ClientPlugin 客户端已安装的插件 type ClientPlugin struct { - ID string `json:"id"` // 插件实例唯一 ID + ID string `json:"id"` // 插件实例唯一 ID Name string `json:"name"` Version string `json:"version"` Enabled bool `json:"enabled"` - Running bool `json:"running"` // 运行状态 - Config map[string]string `json:"config,omitempty"` // 插件配置 - RemotePort int `json:"remote_port,omitempty"`// 远程监听端口 + Running bool `json:"running"` // 运行状态 + Config map[string]string `json:"config,omitempty"` // 插件配置 + RemotePort int `json:"remote_port,omitempty"` // 远程监听端口 ConfigSchema []ConfigField `json:"config_schema,omitempty"` // 配置模式 + AuthEnabled bool `json:"auth_enabled,omitempty"` // 是否启用认证 + AuthUsername string `json:"auth_username,omitempty"` // 认证用户名 + AuthPassword string `json:"auth_password,omitempty"` // 认证密码 } // Client 客户端数据 diff --git a/internal/server/router/handler/store.go b/internal/server/router/handler/store.go index 34cf9f6..51a652b 100644 --- a/internal/server/router/handler/store.go +++ b/internal/server/router/handler/store.go @@ -179,6 +179,9 @@ func (h *StoreHandler) Install(c *gin.Context) { if p.Name == req.PluginName { dbClient.Plugins[i].Enabled = true dbClient.Plugins[i].RemotePort = req.RemotePort + dbClient.Plugins[i].AuthEnabled = req.AuthEnabled + dbClient.Plugins[i].AuthUsername = req.AuthUsername + dbClient.Plugins[i].AuthPassword = req.AuthPassword // 确保有 ID if dbClient.Plugins[i].ID == "" { dbClient.Plugins[i].ID = pluginID @@ -212,6 +215,9 @@ func (h *StoreHandler) Install(c *gin.Context) { Enabled: true, RemotePort: req.RemotePort, ConfigSchema: configSchema, + AuthEnabled: req.AuthEnabled, + AuthUsername: req.AuthUsername, + AuthPassword: req.AuthPassword, }) } diff --git a/internal/server/tunnel/server.go b/internal/server/tunnel/server.go index 56a189f..35b8ccb 100644 --- a/internal/server/tunnel/server.go +++ b/internal/server/tunnel/server.go @@ -1319,10 +1319,13 @@ func (s *Server) pushClientInstalledPlugins(cs *ClientSession, alreadyPushed map } else if cp.RemotePort > 0 { // 安装成功后启动服务端监听器 pluginRule := protocol.ProxyRule{ - Name: cp.Name, - Type: cp.Name, - RemotePort: cp.RemotePort, - Enabled: boolPtr(true), + Name: cp.Name, + Type: cp.Name, + RemotePort: cp.RemotePort, + Enabled: boolPtr(true), + AuthEnabled: cp.AuthEnabled, + AuthUsername: cp.AuthUsername, + AuthPassword: cp.AuthPassword, } s.startClientPluginListener(cs, pluginRule) } diff --git a/pkg/plugin/script/js.go b/pkg/plugin/script/js.go index 93c27dd..82335b7 100644 --- a/pkg/plugin/script/js.go +++ b/pkg/plugin/script/js.go @@ -174,9 +174,12 @@ func (p *JSPlugin) Start() (string, error) { func (p *JSPlugin) HandleConn(conn net.Conn) error { defer conn.Close() + // goja Runtime 不是线程安全的,需要加锁 + p.mu.Lock() + defer p.mu.Unlock() + // 创建连接包装器 jsConn := newJSConn(conn) - p.vm.Set("conn", jsConn) fn, ok := goja.AssertFunction(p.vm.Get("handleConn")) if !ok { diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 34562ab..a2e72a6 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -49,8 +49,30 @@ export const disablePlugin = (name: string) => post(`/plugin/${name}/disable`) // 扩展商店 export const getStorePlugins = () => get<{ plugins: StorePluginInfo[] }>('/store/plugins') -export const installStorePlugin = (pluginName: string, downloadUrl: string, signatureUrl: string, clientId: string, remotePort?: number, version?: string, configSchema?: ConfigField[]) => - post('/store/install', { plugin_name: pluginName, version: version || '', download_url: downloadUrl, signature_url: signatureUrl, client_id: clientId, remote_port: remotePort || 0, config_schema: configSchema || [] }) +export const installStorePlugin = ( + pluginName: string, + downloadUrl: string, + signatureUrl: string, + clientId: string, + remotePort?: number, + version?: string, + configSchema?: ConfigField[], + authEnabled?: boolean, + authUsername?: string, + authPassword?: string +) => + post('/store/install', { + plugin_name: pluginName, + version: version || '', + download_url: downloadUrl, + signature_url: signatureUrl, + client_id: clientId, + remote_port: remotePort || 0, + config_schema: configSchema || [], + auth_enabled: authEnabled || false, + auth_username: authUsername || '', + auth_password: authPassword || '' + }) // 客户端插件配置 export const getClientPluginConfig = (clientId: string, pluginName: string) => diff --git a/web/src/views/ClientView.vue b/web/src/views/ClientView.vue index a494af8..c88190a 100644 --- a/web/src/views/ClientView.vue +++ b/web/src/views/ClientView.vue @@ -88,6 +88,14 @@ const storePlugins = ref([]) const storeLoading = ref(false) const storeInstalling = ref(null) // 正在安装的插件名称 +// 安装配置模态框 +const showInstallConfigModal = ref(false) +const installPlugin = ref(null) +const installRemotePort = ref(8080) +const installAuthEnabled = ref(false) +const installAuthUsername = ref('') +const installAuthPassword = ref('') + // 日志查看相关 const showLogViewer = ref(false) @@ -111,11 +119,34 @@ const handleInstallStorePlugin = async (plugin: StorePluginInfo) => { message.error('该插件没有下载地址') return } + // 打开配置模态框 + installPlugin.value = plugin + installRemotePort.value = 8080 + installAuthEnabled.value = false + installAuthUsername.value = '' + installAuthPassword.value = '' + showInstallConfigModal.value = true +} - storeInstalling.value = plugin.name +const confirmInstallPlugin = async () => { + if (!installPlugin.value) return + + storeInstalling.value = installPlugin.value.name try { - await installStorePlugin(plugin.name, plugin.download_url, plugin.signature_url || '', clientId, 8080, plugin.version, plugin.config_schema) - message.success(`已安装 ${plugin.name},可在配置中修改端口和其他设置`) + await installStorePlugin( + installPlugin.value.name, + installPlugin.value.download_url || '', + installPlugin.value.signature_url || '', + clientId, + installRemotePort.value || 8080, + installPlugin.value.version, + installPlugin.value.config_schema, + installAuthEnabled.value, + installAuthUsername.value, + installAuthPassword.value + ) + message.success(`已安装 ${installPlugin.value.name}`) + showInstallConfigModal.value = false showStoreModal.value = false await loadClient() } catch (e: any) { @@ -748,6 +779,44 @@ const handleDeletePlugin = (plugin: ClientPlugin) => { + + + +
+

插件: {{ installPlugin.name }}

+

{{ installPlugin.description }}

+
+
+

远程端口:

+ +
+
+ + + 启用 HTTP Basic Auth + +
+ +
+ +
+ diff --git a/web/src/views/PluginsView.vue b/web/src/views/PluginsView.vue index 2797920..aafb4e8 100644 --- a/web/src/views/PluginsView.vue +++ b/web/src/views/PluginsView.vue @@ -231,10 +231,18 @@ const showInstallModal = ref(false) const selectedStorePlugin = ref(null) const selectedClientId = ref('') const installing = ref(false) +const installRemotePort = ref(8080) +const installAuthEnabled = ref(false) +const installAuthUsername = ref('') +const installAuthPassword = ref('') const openInstallModal = (plugin: StorePluginInfo) => { selectedStorePlugin.value = plugin selectedClientId.value = '' + installRemotePort.value = 8080 + installAuthEnabled.value = false + installAuthUsername.value = '' + installAuthPassword.value = '' showInstallModal.value = true } @@ -258,11 +266,14 @@ const handleInstallStorePlugin = async () => { selectedStorePlugin.value.download_url, selectedStorePlugin.value.signature_url, selectedClientId.value, - 8080, // 默认端口,可在配置中修改 + installRemotePort.value || 8080, selectedStorePlugin.value.version, - selectedStorePlugin.value.config_schema + selectedStorePlugin.value.config_schema, + installAuthEnabled.value, + installAuthUsername.value, + installAuthPassword.value ) - message.success(`已安装 ${selectedStorePlugin.value.name},可在客户端配置中修改端口和其他设置`) + message.success(`已安装 ${selectedStorePlugin.value.name}`) showInstallModal.value = false } catch (e: any) { message.error(e.response?.data || '安装失败') @@ -453,7 +464,7 @@ onMounted(() => { --> - +

插件: {{ selectedStorePlugin.name }}

@@ -464,7 +475,26 @@ onMounted(() => { placeholder="选择要安装到的客户端" :options="onlineClients.map(c => ({ label: c.nickname || c.id, value: c.id }))" /> -

安装后可在客户端详情页配置端口和其他设置

+
+

远程端口:

+ +
+
+ + + 启用 HTTP Basic Auth + +
+