package services import ( "errors" "gobeyhan/database" "gobeyhan/database/models" "log" "net/url" "strings" "time" "gorm.io/gorm" ) type SettingsService struct { cacheService *CacheService } func NewSettingsService() *SettingsService { return &SettingsService{ cacheService: NewCacheService(), } } // ==================== CORS WHITELIST ==================== func (s *SettingsService) GetAllCorsWhitelist() ([]models.CorsWhitelist, error) { var whitelists []models.CorsWhitelist err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&whitelists).Error return whitelists, err } func (s *SettingsService) GetActiveWhitelistOrigins() ([]string, error) { // Try cache first cached, err := s.cacheService.GetCorsWhitelist() if err == nil && cached != nil { return cached, nil } origins, err := s.getActiveWhitelistOriginsFromDB() if err != nil { return nil, err } // Cache for 1 hour s.cacheService.SetCorsWhitelist(origins, 1*time.Hour) return origins, nil } var ErrCorsOriginExists = errors.New("cors origin already exists") func (s *SettingsService) CreateCorsWhitelist(whitelist *models.CorsWhitelist) error { var existing models.CorsWhitelist err := database.DB.Where("LOWER(origin) = LOWER(?)", whitelist.Origin).First(&existing).Error if err == nil { if existing.IsActive { return ErrCorsOriginExists } updates := map[string]interface{}{ "is_active": true, "description": whitelist.Description, "created_by": whitelist.CreatedBy, } err = database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", existing.ID).Updates(updates).Error if err != nil { return err } s.InvalidateCorsCache() return nil } if !errors.Is(err, gorm.ErrRecordNotFound) { return err } err = database.DB.Create(whitelist).Error if err != nil { return err } // Invalidate cache s.InvalidateCorsCache() return nil } func (s *SettingsService) UpdateCorsWhitelist(id string, updates map[string]interface{}) error { err := database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", id).Updates(updates).Error if err != nil { return err } // Invalidate cache s.InvalidateCorsCache() return nil } func (s *SettingsService) DeleteCorsWhitelist(id string) error { err := database.DB.Delete(&models.CorsWhitelist{}, "id = ?", id).Error if err != nil { return err } // Invalidate cache s.InvalidateCorsCache() return nil } func (s *SettingsService) GetCorsWhitelistByID(id uint64) (*models.CorsWhitelist, error) { var item models.CorsWhitelist err := database.DB.First(&item, id).Error if err != nil { return nil, err } return &item, nil } // ==================== CORS BLACKLIST ==================== func (s *SettingsService) GetAllCorsBlacklist() ([]models.CorsBlacklist, error) { var blacklists []models.CorsBlacklist err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&blacklists).Error return blacklists, err } func (s *SettingsService) GetActiveBlacklistOrigins() ([]string, error) { // Try cache first cached, err := s.cacheService.GetCorsBlacklist() if err == nil && cached != nil { return cached, nil } origins, err := s.getActiveBlacklistOriginsFromDB() if err != nil { return nil, err } // Cache for 1 hour s.cacheService.SetCorsBlacklist(origins, 1*time.Hour) return origins, nil } func (s *SettingsService) CreateCorsBlacklist(blacklist *models.CorsBlacklist) error { err := database.DB.Create(blacklist).Error if err != nil { return err } // Invalidate cache s.InvalidateCorsCache() return nil } func (s *SettingsService) UpdateCorsBlacklist(id string, updates map[string]interface{}) error { err := database.DB.Model(&models.CorsBlacklist{}).Where("id = ?", id).Updates(updates).Error if err != nil { return err } // Invalidate cache s.InvalidateCorsCache() return nil } func (s *SettingsService) DeleteCorsBlacklist(id string) error { err := database.DB.Delete(&models.CorsBlacklist{}, "id = ?", id).Error if err != nil { return err } // Invalidate cache s.InvalidateCorsCache() return nil } func (s *SettingsService) GetCorsBlacklistByID(id uint64) (*models.CorsBlacklist, error) { var item models.CorsBlacklist err := database.DB.First(&item, id).Error if err != nil { return nil, err } return &item, nil } // ==================== RATE LIMIT SETTINGS ==================== func (s *SettingsService) GetAllRateLimitSettings() ([]models.RateLimitSetting, error) { var settings []models.RateLimitSetting err := database.DB.Order("name ASC").Find(&settings).Error return settings, err } func (s *SettingsService) GetRateLimitSettingsMap() (map[string]*models.RateLimitSetting, error) { // Try cache first cached, err := s.cacheService.GetRateLimitSettings() if err == nil && cached != nil { return cached, nil } // Fetch from database var settings []models.RateLimitSetting err = database.DB.Where("is_active = ?", true).Find(&settings).Error if err != nil { return nil, err } settingsMap := make(map[string]*models.RateLimitSetting) for i := range settings { settingsMap[settings[i].Name] = &settings[i] } // Cache for 1 hour s.cacheService.SetRateLimitSettings(settingsMap, 1*time.Hour) return settingsMap, nil } func (s *SettingsService) GetRateLimitSettingByName(name string) (*models.RateLimitSetting, error) { settingsMap, err := s.GetRateLimitSettingsMap() if err != nil { return nil, err } setting, exists := settingsMap[name] if !exists { return nil, nil } return setting, nil } func (s *SettingsService) UpdateRateLimitSetting(id string, updates map[string]interface{}) error { err := database.DB.Model(&models.RateLimitSetting{}).Where("id = ?", id).Updates(updates).Error if err != nil { return err } // Invalidate cache s.cacheService.InvalidateRateLimitSettings() return nil } func (s *SettingsService) GetRateLimitSettingByID(id uint64) (*models.RateLimitSetting, error) { var item models.RateLimitSetting err := database.DB.First(&item, id).Error if err != nil { return nil, err } return &item, nil } func (s *SettingsService) DeleteRateLimitSetting(id string) error { err := database.DB.Delete(&models.RateLimitSetting{}, "id = ?", id).Error if err != nil { return err } // Invalidate cache s.cacheService.InvalidateRateLimitSettings() return nil } // Invalidate CORS caches (whitelist + blacklist) func (s *SettingsService) InvalidateCorsCache() { s.cacheService.InvalidateCorsWhitelist() s.cacheService.InvalidateCorsBlacklist() log.Println("cors_cache_invalidated") } // Check if origin is allowed func (s *SettingsService) IsOriginAllowed(origin string) (bool, error) { allowed, _, _, err := s.CheckOrigin(origin) return allowed, err } // CheckOrigin returns decision details for debug logging. func (s *SettingsService) CheckOrigin(origin string) (bool, string, string, error) { // Check blacklist first blacklist, err := s.GetActiveBlacklistOrigins() if err != nil { return false, "", "", err } for _, blocked := range blacklist { if originMatchesEntry(origin, blocked) { return false, blocked, "blacklist", nil } } // Fallback: refresh blacklist on miss (stale cache protection) freshBlacklist, err := s.getActiveBlacklistOriginsFromDB() if err != nil { return false, "", "", err } if len(freshBlacklist) != 0 { s.cacheService.SetCorsBlacklist(freshBlacklist, 1*time.Hour) } for _, blocked := range freshBlacklist { if originMatchesEntry(origin, blocked) { return false, blocked, "blacklist", nil } } // Check whitelist whitelist, err := s.GetActiveWhitelistOrigins() if err != nil { return false, "", "", err } for _, allowed := range whitelist { if allowed == "*" || originMatchesEntry(origin, allowed) { return true, allowed, "whitelist", nil } } // Fallback: refresh whitelist on miss (stale cache protection) freshWhitelist, err := s.getActiveWhitelistOriginsFromDB() if err != nil { return false, "", "", err } if len(freshWhitelist) != 0 { s.cacheService.SetCorsWhitelist(freshWhitelist, 1*time.Hour) } for _, allowed := range freshWhitelist { if allowed == "*" || originMatchesEntry(origin, allowed) { return true, allowed, "whitelist", nil } } return false, "", "whitelist", nil } func (s *SettingsService) getActiveWhitelistOriginsFromDB() ([]string, error) { var whitelists []models.CorsWhitelist err := database.DB.Where("is_active = ?", true).Find(&whitelists).Error if err != nil { return nil, err } origins := make([]string, len(whitelists)) for i, w := range whitelists { origins[i] = w.Origin } return origins, nil } func (s *SettingsService) getActiveBlacklistOriginsFromDB() ([]string, error) { var blacklists []models.CorsBlacklist err := database.DB.Where("is_active = ?", true).Find(&blacklists).Error if err != nil { return nil, err } origins := make([]string, len(blacklists)) for i, b := range blacklists { origins[i] = b.Origin } return origins, nil } func originMatchesEntry(origin string, entry string) bool { origin = strings.TrimSpace(origin) entry = strings.TrimSpace(entry) if origin == "" || entry == "" { return false } originLower := strings.ToLower(origin) entryLower := strings.ToLower(entry) if strings.Contains(entryLower, "://") { return originLower == entryLower } parsed, err := url.Parse(originLower) if err != nil || parsed.Host == "" { return false } hostLower := strings.ToLower(parsed.Host) if entryLower == hostLower { return true } // Allow entries like "127.0.0.1" to match any port hostOnly := strings.Split(hostLower, ":")[0] return entryLower == hostOnly }