Files
user-system/internal/security/validator.go

186 lines
5.3 KiB
Go

package security
import (
"net"
"regexp"
"strings"
)
// Validator groups lightweight validation and sanitization helpers.
type Validator struct {
passwordMinLength int
passwordRequireSpecial bool
passwordRequireNumber bool
}
// NewValidator creates a validator with the configured password rules.
func NewValidator(minLength int, requireSpecial, requireNumber bool) *Validator {
return &Validator{
passwordMinLength: minLength,
passwordRequireSpecial: requireSpecial,
passwordRequireNumber: requireNumber,
}
}
// ValidateEmail validates email format.
func (v *Validator) ValidateEmail(email string) bool {
if email == "" {
return false
}
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
matched, _ := regexp.MatchString(pattern, email)
return matched
}
// ValidatePhone validates mainland China mobile numbers.
func (v *Validator) ValidatePhone(phone string) bool {
if phone == "" {
return false
}
pattern := `^1[3-9]\d{9}$`
matched, _ := regexp.MatchString(pattern, phone)
return matched
}
// ValidateUsername validates usernames.
func (v *Validator) ValidateUsername(username string) bool {
if username == "" {
return false
}
pattern := `^[a-zA-Z][a-zA-Z0-9_]{3,19}$`
matched, _ := regexp.MatchString(pattern, username)
return matched
}
// ValidatePassword validates passwords using the shared runtime policy.
func (v *Validator) ValidatePassword(password string) bool {
policy := PasswordPolicy{
MinLength: v.passwordMinLength,
RequireSpecial: v.passwordRequireSpecial,
RequireNumber: v.passwordRequireNumber,
}
return policy.Validate(password) == nil
}
// SanitizeSQL removes obviously dangerous SQL injection patterns using regex.
// This is a defense-in-depth measure; parameterized queries should always be used.
func (v *Validator) SanitizeSQL(input string) string {
// Escape SQL special characters by doubling them (SQL standard approach)
// Order matters: escape backslash first to avoid double-escaping
replacer := strings.NewReplacer(
`\`, `\\`,
`'`, `''`,
`"`, `""`,
)
// Remove common SQL injection patterns that could bypass quoting
dangerousPatterns := []string{
`;[\s]*--`, // SQL comment
`/\*.*?\*/`, // Block comment (non-greedy)
`\bxp_\w+`, // Extended stored procedures
`\bexec[\s\(]`, // EXEC statements
`\bsp_\w+`, // System stored procedures
`\bwaitfor[\s]+delay`, // Time-based blind SQL injection
`\bunion[\s]+select`, // UNION injection
`\bdrop[\s]+table`, // DROP TABLE
`\binsert[\s]+into`, // INSERT
`\bupdate[\s]+\w+[\s]+set`, // UPDATE
`\bdelete[\s]+from`, // DELETE
}
result := replacer.Replace(input)
// Apply pattern removal
for _, pattern := range dangerousPatterns {
re := regexp.MustCompile(`(?i)` + pattern) // Case-insensitive
result = re.ReplaceAllString(result, "")
}
return result
}
// SanitizeXSS removes obviously dangerous XSS patterns using regex.
// This is a defense-in-depth measure; output encoding should always be used.
func (v *Validator) SanitizeXSS(input string) string {
// Remove dangerous tags and attributes using pattern matching
dangerousPatterns := []struct {
pattern string
replaceAll bool
}{
{`(?i)<script[^>]*>.*?</script>`, true}, // Script tags
{`(?i)</script>`, false}, // Closing script
{`(?i)<iframe[^>]*>.*?</iframe>`, true}, // Iframe injection
{`(?i)<object[^>]*>.*?</object>`, true}, // Object injection
{`(?i)<embed[^>]*>.*?</embed>`, true}, // Embed injection
{`(?i)<applet[^>]*>.*?</applet>`, true}, // Applet injection
{`(?i)javascript\s*:`, false}, // JavaScript protocol
{`(?i)vbscript\s*:`, false}, // VBScript protocol
{`(?i)data\s*:`, false}, // Data URL protocol
{`(?i)on\w+\s*=`, false}, // Event handlers
{`(?i)<style[^>]*>.*?</style>`, true}, // Style injection
}
result := input
for _, p := range dangerousPatterns {
re := regexp.MustCompile(p.pattern)
if p.replaceAll {
result = re.ReplaceAllString(result, "")
} else {
result = re.ReplaceAllString(result, "")
}
}
// Encode < and > to prevent tag construction
result = strings.ReplaceAll(result, "<", "&lt;")
result = strings.ReplaceAll(result, ">", "&gt;")
// Restore entities if they were part of legitimate content
result = strings.ReplaceAll(result, "&lt;", "<")
result = strings.ReplaceAll(result, "&gt;", ">")
return result
}
// ValidateURL validates a basic HTTP/HTTPS URL.
func (v *Validator) ValidateURL(url string) bool {
if url == "" {
return false
}
pattern := `^https?://[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=]+$`
matched, _ := regexp.MatchString(pattern, url)
return matched
}
// ValidateIP validates IPv4 or IPv6 addresses using net.ParseIP.
// Supports all valid formats including compressed IPv6 (::1, fe80::1, etc.)
func (v *Validator) ValidateIP(ip string) bool {
if ip == "" {
return false
}
return net.ParseIP(ip) != nil
}
// ValidateIPv4 validates IPv4 addresses only.
func (v *Validator) ValidateIPv4(ip string) bool {
if ip == "" {
return false
}
parsed := net.ParseIP(ip)
return parsed != nil && parsed.To4() != nil
}
// ValidateIPv6 validates IPv6 addresses only.
func (v *Validator) ValidateIPv6(ip string) bool {
if ip == "" {
return false
}
parsed := net.ParseIP(ip)
return parsed != nil && parsed.To4() == nil
}