diff --git a/backend/controllers/plugin_secret.go b/backend/controllers/plugin_secret.go
new file mode 100644
index 0000000..c95d80c
--- /dev/null
+++ b/backend/controllers/plugin_secret.go
@@ -0,0 +1,70 @@
+package controllers
+
+import (
+ "errors"
+ "net/http"
+ "strings"
+
+ "git.pcux.cn/xuans/Smail/config"
+ "git.pcux.cn/xuans/Smail/dto/response"
+ "git.pcux.cn/xuans/Smail/hooks"
+ "git.pcux.cn/xuans/Smail/services/access"
+ pluginservice "git.pcux.cn/xuans/Smail/services/plugin"
+ appcontext "git.pcux.cn/xuans/Smail/utils/context"
+)
+
+// PluginSecret 只维护数据库中的插件密钥,不写入文件配置或候选载荷。
+func PluginSecret(ctx *appcontext.Context, w http.ResponseWriter, req *http.Request) {
+ w.Header().Set("Cache-Control", "no-store")
+ if req.Method != http.MethodGet && req.Method != http.MethodPut {
+ w.Header().Set("Allow", "GET, PUT")
+ http.Error(w, "请求方法不支持", http.StatusMethodNotAllowed)
+ return
+ }
+ capability := access.CapabilityPluginReadGlobal
+ if req.Method == http.MethodPut {
+ capability = access.CapabilityPluginWriteGlobal
+ }
+ if ctx == nil || ctx.UserID <= 0 {
+ writeConfigAccessError(w, &access.Denial{Code: access.CodeAccessDenied})
+ return
+ }
+ if err := authorizeCapability(ctx, capability, "", false); err != nil {
+ writeConfigAccessError(w, err)
+ return
+ }
+ pluginID := strings.TrimPrefix(req.URL.Path, "/api/v1/plugin/secrets/")
+ field := map[string]string{"telegram_push": "botToken", "wechat_push": "appSecret"}[pluginID]
+ if field == "" {
+ http.NotFound(w, req)
+ return
+ }
+ service := hooks.PluginConfigService()
+ if service == nil {
+ response.NewErrorResponse(response.ServerError, "插件配置服务不可用", "").FPrint(w)
+ return
+ }
+ var status pluginservice.SecretStatus
+ var err error
+ if req.Method == http.MethodGet {
+ status, err = service.GetSecretStatus(pluginID, field)
+ } else {
+ var input struct {
+ Change config.SecretChange `json:"change"`
+ Version uint64 `json:"version"`
+ }
+ if !decodePluginJSONBody(w, req, &input) {
+ return
+ }
+ status, err = service.ChangeSecret(pluginID, field, input.Change, input.Version)
+ }
+ if err != nil {
+ if errors.Is(err, pluginservice.ErrPluginSettingConflict) {
+ writeConfigError(w, config.ErrGenerationConflict)
+ } else {
+ response.NewErrorResponse(response.ParamsError, "插件密钥操作失败,请检查输入后重试", "").FPrint(w)
+ }
+ return
+ }
+ response.NewSuccessResponse(status).FPrint(w)
+}
diff --git a/backend/listen/http_server/api_v1_compat.go b/backend/listen/http_server/api_v1_compat.go
index 08f7f8b..f4e3270 100644
--- a/backend/listen/http_server/api_v1_compat.go
+++ b/backend/listen/http_server/api_v1_compat.go
@@ -148,6 +148,9 @@ func registerV1TOTPRoute(mux *http.ServeMux) {
}
func registerV1PluginRoutes(mux *http.ServeMux) {
+ for _, id := range []string{"telegram_push", "wechat_push"} {
+ mux.HandleFunc("/api/v1/plugin/secrets/"+id, limitBody(16<<10, contextIterceptor(controllers.PluginSecret)))
+ }
getUserSettings := contextIterceptor(controllers.GetTelegramUserSettings)
updateUserSettings := limitBody(16<<10, contextIterceptor(controllers.UpdateTelegramUserSettings))
mux.HandleFunc("/api/v1/plugin/settings/telegram_push/user", func(w http.ResponseWriter, r *http.Request) {
diff --git a/frontend/src/views/components/ConfigSettings/ConfigEditor/ConfigEditor.vue b/frontend/src/views/components/ConfigSettings/ConfigEditor/ConfigEditor.vue
index 1c754e7..b07e02c 100644
--- a/frontend/src/views/components/ConfigSettings/ConfigEditor/ConfigEditor.vue
+++ b/frontend/src/views/components/ConfigSettings/ConfigEditor/ConfigEditor.vue
@@ -76,11 +76,18 @@
type="password"
show-password
clearable
- :disabled="!canWrite || sectionLoading || busy || !!sectionError || !!pendingCandidate"
+ :disabled="!canWrite || sectionLoading || busy || !!sectionError || !!pendingCandidate || (pluginSecretPage && !pluginSecretStatus)"
:placeholder="secretField.placeholder"
autocomplete="new-password"
@update:model-value="updateSecret"
/>
+