feat: 密码强度多级校验 — low/medium/high/very_high

- site_settings 新增 password.strength 配置项
- 四级:low(len>=8) / medium(两类) / high(三类+无连续) / very_high(四类+无连续)
- 连续字符检查:重复/升序/降序/键盘水平/键盘垂直序列
- 注册和改密接口统一读取配置
- 前端 register.js 动态校验 + 管理后台安全设置区
This commit is contained in:
2026-06-22 00:54:21 +08:00
parent e9b7401dc9
commit 9a496d3055
8 changed files with 225 additions and 11 deletions

View File

@ -23,7 +23,7 @@
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" placeholder="至少 8 位,包含大小写字母与数字" required minlength="8">
<input type="password" id="password" placeholder="至少 8 位,包含大小写字母与数字" required minlength="8" data-pw-level="{{.PasswordStrength}}">
<span class="form-error" id="passwordError">密码至少 8 位,需包含大小写字母与数字</span>
</div>
<div class="form-group">

View File

@ -23,6 +23,10 @@
var toast = document.getElementById('toast');
var emailInput = document.getElementById('email');
var passwordInput = document.getElementById('password');
// 密码强度提示从 data 属性读取
var pwHint = passwordInput.getAttribute('placeholder') || '密码至少 8 位';
var pwMinLen = 8;
var pwLevel = passwordInput.getAttribute('data-pw-level') || 'medium';
var confirmPasswordInput = document.getElementById('confirmPassword');
var rememberMeCheckbox = document.getElementById('rememberMe');
var guidelinesAgreedCheckbox = document.getElementById('guidelinesAgreed');
@ -178,7 +182,7 @@
var password = passwordInput.value;
var confirm = confirmPasswordInput.value;
var emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
var passwordValid = password.length >= 8 && /[a-z]/.test(password) && /[A-Z]/.test(password) && /\d/.test(password);
var passwordValid = checkPWStrength(password);
var confirmValid = passwordValid && confirm === password;
submitBtn.disabled = !(emailValid && passwordValid && confirmValid);
}
@ -195,9 +199,22 @@
checkFormValidity();
});
function checkPWStrength(pw) {
if (pw.length < pwMinLen) return false;
var cats = 0;
if (/[a-z]/.test(pw)) cats++;
if (/[A-Z]/.test(pw)) cats++;
if (/\d/.test(pw)) cats++;
if (/[^a-zA-Z0-9]/.test(pw)) cats++;
if (pwLevel === 'low') return true;
if (pwLevel === 'medium') return cats >= 2;
if (pwLevel === 'high' || pwLevel === 'very_high') return cats >= (pwLevel === 'very_high' ? 4 : 3);
return cats >= 2;
}
passwordInput.addEventListener('input', function () {
var v = passwordInput.value;
if (v && (v.length < 8 || !/[a-z]/.test(v) || !/[A-Z]/.test(v) || !/\d/.test(v))) {
if (v && !checkPWStrength(v)) {
passwordInput.classList.add('error');
passwordError.style.display = 'block';
} else {

View File

@ -165,6 +165,28 @@
</div>
</div>
<!-- 安全设置 -->
<div class="settings-section">
<div class="section-title">安全设置</div>
<div class="section-body">
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">密码强度</span>
<span class="setting-desc">注册和修改密码时的最低强度要求</span>
</div>
<div class="setting-input-wrap">
<select id="setting-password-strength" class="setting-select" data-key="password.strength">
<option value="low">低 — 仅要求 8 位</option>
<option value="medium" selected>中 — 字母+数字/符号(两类)</option>
<option value="high">高 — 大小写+数字+符号(至少三类)+ 禁止连续字符</option>
<option value="very_high">极高 — 四类全有 + 禁止连续字符</option>
</select>
<button class="btn btn-sm btn-primary btn-save-select" data-key="password.strength" data-input="setting-password-strength">保存</button>
</div>
</div>
</div>
</div>
<!-- 注册设置 -->
<div class="settings-section">
<div class="section-title">注册设置</div>

View File

@ -9,6 +9,8 @@
const TOGGLE_KEYS = [
{ id: 'maintenance-enabled', key: 'maintenance.enabled' },
{ id: 'registration-enabled', key: 'registration.enabled' },
{ id: 'registration-show-guidelines', key: 'registration.show_guidelines' },
{ id: 'registration-force-guidelines', key: 'registration.force_guidelines' },
{ id: 'audit-enabled', key: 'audit.enabled' },
{ id: 'audit-username', key: 'audit.username_audit' },
{ id: 'audit-avatar', key: 'audit.avatar_audit' },
@ -18,9 +20,15 @@
// 已有专属 UI 的设置 key不在"其他设置"中重复显示
var DEDICATED_KEYS = {
'site.framework': true, 'site.copyright_start': true,
'site.name': true, 'site.tagline': true, 'site.contact_email': true,
'site.operator_name': true, 'site.icp_number': true, 'site.icp_license': true,
'site.police_number': true, 'site.wenwangwen': true, 'site.algorithm_filing': true,
'site.privacy_url': true, 'site.terms_url': true,
'site.guidelines': true,
'maintenance.enabled': true,
'registration.enabled': true,
'registration.enabled': true, 'registration.show_guidelines': true,
'registration.force_guidelines': true, 'registration.guidelines_timer': true,
'password.strength': true,
'audit.enabled': true, 'audit.username_audit': true, 'audit.avatar_audit': true, 'audit.bio_audit': true
};
@ -73,8 +81,7 @@
// --- 文本输入保存 ---
function bindSaveButtons() {
var buttons = document.querySelectorAll('.btn-save');
buttons.forEach(function(btn) {
document.querySelectorAll('.btn-save, .btn-save-select').forEach(function(btn) {
btn.addEventListener('click', function() {
var key = btn.getAttribute('data-key');
var inputId = btn.getAttribute('data-input');