package handlers
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/Fimeg/RedFlag/aggregator-server/internal/config"
"github.com/gin-gonic/gin"
)
// SetupHandler handles server configuration
type SetupHandler struct {
configPath string
}
func NewSetupHandler(configPath string) *SetupHandler {
return &SetupHandler{
configPath: configPath,
}
}
// ShowSetupPage displays the web setup interface
func (h *SetupHandler) ShowSetupPage(c *gin.Context) {
html := `
RedFlag - Server Configuration
`
c.Data(200, "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
}
// Create configuration content
envContent := fmt.Sprintf(`# RedFlag Server Configuration
# Generated by web setup
# Server Configuration
REDFLAG_SERVER_HOST=%s
REDFLAG_SERVER_PORT=%d
REDFLAG_TLS_ENABLED=false
# REDFLAG_TLS_CERT_FILE=
# REDFLAG_TLS_KEY_FILE=
# Database Configuration
REDFLAG_DB_HOST=%s
REDFLAG_DB_PORT=%d
REDFLAG_DB_NAME=%s
REDFLAG_DB_USER=%s
REDFLAG_DB_PASSWORD=%s
# Admin Configuration
REDFLAG_ADMIN_USER=%s
REDFLAG_ADMIN_PASSWORD=%s
REDFLAG_JWT_SECRET=%s
# Agent Registration
REDFLAG_TOKEN_EXPIRY=24h
REDFLAG_MAX_TOKENS=100
REDFLAG_MAX_SEATS=%d
# Legacy Configuration (for backwards compatibility)
SERVER_PORT=%d
DATABASE_URL=postgres://%s:%s@%s:%d/%s?sslmode=disable
JWT_SECRET=%s
CHECK_IN_INTERVAL=300
OFFLINE_THRESHOLD=600
TIMEZONE=UTC
LATEST_AGENT_VERSION=0.1.16`,
req.ServerHost, serverPort,
req.DBHost, dbPort, req.DBName, req.DBUser, req.DBPassword,
req.AdminUser, req.AdminPass, deriveJWTSecret(req.AdminUser, req.AdminPass),
maxSeats,
serverPort, req.DBUser, req.DBPassword, req.DBHost, dbPort, req.DBName, deriveJWTSecret(req.AdminUser, req.AdminPass))
// Write configuration to persistent location
configDir := "/app/config"
if err := os.MkdirAll(configDir, 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create config directory"})
return
}
envPath := filepath.Join(configDir, ".env")
if err := os.WriteFile(envPath, []byte(envContent), 0600); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Configuration saved successfully! Server will restart automatically.",
"configPath": envPath,
})
}
// deriveJWTSecret generates a JWT secret from admin credentials
func deriveJWTSecret(username, password string) string {
hash := sha256.Sum256([]byte(username + password + "redflag-jwt-2024"))
return hex.EncodeToString(hash[:])
}