diff --git a/backend/internal/httpapi/server.go b/backend/internal/httpapi/server.go index 198eb8a..cca78c6 100644 --- a/backend/internal/httpapi/server.go +++ b/backend/internal/httpapi/server.go @@ -226,7 +226,7 @@ func (s *Server) registerRoutes() { adminAPI.GET("/dns/accounts", s.listDNSAccounts) adminAPI.POST("/dns/test", s.dnsOperation, s.testAdminDNS) adminAPI.POST("/dns/aliyun/check", s.dnsOperation, s.checkAliyunDNSDomain) - adminAPI.POST("/renewal/run", s.longOperation(certificateTaskTimeout), s.runRenewalNow) + adminAPI.POST("/renewal/run", s.requireSuperAdmin, s.longOperation(certificateTaskTimeout), s.runRenewalNow) domainAPI.POST("/domains/:id/certificate/import", s.longOperation(certificateTaskTimeout), s.importCertificateFiles) domainAPI.POST("/domains/:id/certificate/validate", s.longOperation(certificateTaskTimeout), s.validateCertificate) domainAPI.POST("/domains/:id/deploy", s.longOperation(certificateTaskTimeout), s.deployCertificateCurrent) diff --git a/backend/internal/httpapi/settings_api_test.go b/backend/internal/httpapi/settings_api_test.go index cfe289f..264275d 100644 --- a/backend/internal/httpapi/settings_api_test.go +++ b/backend/internal/httpapi/settings_api_test.go @@ -397,6 +397,29 @@ func TestRenewalAPIHidesWorkflowErrors(t *testing.T) { t.Fatalf("renewal API did not include the safe error message: %s", body) } } +func TestRenewalAPIRequiresSuperAdmin(t *testing.T) { + cfg := config.Default(t.TempDir()) + cfg.SetupReady = true + runCalls := 0 + server := New(Dependencies{ + Config: cfg, + RunRenewal: func(context.Context) (certificate.RenewalReport, error) { + runCalls++ + return certificate.RenewalReport{}, nil + }, + }) + cookie := httpUserSessionCookie(server, "renewal-admin", repository.UserRoleAdmin) + request := httptest.NewRequest(http.MethodPost, "/api/v1/renewal/run", nil) + request.AddCookie(cookie) + response := httptest.NewRecorder() + server.Handler().ServeHTTP(response, request) + if response.Code != http.StatusForbidden { + t.Fatalf("non-super-admin renewal request returned %d: %s", response.Code, response.Body.String()) + } + if runCalls != 0 { + t.Fatalf("renewal runner was called for a non-super-admin: %d", runCalls) + } +} func loginForSettingsTest(t *testing.T, server *Server) *http.Cookie { t.Helper() request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", strings.NewReader(`{"identifier":"admin","password":"Password123"}`))