diff --git a/go-backend/internal/http/handler/config_access_test.go b/go-backend/internal/http/handler/config_access_test.go index 617cf77..c08cd47 100644 --- a/go-backend/internal/http/handler/config_access_test.go +++ b/go-backend/internal/http/handler/config_access_test.go @@ -67,7 +67,22 @@ func TestConfigGetRejectsSensitiveKeysWithoutAuth(t *testing.T) { assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") } -func TestConfigUpdateRejectsSensitiveKeys(t *testing.T) { +func TestConfigGetAllowsSensitiveKeysForAdmin(t *testing.T) { + router, r := setupConfigAccessTestRouter(t) + adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) + seedConfigValue(t, r, "jwt_secret", "jwt-secret") + + req := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"jwt_secret"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", adminToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assertHandlerConfigValue(t, resp, "jwt_secret", "jwt-secret") +} + +func TestConfigUpdateAllowsSensitiveKeysForAdmin(t *testing.T) { router, _ := setupConfigAccessTestRouter(t) adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) @@ -78,10 +93,10 @@ func TestConfigUpdateRejectsSensitiveKeys(t *testing.T) { router.ServeHTTP(resp, req) - assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") + assertHandlerCode(t, resp, 0) } -func TestConfigUpdateSingleRejectsSensitiveKeys(t *testing.T) { +func TestConfigUpdateSingleAllowsSensitiveKeysForAdmin(t *testing.T) { router, _ := setupConfigAccessTestRouter(t) adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) @@ -92,7 +107,95 @@ func TestConfigUpdateSingleRejectsSensitiveKeys(t *testing.T) { router.ServeHTTP(resp, req) - assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") + assertHandlerCode(t, resp, 0) +} + +func TestConfigUpdateAllowsCloudflareSecretKeyWrite(t *testing.T) { + router, r := setupConfigAccessTestRouter(t) + adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) + + req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update", bytes.NewBufferString(`{"cloudflare_secret_key":"turnstile-secret"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", adminToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assertHandlerCode(t, resp, 0) + + cfg, err := r.GetConfigByName("cloudflare_secret_key") + if err != nil { + t.Fatalf("get config: %v", err) + } + if cfg == nil || cfg.Value != "turnstile-secret" { + t.Fatalf("expected cloudflare_secret_key to be updated, got %#v", cfg) + } +} + +func TestConfigUpdateSingleAllowsCloudflareSecretKeyWrite(t *testing.T) { + router, r := setupConfigAccessTestRouter(t) + adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) + + req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update-single", bytes.NewBufferString(`{"name":"cloudflare_secret_key","value":"turnstile-secret"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", adminToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assertHandlerCode(t, resp, 0) + + cfg, err := r.GetConfigByName("cloudflare_secret_key") + if err != nil { + t.Fatalf("get config: %v", err) + } + if cfg == nil || cfg.Value != "turnstile-secret" { + t.Fatalf("expected cloudflare_secret_key to be updated, got %#v", cfg) + } +} + +func TestConfigUpdateAllowsLicenseKeyWrite(t *testing.T) { + router, r := setupConfigAccessTestRouter(t) + adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) + + req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update", bytes.NewBufferString(`{"license_key":"license-secret"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", adminToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assertHandlerCode(t, resp, 0) + + cfg, err := r.GetConfigByName("license_key") + if err != nil { + t.Fatalf("get config: %v", err) + } + if cfg == nil || cfg.Value != "license-secret" { + t.Fatalf("expected license_key to be updated, got %#v", cfg) + } +} + +func TestConfigUpdateSingleAllowsLicenseKeyWrite(t *testing.T) { + router, r := setupConfigAccessTestRouter(t) + adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) + + req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update-single", bytes.NewBufferString(`{"name":"license_key","value":"license-secret"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", adminToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assertHandlerCode(t, resp, 0) + + cfg, err := r.GetConfigByName("license_key") + if err != nil { + t.Fatalf("get config: %v", err) + } + if cfg == nil || cfg.Value != "license-secret" { + t.Fatalf("expected license_key to be updated, got %#v", cfg) + } } func setupConfigAccessTestRouter(t *testing.T) (http.Handler, *repo.Repository) { diff --git a/go-backend/internal/http/handler/handler.go b/go-backend/internal/http/handler/handler.go index e1958f3..8c4a180 100644 --- a/go-backend/internal/http/handler/handler.go +++ b/go-backend/internal/http/handler/handler.go @@ -393,8 +393,7 @@ func (h *Handler) getConfigByName(w http.ResponseWriter, r *http.Request) { return } configName := strings.ToLower(strings.TrimSpace(req.Name)) - switch configName { - case "license_key", "cloudflare_secret_key", "jwt_secret": + if repo.IsSensitiveConfigKey(configName) && !isAdminRequest(r) { response.WriteJSON(w, response.Err(403, "禁止访问敏感配置")) return } @@ -989,7 +988,7 @@ func (h *Handler) updateConfigs(w http.ResponseWriter, r *http.Request) { if key == "" { continue } - if repo.IsSensitiveConfigKey(key) { + if repo.IsSensitiveConfigKey(key) && !isAdminRequest(r) { response.WriteJSON(w, response.Err(403, "禁止访问敏感配置")) return } @@ -1030,7 +1029,7 @@ func (h *Handler) updateSingleConfig(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, response.ErrDefault("配置名称不能为空")) return } - if repo.IsSensitiveConfigKey(name) { + if repo.IsSensitiveConfigKey(name) && !isAdminRequest(r) { response.WriteJSON(w, response.Err(403, "禁止访问敏感配置")) return } @@ -1060,6 +1059,14 @@ func (h *Handler) updateSingleConfig(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, response.OKEmpty()) } +func isAdminRequest(r *http.Request) bool { + if r == nil { + return false + } + claims, ok := r.Context().Value(middleware.ClaimsContextKey).(auth.Claims) + return ok && claims.RoleID == 0 +} + func normalizeAndValidateConfigValue(key, value string) (string, error) { switch strings.TrimSpace(key) { case "app_logo", "app_favicon":