package company import ( "fmt" "strings" ) // Well-known tenant company_settings.settings JSON keys (migrator domain fields). // Do not invent preference keys here — extend only when a real product key exists. const ( SettingsKeyLanguage = "language" SettingsKeyMergeProducts = "merge_products" ) // AllowedSettingsKeys is the allowlist for PUT /api/company/settings mass-assignment guard. func AllowedSettingsKeys() map[string]struct{} { return map[string]struct{}{ SettingsKeyLanguage: {}, SettingsKeyMergeProducts: {}, } } func isAllowedSettingsKey(key string) bool { _, ok := AllowedSettingsKeys()[key] return ok } // ValidateSettingsMap rejects unknown keys and mistyped values for the settings bag. // nil / empty maps are valid (clear or no-op payload). func ValidateSettingsMap(settings map[string]any) error { if len(settings) == 0 { return nil } for k, v := range settings { if strings.TrimSpace(k) == "" || strings.ContainsAny(k, " \t\n\r") || k != strings.TrimSpace(k) { return fmt.Errorf("unknown settings key") } if !isAllowedSettingsKey(k) { return fmt.Errorf("unknown settings key") } switch k { case SettingsKeyLanguage: raw, ok := v.(string) if !ok { return fmt.Errorf("invalid language") } if _, err := ParseLanguage(raw, false); err != nil { return fmt.Errorf("unsupported language") } case SettingsKeyMergeProducts: if _, ok := v.(bool); !ok { return fmt.Errorf("invalid merge_products") } } } return nil }