- Documented certificate management security features in security.md, including backup and recovery processes. - Implemented CertificateCleanupDialog component for confirming deletion of orphaned certificates when deleting proxy hosts. - Enhanced ProxyHosts page to check for orphaned certificates and prompt users accordingly during deletion. - Added tests for certificate cleanup prompts and behaviors in ProxyHosts, ensuring correct handling of unique, shared, and production certificates.
200 lines
6.3 KiB
Go
200 lines
6.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/internal/services"
|
|
)
|
|
|
|
// TestCertificateHandler_Delete_RequiresAuth tests that delete requires authentication
|
|
func TestCertificateHandler_Delete_RequiresAuth(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to open db: %v", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
// Note: NOT adding mockAuthMiddleware here to test auth requirement
|
|
svc := services.NewCertificateService("/tmp", db)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
r.DELETE("/api/certificates/:id", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/1", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 Unauthorized without auth, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCertificateHandler_List_RequiresAuth tests that list requires authentication
|
|
func TestCertificateHandler_List_RequiresAuth(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to open db: %v", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
// Note: NOT adding mockAuthMiddleware here to test auth requirement
|
|
svc := services.NewCertificateService("/tmp", db)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
r.GET("/api/certificates", h.List)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/certificates", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 Unauthorized without auth, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCertificateHandler_Upload_RequiresAuth tests that upload requires authentication
|
|
func TestCertificateHandler_Upload_RequiresAuth(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to open db: %v", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
// Note: NOT adding mockAuthMiddleware here to test auth requirement
|
|
svc := services.NewCertificateService("/tmp", db)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
r.POST("/api/certificates", h.Upload)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 Unauthorized without auth, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCertificateHandler_Delete_DiskSpaceCheck tests the disk space check before backup
|
|
func TestCertificateHandler_Delete_DiskSpaceCheck(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to open db: %v", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
// Create a certificate
|
|
cert := models.SSLCertificate{
|
|
UUID: "test-cert",
|
|
Name: "test",
|
|
Provider: "custom",
|
|
Domains: "test.com",
|
|
}
|
|
if err := db.Create(&cert).Error; err != nil {
|
|
t.Fatalf("failed to create cert: %v", err)
|
|
}
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
svc := services.NewCertificateService("/tmp", db)
|
|
|
|
// Mock backup service that reports low disk space
|
|
mockBackup := &mockBackupService{
|
|
availableSpaceFunc: func() (int64, error) {
|
|
return 50 * 1024 * 1024, nil // 50MB (less than 100MB required)
|
|
},
|
|
}
|
|
|
|
h := NewCertificateHandler(svc, mockBackup, nil)
|
|
r.DELETE("/api/certificates/:id", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert.ID), nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInsufficientStorage {
|
|
t.Fatalf("expected 507 Insufficient Storage with low disk space, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCertificateHandler_Delete_NotificationRateLimiting tests rate limiting
|
|
func TestCertificateHandler_Delete_NotificationRateLimiting(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to open db: %v", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
// Create certificates
|
|
cert1 := models.SSLCertificate{UUID: "test-1", Name: "test1", Provider: "custom", Domains: "test1.com"}
|
|
cert2 := models.SSLCertificate{UUID: "test-2", Name: "test2", Provider: "custom", Domains: "test2.com"}
|
|
if err := db.Create(&cert1).Error; err != nil {
|
|
t.Fatalf("failed to create cert1: %v", err)
|
|
}
|
|
if err := db.Create(&cert2).Error; err != nil {
|
|
t.Fatalf("failed to create cert2: %v", err)
|
|
}
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
svc := services.NewCertificateService("/tmp", db)
|
|
|
|
mockBackup := &mockBackupService{
|
|
createFunc: func() (string, error) {
|
|
return "backup.zip", nil
|
|
},
|
|
}
|
|
|
|
h := NewCertificateHandler(svc, mockBackup, nil)
|
|
r.DELETE("/api/certificates/:id", h.Delete)
|
|
|
|
// Delete first cert
|
|
req1 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert1.ID), nil)
|
|
w1 := httptest.NewRecorder()
|
|
r.ServeHTTP(w1, req1)
|
|
|
|
if w1.Code != http.StatusOK {
|
|
t.Fatalf("first delete failed: got %d", w1.Code)
|
|
}
|
|
|
|
// Delete second cert (different ID, should not be rate limited)
|
|
req2 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert2.ID), nil)
|
|
w2 := httptest.NewRecorder()
|
|
r.ServeHTTP(w2, req2)
|
|
|
|
if w2.Code != http.StatusOK {
|
|
t.Fatalf("second delete failed: got %d", w2.Code)
|
|
}
|
|
|
|
// The test passes if both deletions succeed
|
|
// Rate limiting is per-certificate ID, so different certs should not interfere
|
|
}
|