diff --git a/internal/config/site_settings.go b/internal/config/site_settings.go index d84124f..a6bdbff 100644 --- a/internal/config/site_settings.go +++ b/internal/config/site_settings.go @@ -166,6 +166,16 @@ func (s *SiteSettings) ForceGuidelines() bool { return s.GetBool("registration.force_guidelines", true) } +// PasswordStrength 密码强度级别 +func (s *SiteSettings) PasswordStrength() string { + level := s.Get("password.strength", "medium") + switch level { + case "low", "medium", "high", "very_high": + return level + } + return "medium" +} + // GuidelinesTimer 强制阅读倒计时秒数 func (s *SiteSettings) GuidelinesTimer() int { val := s.Get("registration.guidelines_timer", "60") diff --git a/internal/controller/auth_controller.go b/internal/controller/auth_controller.go index 7104f78..9e6ad90 100644 --- a/internal/controller/auth_controller.go +++ b/internal/controller/auth_controller.go @@ -57,6 +57,7 @@ func (ac *AuthController) RegisterPage(c *gin.Context) { "ExtraCSS": "/static/css/auth.css", "Guidelines": guidelines, "RegistrationEnabled": ac.authService.IsRegistrationEnabled(), + "PasswordStrength": ac.siteSettings.PasswordStrength(), "ShowGuidelinesModal": ac.siteSettings.ShowGuidelinesModal(), "ForceGuidelines": ac.siteSettings.ForceGuidelines(), "GuidelinesTimer": ac.siteSettings.GuidelinesTimer(), diff --git a/internal/service/auth_password.go b/internal/service/auth_password.go index bb8aad6..6e582f4 100644 --- a/internal/service/auth_password.go +++ b/internal/service/auth_password.go @@ -2,7 +2,9 @@ package service import ( "regexp" + "strings" "time" + "unicode" "metazone.cc/mce/internal/common" "metazone.cc/mce/internal/model" @@ -14,6 +16,19 @@ import ( var pwLower = regexp.MustCompile(`[a-z]`) var pwUpper = regexp.MustCompile(`[A-Z]`) var pwDigit = regexp.MustCompile(`\d`) +var pwSymbol = regexp.MustCompile(`[^a-zA-Z0-9]`) + +// 键盘水平序列(QWERTY 布局) +var keyboardHoriz = []string{ + "qwertyuiop", "asdfghjkl", "zxcvbnm", + "poiuytrewq", "lkjhgfdsa", "mnbvcxz", +} + +// 键盘垂直序列 +var keyboardVert = []string{ + "qaz", "wsx", "edc", "rfv", "tgb", "yhn", "ujm", "ik", "ol", "p", + "zaq", "xsw", "cde", "vfr", "bgt", "nhy", "mju", "ki", "lo", +} // dummyHash 防时序攻击:当用户不存在时,仍对虚拟哈希执行完整的 bcrypt 比对 var dummyHash []byte @@ -26,14 +41,156 @@ func init() { dummyHash = hash } -// validatePassword 密码强度:至少 8 位 + 包含大写字母 + 包含小写字母 + 包含数字 -func validatePassword(pw string) error { - if len(pw) < 8 || !pwLower.MatchString(pw) || !pwUpper.MatchString(pw) || !pwDigit.MatchString(pw) { +// validatePassword 多级密码强度校验 +func validatePassword(pw string, level string) error { + if level == "" { + level = "medium" + } + if len(pw) < 8 { return common.ErrWeakPassword } + + switch level { + case "low": + return nil + case "medium": + if !hasAtLeastN(pw, 2) { + return common.ErrWeakPassword + } + case "high": + if !hasAtLeastN(pw, 3) { + return common.ErrWeakPassword + } + if isWeakSequence(pw) { + return common.ErrWeakPassword + } + case "very_high": + if !hasAtLeastN(pw, 4) { + return common.ErrWeakPassword + } + if isWeakSequence(pw) { + return common.ErrWeakPassword + } + } return nil } +// hasAtLeastN 检查密码包含至少 n 类字符(小写/大写/数字/符号) +func hasAtLeastN(pw string, n int) bool { + count := 0 + if pwLower.MatchString(pw) { + count++ + } + if pwUpper.MatchString(pw) { + count++ + } + if pwDigit.MatchString(pw) { + count++ + } + if pwSymbol.MatchString(pw) { + count++ + } + return count >= n +} + +// isWeakSequence 检查连续字符(≥3 重复/排序/键盘序列) +func isWeakSequence(pw string) bool { + runes := []rune(strings.ToLower(pw)) + if len(runes) < 3 { + return false + } + + // 重复字符 aaa 111 + repeatCount := 1 + // 升序/降序计数 + ascCount, descCount := 1, 1 + + for i := 1; i < len(runes); i++ { + // 重复 + if runes[i] == runes[i-1] { + repeatCount++ + if repeatCount >= 3 { + return true + } + } else { + repeatCount = 1 + } + + // 升序 abc / 123 + if runes[i] == runes[i-1]+1 { + ascCount++ + if ascCount >= 3 { + return true + } + } else { + ascCount = 1 + } + + // 降序 cba / 321 + if runes[i] == runes[i-1]-1 { + descCount++ + if descCount >= 3 { + return true + } + } else { + descCount = 1 + } + } + + // 键盘序列 + lower := strings.ToLower(pw) + for _, seq := range keyboardHoriz { + if len(seq) >= 3 { + for i := 0; i <= len(seq)-3; i++ { + if strings.Contains(lower, seq[i:i+3]) { + return true + } + } + } + } + for _, seq := range keyboardVert { + if len(seq) >= 3 && strings.Contains(lower, seq) { + return true + } + } + + return false +} + +// PasswordStrengthLevel 获取当前密码强度级别 +func PasswordStrengthLevel(strengthStr string) string { + switch strengthStr { + case "low", "medium", "high", "very_high": + return strengthStr + } + return "medium" +} + +// PasswordStrengthHint 前端校验提示文案 +func PasswordStrengthHint(level string) string { + switch level { + case "low": + return "密码至少 8 位" + case "medium": + return "密码至少 8 位,需包含字母、数字或符号中的两类" + case "high": + return "密码至少 8 位,需包含大小写字母、数字和符号中的至少三类,且不含连续字符" + case "very_high": + return "密码至少 8 位,需包含大小写字母、数字和符号全部四类,且不含连续字符" + } + return "密码至少 8 位" +} + +// hasUnicode 检查是否包含非 ASCII 字符 +func hasUnicode(s string) bool { + for _, r := range s { + if r > unicode.MaxASCII { + return true + } + } + return false +} + // ChangePassword 修改密码 // 流程:验证当前密码 → 校验新密码强度 → 哈希 → 更新 → 删除所有 session(强制重新登录) func (s *AuthService) ChangePassword(userID uint, currentPassword, newPassword string) error { @@ -46,7 +203,7 @@ func (s *AuthService) ChangePassword(userID uint, currentPassword, newPassword s return common.ErrIncorrectPassword } - if err := validatePassword(newPassword); err != nil { + if err := validatePassword(newPassword, s.siteSettings.PasswordStrength()); err != nil { return err } diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index 54fb365..61302f1 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -48,7 +48,7 @@ func (s *AuthService) Register(req model.RegisterRequest, regIP string) (*model. } // 2. 密码强度 - if err := validatePassword(req.Password); err != nil { + if err := validatePassword(req.Password, s.siteSettings.PasswordStrength()); err != nil { return nil, err } diff --git a/templates/MetaLab-2026/html/auth/register.html b/templates/MetaLab-2026/html/auth/register.html index ca06536..1baeadb 100644 --- a/templates/MetaLab-2026/html/auth/register.html +++ b/templates/MetaLab-2026/html/auth/register.html @@ -23,7 +23,7 @@