Files
chiyiandClaude Opus 4.8 606caaf047 Initial open-source release (MIT): image2api AI gateway
Full Go backend + Vue 3 frontend, OpenAI-compatible API, multi-provider
account pools, billing/admin, Docker one-command deploy with auto HTTPS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:59:04 +08:00

162 lines
4.1 KiB
Go

package service
import (
"errors"
"net/mail"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
const (
MinUsernameLength = 6
MaxUsernameLength = 24
MinPasswordLength = 8
MaxPasswordLength = 24
)
var (
usernamePattern = regexp.MustCompile(`^[A-Za-z0-9]{6,24}$`)
emailCodePattern = regexp.MustCompile(`^\d{6}$`)
)
func ValidateEmail(email string) (string, error) {
normalized := strings.TrimSpace(strings.ToLower(email))
if normalized == "" {
return "", errors.New("邮箱不能为空")
}
if len(normalized) > 254 {
return "", errors.New("邮箱长度不能超过 254 个字符")
}
addr, err := mail.ParseAddress(normalized)
if err != nil || strings.TrimSpace(strings.ToLower(addr.Address)) != normalized {
return "", errors.New("邮箱格式不正确")
}
local, domain, ok := strings.Cut(normalized, "@")
if !ok || local == "" || domain == "" || strings.Contains(domain, "..") || !strings.Contains(domain, ".") {
return "", errors.New("邮箱格式不正确")
}
return normalized, nil
}
func ValidateUsername(username string) (string, error) {
normalized := strings.TrimSpace(username)
if normalized == "" {
return "", errors.New("用户名不能为空")
}
length := utf8.RuneCountInString(normalized)
if length < MinUsernameLength || length > MaxUsernameLength {
return "", errors.New("用户名长度需为 6 到 24 个字符")
}
if !usernamePattern.MatchString(normalized) {
return "", errors.New("用户名只能使用字母和数字")
}
return normalized, nil
}
func ValidatePassword(password string) error {
length := utf8.RuneCountInString(password)
if length < MinPasswordLength || length > MaxPasswordLength {
return errors.New("密码长度需为 8 到 24 个字符")
}
var hasLetter bool
var hasUpper bool
var hasLower bool
var hasDigit bool
var hasSymbol bool
for _, r := range password {
if unicode.IsSpace(r) {
return errors.New("密码不能包含空白字符")
}
if !isAllowedPasswordRune(r) {
return errors.New("密码包含不允许的字符")
}
if unicode.IsLetter(r) {
hasLetter = true
if unicode.IsUpper(r) {
hasUpper = true
}
if unicode.IsLower(r) {
hasLower = true
}
}
if unicode.IsDigit(r) {
hasDigit = true
}
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
hasSymbol = true
}
}
if !hasLetter || !hasUpper || !hasLower || !hasDigit || !hasSymbol {
return errors.New("密码必须同时包含大写字母、小写字母、数字和符号")
}
return nil
}
func isAllowedPasswordRune(r rune) bool {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return true
}
switch r {
case '(', ')', '~', '!', '@', '#', '$', '%', '^', '&', '*', '-', '_', '+', '=', '|',
'{', '}', '[', ']', ':', ';', '\'', '<', '>', ',', '.', '?', '/':
return true
default:
return false
}
}
func ValidateEmailCode(code string) (string, error) {
normalized := strings.TrimSpace(code)
if !emailCodePattern.MatchString(normalized) {
return "", errors.New("邮箱验证码必须是 6 位纯数字")
}
return normalized, nil
}
func ValidateLoginIdentifier(identifier string) (string, error) {
normalized := strings.TrimSpace(identifier)
if normalized == "" {
return "", errors.New("账号不能为空")
}
if strings.Contains(normalized, "@") {
return ValidateEmail(normalized)
}
return ValidateUsername(normalized)
}
func ValidateAllowedEmailDomains(domains []string) []string {
out := make([]string, 0, len(domains))
seen := map[string]struct{}{}
for _, raw := range domains {
normalized := strings.TrimSpace(strings.ToLower(strings.TrimPrefix(raw, "@")))
if normalized == "" || strings.Contains(normalized, " ") {
continue
}
if _, ok := seen[normalized]; ok {
continue
}
seen[normalized] = struct{}{}
out = append(out, normalized)
}
return out
}
func EmailDomainAllowed(email string, domains []string) bool {
if len(domains) == 0 {
return true
}
_, domain, ok := strings.Cut(strings.ToLower(strings.TrimSpace(email)), "@")
if !ok {
return false
}
for _, allowed := range ValidateAllowedEmailDomains(domains) {
if domain == allowed {
return true
}
}
return false
}