package handlers import ( "database/sql" "fmt" "net/http" "strconv" "github.com/Fimeg/RedFlag/aggregator-server/internal/config" "github.com/gin-gonic/gin" "github.com/lib/pq" _ "github.com/lib/pq" ) // SetupHandler handles server configuration type SetupHandler struct { configPath string } func NewSetupHandler(configPath string) *SetupHandler { return &SetupHandler{ configPath: configPath, } } // updatePostgresPassword updates the PostgreSQL user password func updatePostgresPassword(dbHost, dbPort, dbUser, currentPassword, newPassword string) error { // Connect to PostgreSQL with current credentials connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/postgres?sslmode=disable", dbUser, currentPassword, dbHost, dbPort) db, err := sql.Open("postgres", connStr) if err != nil { return fmt.Errorf("failed to connect to PostgreSQL: %v", err) } defer db.Close() // Test connection if err := db.Ping(); err != nil { return fmt.Errorf("failed to ping PostgreSQL: %v", err) } // Update the password _, err = db.Exec("ALTER USER "+pq.QuoteIdentifier(dbUser)+" PASSWORD '"+newPassword+"'") if err != nil { return fmt.Errorf("failed to update PostgreSQL password: %v", err) } fmt.Println("PostgreSQL password updated successfully") return nil } // createSharedEnvContentForDisplay generates the .env file content for display func createSharedEnvContentForDisplay(req struct { AdminUser string `json:"adminUser"` AdminPass string `json:"adminPassword"` DBHost string `json:"dbHost"` DBPort string `json:"dbPort"` DBName string `json:"dbName"` DBUser string `json:"dbUser"` DBPassword string `json:"dbPassword"` ServerHost string `json:"serverHost"` ServerPort string `json:"serverPort"` MaxSeats string `json:"maxSeats"` }, jwtSecret string) (string, error) { // Generate .env file content for user to copy envContent := fmt.Sprintf(`# RedFlag Environment Configuration # Generated by web setup - Save this content to ./config/.env # PostgreSQL Configuration (for PostgreSQL container) POSTGRES_DB=%s POSTGRES_USER=%s POSTGRES_PASSWORD=%s # RedFlag Server Configuration REDFLAG_SERVER_HOST=%s REDFLAG_SERVER_PORT=%s REDFLAG_DB_HOST=%s REDFLAG_DB_PORT=%s REDFLAG_DB_NAME=%s REDFLAG_DB_USER=%s REDFLAG_DB_PASSWORD=%s REDFLAG_ADMIN_USER=%s REDFLAG_ADMIN_PASSWORD=%s REDFLAG_JWT_SECRET=%s REDFLAG_TOKEN_EXPIRY=24h REDFLAG_MAX_TOKENS=100 REDFLAG_MAX_SEATS=%s`, req.DBName, req.DBUser, req.DBPassword, req.ServerHost, req.ServerPort, req.DBHost, req.DBPort, req.DBName, req.DBUser, req.DBPassword, req.AdminUser, req.AdminPass, jwtSecret, req.MaxSeats) return envContent, nil } // ShowSetupPage displays the web setup interface func (h *SetupHandler) ShowSetupPage(c *gin.Context) { // Display setup page - configuration will be generated via web interface fmt.Println("Showing setup page - configuration will be generated via web interface") html := ` RedFlag - Server Configuration

🚀 RedFlag Server Setup

Configure your RedFlag deployment

📊 Server Configuration

🗄️ Database Configuration

👤 Administrator Account

🔧 Agent Settings

Maximum number of agents that can register

Configuring your RedFlag server...

` c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html)) } // ConfigureServer handles the configuration submission func (h *SetupHandler) ConfigureServer(c *gin.Context) { var req struct { AdminUser string `json:"adminUser"` AdminPass string `json:"adminPassword"` DBHost string `json:"dbHost"` DBPort string `json:"dbPort"` DBName string `json:"dbName"` DBUser string `json:"dbUser"` DBPassword string `json:"dbPassword"` ServerHost string `json:"serverHost"` ServerPort string `json:"serverPort"` MaxSeats string `json:"maxSeats"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"}) return } // Validate inputs if req.AdminUser == "" || req.AdminPass == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Admin username and password are required"}) return } if req.DBHost == "" || req.DBPort == "" || req.DBName == "" || req.DBUser == "" || req.DBPassword == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "All database fields are required"}) return } // Parse numeric values dbPort, err := strconv.Atoi(req.DBPort) if err != nil || dbPort <= 0 || dbPort > 65535 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid database port"}) return } serverPort, err := strconv.Atoi(req.ServerPort) if err != nil || serverPort <= 0 || serverPort > 65535 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid server port"}) return } maxSeats, err := strconv.Atoi(req.MaxSeats) if err != nil || maxSeats <= 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid maximum agent seats"}) return } // Generate secure JWT secret (not derived from credentials for security) jwtSecret, err := config.GenerateSecureToken() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate JWT secret"}) return } // Step 1: Update PostgreSQL password from bootstrap to user password fmt.Println("Updating PostgreSQL password from bootstrap to user-provided password...") bootstrapPassword := "redflag_bootstrap" // This matches our bootstrap .env if err := updatePostgresPassword(req.DBHost, req.DBPort, req.DBUser, bootstrapPassword, req.DBPassword); err != nil { fmt.Printf("Warning: Failed to update PostgreSQL password: %v\n", err) fmt.Println("Will proceed with configuration anyway...") } // Step 2: Generate configuration content for manual update fmt.Println("Generating configuration content for manual .env file update...") // Generate the complete .env file content for the user to copy newEnvContent, err := createSharedEnvContentForDisplay(req, jwtSecret) if err != nil { fmt.Printf("Failed to generate .env content: %v\n", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate configuration content"}) return } c.JSON(http.StatusOK, gin.H{ "message": "Configuration generated successfully!", "envContent": newEnvContent, "restartMessage": "Please replace the bootstrap environment variables with the newly generated ones, then run: docker-compose down && docker-compose up -d", "manualRestartRequired": true, "manualRestartCommand": "docker-compose down && docker-compose up -d", "configFilePath": "./config/.env", }) }