Ctrl K
ComunidadGuías…

server/server.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package server

import (
	"context"
	db "faynoSync/mongod"
	"faynoSync/redisdb"
	"faynoSync/server/dashboard"
	"faynoSync/server/handler"
	"faynoSync/server/tuf"
	tuf_metadata "faynoSync/server/tuf/metadata"
	"faynoSync/server/utils"
	"fmt"
	"os"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"github.com/spf13/viper"
)

const (
	insecureDevSecretPrefix = "insecure-dev-"
	minJWTSecretBytes       = 32
)

func validateSecrets(config *viper.Viper, releaseMode bool) error {
	if len(config.GetString("JWT_SECRET")) < minJWTSecretBytes {
		return fmt.Errorf("JWT_SECRET is empty or shorter than %d bytes; generate one with: openssl rand -base64 32", minJWTSecretBytes)
	}

	devSecrets := []struct {
		key  string
		risk string
	}{
		{"JWT_SECRET", "forge admin tokens"},
		{"API_KEY", "create admin accounts through /signup"},
	}
	for _, s := range devSecrets {
		if !strings.HasPrefix(config.GetString(s.key), insecureDevSecretPrefix) {
			continue
		}
		if releaseMode {
			return fmt.Errorf("%s holds the public development value from the repository; generate one with: openssl rand -base64 32", s.key)
		}
		logrus.Warnf("%s holds the public development value from the repository: anyone can %s. Do not expose this instance", s.key, s.risk)
	}
	return nil
}

