// Copyright (c) 2025 Nikolai Papin // // This file is part of Easywish // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See // the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package validation import ( "easywish/config" "fmt" "regexp" "time" "github.com/go-playground/validator/v10" ) type CustomValidatorHandler struct { Function func(fl validator.FieldLevel) bool FieldName string } func GetCustomHandlers() []CustomValidatorHandler { handlers := []CustomValidatorHandler{ { FieldName: "username", Function: func(fl validator.FieldLevel) bool { username := fl.Field().String() return regexp.MustCompile(`^[a-zA-Z0-9_]{3,20}$`).MatchString(username) }}, { FieldName: "guid", Function: func(fl validator.FieldLevel) bool { guid := fl.Field().String() return regexp.MustCompile(`^([{(]?([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})[})]?)$`).MatchString(guid) }}, { FieldName: "name", Function: func(fl validator.FieldLevel) bool { username := fl.Field().String() return regexp.MustCompile(`^.{1,75}$`).MatchString(username) }}, { FieldName: "bio", Function: func(fl validator.FieldLevel) bool { username := fl.Field().String() return regexp.MustCompile(`^.{1,512}$`).MatchString(username) }}, { FieldName: "birthday_unix_milli", Function: func(fl validator.FieldLevel) bool { timestamp := fl.Field().Int() date := time.UnixMilli(timestamp) currentDate := time.Now() age := currentDate.Year() - date.Year() if currentDate.YearDay() < date.YearDay() { age-- } return age >= 0 && age <= 122 }}, { FieldName: "color_hex", Function: func(fl validator.FieldLevel) bool { username := fl.Field().String() return regexp.MustCompile(`^#[0-9a-f]{6,6}$`).MatchString(username) }}, { FieldName: "password", Function: func(fl validator.FieldLevel) bool { password := fl.Field().String() cfg := config.GetConfig() if cfg.PasswordMaxLength < len(password) || len(password) < cfg.PasswordMinLength { return false } if cfg.PasswordCheckNumbers && !regexp.MustCompile(`\d+`).MatchString(password) { return false } if cfg.PasswordCheckCases && !regexp.MustCompile(`^(?=.*[a-z])(?=.*[A-Z]).*$`).MatchString(password) || cfg.PasswordCheckCharacters && !regexp.MustCompile(`[a-zA-Z]`).MatchString(password) { return false } if cfg.PasswordCheckSymbols && !regexp.MustCompile(`[.,/;'[\]\-=_+{}:"<>?\/|!@#$%^&*()~]`).MatchString(password) { return false } if cfg.PasswordCheckLeaked { // TODO: implement rockme check } return true }}, { FieldName: "verification_code", Function: func(fl validator.FieldLevel) bool { codeType := fl.Param() code := fl.Field().String() if codeType == "reg" { return regexp.MustCompile(`[\d]{6,6}`).MatchString(code) } if codeType == "reset" { return regexp.MustCompile( `^[{(]?([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})[})]?$`, ).MatchString(code) } panic(fmt.Sprintf("'%s' is not a valid verification code type", codeType)) }}, { FieldName: "upload_id", Function: func(fl validator.FieldLevel) bool { uploadType := fl.Param() uploadID := fl.Field().String() pattern := fmt.Sprintf( "^%s-([{(]?([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})[})]?)$", uploadType, ) return regexp.MustCompile(pattern).MatchString(uploadID) }}, } return handlers }