first commit
This commit is contained in:
391
app/settings/services/settings_service.go
Normal file
391
app/settings/services/settings_service.go
Normal file
@@ -0,0 +1,391 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user