feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
package security
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"net"
|
|
|
|
|
|
"regexp"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Validator groups lightweight validation and sanitization helpers.
|
|
|
|
|
|
type Validator struct {
|
|
|
|
|
|
passwordMinLength int
|
|
|
|
|
|
passwordRequireSpecial bool
|
|
|
|
|
|
passwordRequireNumber bool
|
2026-05-08 08:05:26 +08:00
|
|
|
|
// 预编译的正则表达式,避免每次调用重复编译(P1性能优化)
|
|
|
|
|
|
emailRe *regexp.Regexp
|
|
|
|
|
|
phoneRe *regexp.Regexp
|
|
|
|
|
|
usernameRe *regexp.Regexp
|
|
|
|
|
|
urlRe *regexp.Regexp
|
|
|
|
|
|
sqlPatterns []*regexp.Regexp
|
|
|
|
|
|
xssPatterns []*regexp.Regexp
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewValidator creates a validator with the configured password rules.
|
|
|
|
|
|
func NewValidator(minLength int, requireSpecial, requireNumber bool) *Validator {
|
2026-05-08 08:05:26 +08:00
|
|
|
|
v := &Validator{
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
passwordMinLength: minLength,
|
|
|
|
|
|
passwordRequireSpecial: requireSpecial,
|
|
|
|
|
|
passwordRequireNumber: requireNumber,
|
|
|
|
|
|
}
|
2026-05-08 08:05:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 预编译常用验证正则(P1性能优化)
|
|
|
|
|
|
v.emailRe = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
|
|
|
|
|
|
v.phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`)
|
|
|
|
|
|
v.usernameRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{3,19}$`)
|
|
|
|
|
|
v.urlRe = regexp.MustCompile(`^https?://[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=]+$`)
|
|
|
|
|
|
|
|
|
|
|
|
// 预编译SQL注入检测正则(P1性能优化)
|
|
|
|
|
|
sqlRawPatterns := []string{
|
|
|
|
|
|
`;[\s]*--`,
|
|
|
|
|
|
`/\*.*?\*/`,
|
|
|
|
|
|
`\bxp_\w+`,
|
|
|
|
|
|
`\bexec[\s\(]`,
|
|
|
|
|
|
`\bsp_\w+`,
|
|
|
|
|
|
`\bwaitfor[\s]+delay`,
|
|
|
|
|
|
`\bunion[\s]+select`,
|
|
|
|
|
|
`\bdrop[\s]+table`,
|
|
|
|
|
|
`\binsert[\s]+into`,
|
|
|
|
|
|
`\bupdate[\s]+\w+[\s]+set`,
|
|
|
|
|
|
`\bdelete[\s]+from`,
|
|
|
|
|
|
}
|
|
|
|
|
|
v.sqlPatterns = make([]*regexp.Regexp, len(sqlRawPatterns))
|
|
|
|
|
|
for i, p := range sqlRawPatterns {
|
|
|
|
|
|
v.sqlPatterns[i] = regexp.MustCompile(`(?i)` + p)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 预编译XSS检测正则(P1性能优化)
|
|
|
|
|
|
xssRawPatterns := []string{
|
|
|
|
|
|
`(?i)<script[^>]*>.*?</script>`,
|
|
|
|
|
|
`(?i)</script>`,
|
|
|
|
|
|
`(?i)<iframe[^>]*>.*?</iframe>`,
|
|
|
|
|
|
`(?i)<object[^>]*>.*?</object>`,
|
|
|
|
|
|
`(?i)<embed[^>]*>.*?</embed>`,
|
|
|
|
|
|
`(?i)<applet[^>]*>.*?</applet>`,
|
|
|
|
|
|
`(?i)javascript\s*:`,
|
|
|
|
|
|
`(?i)vbscript\s*:`,
|
|
|
|
|
|
`(?i)data\s*:`,
|
|
|
|
|
|
`(?i)on\w+\s*=`,
|
|
|
|
|
|
`(?i)<style[^>]*>.*?</style>`,
|
|
|
|
|
|
}
|
|
|
|
|
|
v.xssPatterns = make([]*regexp.Regexp, len(xssRawPatterns))
|
|
|
|
|
|
for i, p := range xssRawPatterns {
|
|
|
|
|
|
v.xssPatterns[i] = regexp.MustCompile(p)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return v
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateEmail validates email format.
|
|
|
|
|
|
func (v *Validator) ValidateEmail(email string) bool {
|
2026-05-08 08:05:26 +08:00
|
|
|
|
if email == "" || v.emailRe == nil {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-05-08 08:05:26 +08:00
|
|
|
|
return v.emailRe.MatchString(email)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidatePhone validates mainland China mobile numbers.
|
|
|
|
|
|
func (v *Validator) ValidatePhone(phone string) bool {
|
2026-05-08 08:05:26 +08:00
|
|
|
|
if phone == "" || v.phoneRe == nil {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-05-08 08:05:26 +08:00
|
|
|
|
return v.phoneRe.MatchString(phone)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateUsername validates usernames.
|
|
|
|
|
|
func (v *Validator) ValidateUsername(username string) bool {
|
2026-05-08 08:05:26 +08:00
|
|
|
|
if username == "" || v.usernameRe == nil {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-05-08 08:05:26 +08:00
|
|
|
|
return v.usernameRe.MatchString(username)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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(
|
|
|
|
|
|
`\`, `\\`,
|
|
|
|
|
|
`'`, `''`,
|
|
|
|
|
|
`"`, `""`,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
result := replacer.Replace(input)
|
|
|
|
|
|
|
2026-05-08 08:05:26 +08:00
|
|
|
|
// 使用预编译的正则移除SQL注入模式(P1性能优化)
|
|
|
|
|
|
for _, re := range v.sqlPatterns {
|
|
|
|
|
|
if re != nil {
|
|
|
|
|
|
result = re.ReplaceAllString(result, "")
|
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
result := input
|
|
|
|
|
|
|
2026-05-08 08:05:26 +08:00
|
|
|
|
// 使用预编译的正则移除XSS模式(P1性能优化)
|
|
|
|
|
|
for _, re := range v.xssPatterns {
|
|
|
|
|
|
if re != nil {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
result = re.ReplaceAllString(result, "")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Encode < and > to prevent tag construction
|
|
|
|
|
|
result = strings.ReplaceAll(result, "<", "<")
|
|
|
|
|
|
result = strings.ReplaceAll(result, ">", ">")
|
|
|
|
|
|
|
|
|
|
|
|
// Restore entities if they were part of legitimate content
|
|
|
|
|
|
result = strings.ReplaceAll(result, "<", "<")
|
|
|
|
|
|
result = strings.ReplaceAll(result, ">", ">")
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateURL validates a basic HTTP/HTTPS URL.
|
|
|
|
|
|
func (v *Validator) ValidateURL(url string) bool {
|
2026-05-08 08:05:26 +08:00
|
|
|
|
if url == "" || v.urlRe == nil {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-05-08 08:05:26 +08:00
|
|
|
|
return v.urlRe.MatchString(url)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|