package server import ( "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "github.com/gin-gonic/gin" "warpbox/lib/activity" "warpbox/lib/alerts" "warpbox/lib/config" "warpbox/lib/security" ) func TestAdminSecurityActionsWriteAuditTrail(t *testing.T) { app, router := setupAdminSecurityTest(t) for _, body := range []string{ `{"action":"ban","ip":"203.0.113.7"}`, `{"action":"unban","ip":"203.0.113.7"}`, } { request := httptest.NewRequest(http.MethodPost, "/admin/security/actions", strings.NewReader(body)) request.Header.Set("Content-Type", "application/json") request.AddCookie(authCookie(app)) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", response.Code, response.Body.String()) } } events, err := app.activityStore.List(100, app.config.ActivityRetentionSeconds) if err != nil { t.Fatalf("activity list error: %v", err) } if len(events) < 2 { t.Fatalf("expected activity events, got %d", len(events)) } alertsList, err := app.alertStore.List(100) if err != nil { t.Fatalf("alerts list error: %v", err) } if len(alertsList) < 2 { t.Fatalf("expected alerts for manual actions, got %d", len(alertsList)) } } func TestAdminSecurityBulkUnbanAndUnbanAll(t *testing.T) { app, router := setupAdminSecurityTest(t) app.securityGuard.Ban("203.0.113.8", 300) app.securityGuard.Ban("203.0.113.9", 300) request := httptest.NewRequest(http.MethodPost, "/admin/security/actions", strings.NewReader(`{"action":"bulk_unban","ips":["203.0.113.8"]}`)) request.Header.Set("Content-Type", "application/json") request.AddCookie(authCookie(app)) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("bulk_unban expected 200, got %d", response.Code) } if app.securityGuard.IsBanned("203.0.113.8") { t.Fatal("expected selected IP to be unbanned") } if !app.securityGuard.IsBanned("203.0.113.9") { t.Fatal("expected non-selected IP to remain banned") } requestAll := httptest.NewRequest(http.MethodPost, "/admin/security/actions", strings.NewReader(`{"action":"unban_all"}`)) requestAll.Header.Set("Content-Type", "application/json") requestAll.AddCookie(authCookie(app)) responseAll := httptest.NewRecorder() router.ServeHTTP(responseAll, requestAll) if responseAll.Code != http.StatusOK { t.Fatalf("unban_all expected 200, got %d", responseAll.Code) } if len(app.securityGuard.BanList()) != 0 { t.Fatal("expected all bans to be removed") } } func setupAdminSecurityTest(t *testing.T) (*App, *gin.Engine) { t.Helper() gin.SetMode(gin.TestMode) cwd, _ := os.Getwd() root := filepath.Clean(filepath.Join(cwd, "..", "..")) if err := os.Chdir(root); err != nil { t.Fatalf("chdir: %v", err) } t.Cleanup(func() { _ = os.Chdir(cwd) }) clearAdminSettingsEnv(t) t.Setenv("WARPBOX_DATA_DIR", t.TempDir()) t.Setenv("WARPBOX_ADMIN_PASSWORD", "secret") t.Setenv("WARPBOX_ADMIN_ENABLED", "true") cfg, err := config.Load() if err != nil { t.Fatalf("config load: %v", err) } if err := cfg.EnsureDirectories(); err != nil { t.Fatalf("ensure dirs: %v", err) } app := &App{ config: cfg, activityStore: activity.NewStore(filepath.Join(cfg.DBDir, "activity.json")), alertStore: alerts.NewStore(filepath.Join(cfg.DBDir, "alerts.json")), securityGuard: security.NewGuard(), } if err := app.reloadSecurityConfig(); err != nil { t.Fatalf("reload security config: %v", err) } t.Cleanup(func() { _ = app.securityGuard.Close() }) router := gin.New() admin := router.Group("/admin") admin.GET("/login", app.handleAdminLogin) protected := router.Group("/admin", app.adminAuthMiddleware) protected.POST("/security/actions", app.handleAdminSecurityAction) return app, router }