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
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",
})
}