Files
qubiaobiao a2e24aba2a feat(auth): 口令强度策略(弱口令黑名单 / 账号派生值 / 长度上限)
背景(R61 实测):浏览器密码管理器在「设置密码」页预填 123456,考生若直接提交,该口令即成为其账号真实口令;而后端此前仅由 validate 校验「长度 >= 6」,弱口令完全合法。R62 已从前端修掉预填,本次补齐后端这道闸门。

- app/lib/passwordPolicy.js + password_blacklist.json:口令策略唯一事实来源。黑名单刻意做成纯 JSON,供 Node 运行时与运维脚本共用,避免两份清单漂移。

- controller/auth.js:activate() 与 resetPassword() 接入策略(错误码 2001 / 7001),位置在 validate 之后、短信校验之前(纯本地计算、无副作用、快速失败)。

- validate/index.js:补 max:20,与前端 el-form 规则对齐(此前只有前端限制长度上限)。

- scripts/_db.cjs:DB 配置解析唯一入口;scan_weak_password_accounts.cjs:存量弱口令只读扫描器;verify_password_policy_api.cjs:真接口验证(含「强口令通过」正向对照臂,防假绿)。
2026-09-30 11:56:35 +08:00

97 lines
3.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* 统一输入校验规则集(基于 egg-validate / parameter)
*
* 对应各接口请求体的字段级校验。校验失败由控制器以
* `ctx.helper.error(400, '参数校验失败:' + validateErr.errors[0].message)`
* 统一封装为应用错误信封,与 admin 控制器既有模式保持一致。
*
* 规则说明:
* - type: 'string' + required: true + allowEmpty: false:字段必填且不可为空串/纯空白
* - trim: true:校验前去除首尾空白
* - min / max:字符串长度区间(口令口径 6-20,与前端 el-form 规则保持一致)
* - message:为每个校验项提供中文错误文案,parameter 会在 errors[] 中携带对应 message
*
* ⚠️ 口令的**强度**(弱口令黑名单 / 纯重复字符 / 连续序列 / 账号派生值)**不在本文件**。
* 本文件只负责「必填 + 长度格式」这一层;强度规则由 app/lib/passwordPolicy.js
* 单源提供,调用点在 controller/auth.js 的 activate() 与 resetPassword()。
* 分层理由:同一份强度规则需被多个口令写入口共用,并须能被运维脚本复用
* (脚本要扫描存量账号是否命中弱口令),散落实现必然出现「一处拦、一处放」。
*/
const required = (msg) => ({
type: 'string',
required: true,
allowEmpty: false,
trim: true,
message: { required: msg, empty: msg },
});
module.exports = {
// 选手登录:手机号 + 密码
login: {
phone: required('请输入手机号'),
password: required('请输入密码'),
},
// 管理员登录:用户名 + 密码
adminLogin: {
username: required('请输入管理员用户名'),
password: required('请输入密码'),
},
// 发送短信验证码:身份证号 + 图形验证码
sms: {
phone: required('请输入身份证号'),
captcha: required('请输入图形验证码'),
},
// 账号激活:身份证号 + 手机号 + 短信验证码 + 密码(>=6)
activate: {
id_number: required('请输入身份证号'),
phone: required('请输入手机号'),
smsCode: required('请输入短信验证码'),
password: {
type: 'string',
required: true,
allowEmpty: false,
trim: true,
min: 6,
max: 20,
message: {
required: '请输入密码',
empty: '请输入密码',
min: '密码长度不能少于 6 位',
max: '密码长度不能超过 20 位',
},
},
},
// 忘记密码-发码:身份证号 + 图形验证码
forgotPassword: {
id_number: required('请输入身份证号'),
captcha: required('请输入图形验证码'),
},
// 重置密码:身份证号 + 短信验证码 + 密码(>=6)
resetPassword: {
id_number: required('请输入身份证号'),
smsCode: required('请输入短信验证码'),
password: {
type: 'string',
required: true,
allowEmpty: false,
trim: true,
min: 6,
max: 20,
message: {
required: '请输入密码',
empty: '请输入密码',
min: '密码长度不能少于 6 位',
max: '密码长度不能超过 20 位',
},
},
},
};