package api import ( "regexp" "strings" "github.com/go-playground/validator/v10" ) // Validate is a global validator instance var Validate = validator.New() // RegisterCustomValidations registers custom validation functions func RegisterCustomValidations() { // Register custom validation for issuer Validate.RegisterValidation("issuer", validateIssuer) // Register custom validation for XSS prevention Validate.RegisterValidation("no_xss", validateNoXSS) // Register custom validation for OTP secret Validate.RegisterValidation("otpsecret", validateOTPSecret) } // validateOTPSecret validates that the OTP secret is in valid base32 format func validateOTPSecret(fl validator.FieldLevel) bool { secret := fl.Field().String() // Check if the secret is not empty if secret == "" { return false } // Check if the secret is in base32 format (A-Z, 2-7) base32Regex := regexp.MustCompile(`^[A-Z2-7]+=*$`) if !base32Regex.MatchString(secret) { return false } // Check if the length is valid (must be at least 16 characters) if len(secret) < 16 || len(secret) > 128 { return false } return true } // validateIssuer validates that the issuer field contains only allowed characters func validateIssuer(fl validator.FieldLevel) bool { issuer := fl.Field().String() // Empty issuer is valid (since it's optional) if issuer == "" { return true } // Allow alphanumeric characters, spaces, and common punctuation issuerRegex := regexp.MustCompile(`^[a-zA-Z0-9\s\-_.,:;!?()[\]{}'"]+package api import ( "regexp" "strings" "github.com/go-playground/validator/v10" ) // Validate is a global validator instance var Validate = validator.New() // RegisterCustomValidations registers custom validation functions func RegisterCustomValidations() { // Register custom validation for issuer Validate.RegisterValidation("issuer", validateIssuer) // Register custom validation for XSS prevention Validate.RegisterValidation("no_xss", validateNoXSS) // Register custom validation for OTP secret Validate.RegisterValidation("otpsecret", validateOTPSecret) } // validateOTPSecret validates that the OTP secret is in valid base32 format func validateOTPSecret(fl validator.FieldLevel) bool { secret := fl.Field().String() // Check if the secret is not empty if secret == "" { return false } // Check if the secret is in base32 format (A-Z, 2-7) base32Regex := regexp.MustCompile(`^[A-Z2-7]+=*$`) if !base32Regex.MatchString(secret) { return false } // Check if the length is valid (must be at least 16 characters) if len(secret) < 16 || len(secret) > 128 { return false } return true } ) if !issuerRegex.MatchString(issuer) { return false } // Check length if len(issuer) > 100 { return false } return true } // validateNoXSS validates that the field doesn't contain potential XSS payloads func validateNoXSS(fl validator.FieldLevel) bool { value := fl.Field().String() // Check for HTML encoding if strings.Contains(value, "&#") || strings.Contains(value, "<") || strings.Contains(value, ">") { return false } // Check for common XSS patterns suspiciousPatterns := []*regexp.Regexp{ regexp.MustCompile(`(?i)]*>.*?`), regexp.MustCompile(`(?i)javascript:`), regexp.MustCompile(`(?i)data:text/html`), regexp.MustCompile(`(?i)on\w+\s*=`), regexp.MustCompile(`(?i)<\s*img[^>]*src\s*=`), regexp.MustCompile(`(?i)<\s*iframe`), regexp.MustCompile(`(?i)<\s*object`), regexp.MustCompile(`(?i)<\s*embed`), regexp.MustCompile(`(?i)<\s*style`), regexp.MustCompile(`(?i)<\s*form`), regexp.MustCompile(`(?i)<\s*applet`), regexp.MustCompile(`(?i)<\s*meta`), } for _, pattern := range suspiciousPatterns { if pattern.MatchString(value) { return false } } return true }