func StartServer(config *viper.Viper) {
	if err := validateSecrets(config, gin.Mode() == gin.ReleaseMode); err != nil {
		logrus.Fatal(err)
	}

	mongoUrl := config.GetString("MONGODB_URL")

	router := gin.Default()
	if trustedProxies := config.GetString("TRUSTED_PROXIES"); trustedProxies != "" {
		proxies := strings.Split(trustedProxies, ",")
		for i := range proxies {
			proxies[i] = strings.TrimSpace(proxies[i])
		}
		if err := router.SetTrustedProxies(proxies); err != nil {
			logrus.Fatalf("Invalid TRUSTED_PROXIES: %v", err)
		}
	}

	client, configDB := db.ConnectToDatabase(mongoUrl)

	db := db.NewAppRepository(&configDB, client)

	mongoDatabase := client.Database(configDB.Database)

	logrus.Infoln("Redis connection is required. Connecting to Redis.")
	redisConfig := redisdb.RedisConfig{
		Addr:     config.GetString("REDIS_HOST") + ":" + config.GetString("REDIS_PORT"),
		Password: config.GetString("REDIS_PASSWORD"),
		DB:       config.GetInt("REDIS_DB"),
	}
	redisClient := redisdb.ConnectToRedis(redisConfig)

	handler := handler.NewAppHandler(client, db, mongoDatabase, redisClient, config.GetBool("PERFORMANCE_MODE"))
	os.Setenv("API_KEY", config.GetString("API_KEY"))
	// Add authentication middleware to required paths
	authMiddleware := utils.AuthMiddleware(mongoDatabase)

	router.GET("/health", handler.HealthCheck)

	config.SetDefault("DASHBOARD_ENABLED", true)
	if config.GetBool("DASHBOARD_ENABLED") {
		dashboard.Register(router, dashboard.Config{TUFMetadataURL: config.GetString("DASHBOARD_TUF_METADATA_URL")})
	} else {
		logrus.Infoln("DASHBOARD_ENABLED is false, the dashboard is not served")
	}

	allowedCORS := config.GetString("ALLOWED_CORS")
	allowedOrigins := strings.Split(allowedCORS, ",")

	router.Use(corsMiddleware(allowedOrigins))

	// Add squirrel_windows updater compatibility
	router.GET("/update/:owner/:app/:channel/:platform/:arch/:version/RELEASES", handler.SquirrelReleases)

	// Velopack dynamic feed
	router.GET("/velopack/:owner/:app/:platform/:arch/*feed", handler.FindVelopackFeed)

	router.GET("/checkVersion", handler.FindLatestVersion)
	router.GET("/apps/latest", handler.FetchLatestVersionOfApp)
	router.GET("/telemetry/beacon", telemetryMiddleware(config), handler.TelemetryBeacon)
	if config.GetString("API_KEY") != "" {
		router.POST("/signup", handler.SignUp)
	} else {
		logrus.Warnln("API_KEY is not set, /signup is disabled")
	}
	router.POST("/login", handler.Login)

	if config.GetBool("REPORTS_ENABLED") {
		router.POST("/reports/ingest", handler.IngestReport)
	}

	// Access to private artifacts is decided per app inside the handler, so the route stays outside authMiddleware.
	router.GET("/download", handler.DownloadArtifact)
	router.Use(authMiddleware)

	// App routes
	// router.GET("/", handler.GetAllApps)
	router.GET("/whoami", handler.Whoami)
	router.POST("/upload", utils.CheckPermission(utils.PermissionUpload, utils.ResourceApps, mongoDatabase), handler.UploadApp)
	router.POST("/upload/init", utils.CheckPermission(utils.PermissionUpload, utils.ResourceApps, mongoDatabase), handler.InitPresignedUpload)
	router.POST("/upload/complete", utils.CheckPermission(utils.PermissionUpload, utils.ResourceApps, mongoDatabase), handler.CompletePresignedUpload)
	router.POST("/apps/update", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.UpdateSpecificApp)
	router.POST("/app/update", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.UpdateApp)
	router.DELETE("/apps/delete", utils.CheckPermission(utils.PermissionDelete, utils.ResourceApps, mongoDatabase), handler.DeleteSpecificVersionOfApp)
	router.GET("/search", handler.GetAppByName)

	// Channel routes
	router.POST("/channel/create", utils.CheckPermission(utils.PermissionCreate, utils.ResourceChannels, mongoDatabase), handler.CreateChannel)
	router.GET("/channel/list", handler.ListChannels)
	router.DELETE("/channel/delete", utils.CheckPermission(utils.PermissionDelete, utils.ResourceChannels, mongoDatabase), handler.DeleteChannel)
	router.POST("/channel/update", utils.CheckPermission(utils.PermissionEdit, utils.ResourceChannels, mongoDatabase), handler.UpdateChannel)

	// Platform routes
	router.POST("/platform/create", utils.CheckPermission(utils.PermissionCreate, utils.ResourcePlatforms, mongoDatabase), handler.CreatePlatform)
	router.GET("/platform/list", handler.ListPlatforms)
	router.DELETE("/platform/delete", utils.CheckPermission(utils.PermissionDelete, utils.ResourcePlatforms, mongoDatabase), handler.DeletePlatform)
	router.POST("/platform/update", utils.CheckPermission(utils.PermissionEdit, utils.ResourcePlatforms, mongoDatabase), handler.UpdatePlatform)

	// Arch routes
	router.POST("/arch/create", utils.CheckPermission(utils.PermissionCreate, utils.ResourceArchs, mongoDatabase), handler.CreateArch)
	router.GET("/arch/list", handler.ListArchs)
	router.DELETE("/arch/delete", utils.CheckPermission(utils.PermissionDelete, utils.ResourceArchs, mongoDatabase), handler.DeleteArch)
	router.POST("/arch/update", utils.CheckPermission(utils.PermissionEdit, utils.ResourceArchs, mongoDatabase), handler.UpdateArch)

	// App management routes
	router.POST("/app/create", utils.CheckPermission(utils.PermissionCreate, utils.ResourceApps, mongoDatabase), handler.CreateApp)
	router.GET("/app/list", handler.ListApps)
	router.DELETE("/app/delete", utils.CheckPermission(utils.PermissionDelete, utils.ResourceApps, mongoDatabase), handler.DeleteApp)
	router.POST("/artifact/delete", utils.CheckPermission(utils.PermissionDelete, utils.ResourceApps, mongoDatabase), handler.DeleteSpecificArtifactOfApp)

	// Team user management - only admins can create team users
	router.POST("/user/create", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.CreateTeamUser)
	router.POST("/user/update", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.UpdateTeamUser)
	router.GET("/users/list", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.ListTeamUsers)
	router.DELETE("/user/delete", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.DeleteTeamUser)
	router.POST("/admin/update", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.UpdateAdmin)

	// Telemetry endpoint
	router.GET("/telemetry", authMiddleware, telemetryMiddleware(config), handler.GetTelemetry)

	// Token routes
	router.POST("/token/create", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.CreateToken)
	router.GET("/token/list", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.ListTokens)
	router.DELETE("/token/delete", authMiddleware, utils.AdminOnlyMiddleware(mongoDatabase), handler.DeleteToken)

	// Reports routes
	router.GET("/report-keys/list", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.ListReportKeys)
	router.POST("/report-keys/regenerate", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.RegenerateReportKey)

	// Download token routes
	router.GET("/download-tokens/list", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.ListDownloadTokens)
	router.POST("/download-tokens/regenerate", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.RegenerateDownloadToken)

	// Reports read API (admin + team users scoped to their allowed apps)
	if config.GetBool("REPORTS_ENABLED") {
		router.GET("/reports/groups", utils.CheckPermission(utils.PermissionDownload, utils.ResourceApps, mongoDatabase), handler.ListReportGroups)
		router.GET("/reports/groups/:groupHash/blobs", utils.CheckPermission(utils.PermissionDownload, utils.ResourceApps, mongoDatabase), handler.ListReportGroupBlobs)
		router.PATCH("/reports/groups/:groupHash", utils.CheckPermission(utils.PermissionEdit, utils.ResourceApps, mongoDatabase), handler.UpdateReportGroup)
		router.DELETE("/reports/groups/:groupHash", utils.CheckPermission(utils.PermissionDelete, utils.ResourceApps, mongoDatabase), handler.DeleteReportGroup)
	}

	// TUF routes
	if config.GetBool("TUF_ENABLED") {
		tuf.SetupRoutes(router, authMiddleware, mongoDatabase, redisClient, db)
		tuf_metadata.StartOnlineRolesBumper(context.Background(), mongoDatabase, redisClient)
	}

	// get the port from the configuration file
	port := config.GetString("PORT")
	if port == "" {
		port = "9000"
	}
	router.Run(":" + port)
}

func corsMiddleware(allowedOrigins []string) gin.HandlerFunc {
	return func(c *gin.Context) {
		origin := c.Request.Header.Get("Origin")

		allowed := false
		for _, allowedOrigin := range allowedOrigins {
			if allowedOrigin == origin {
				allowed = true
				break
			}
		}

		if allowed {
			c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
			c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
			c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Download-Token, accept, origin, Cache-Control, X-Requested-With")
			c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
		}

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}

		c.Next()
	}
}

func telemetryMiddleware(config *viper.Viper) gin.HandlerFunc {
	return func(c *gin.Context) {
		if !config.GetBool("ENABLE_TELEMETRY") {
			c.JSON(403, gin.H{
				"error": "Telemetry is not enabled on this instance",
			})
			c.Abort()
			return
		}
		c.Next()
	}
}