Documentation
¶
Overview ¶
Package pwd 提供密码策略与改密陈旧判定原语(#121):最小长度/复杂度/常见 弱口令拒绝面、历史防复用(fail-closed)、登录期过期轮换判定、令牌陈旧 秒截断比较——纯 Go 零依赖。
#121 的缺口:最小长度、历史防复用、过期轮换、改密后旧会话失效是账号体系 标配,下游(zeus 3 服务)同构手搓 pwdpolicy/pwdfresh 两包;其中 「fail-closed 的安全取舍」正该由库统一:
- 历史查询失败 ≠ 放行(HistoryCheck 契约:Fetch/Match 出错原样上抛, 绝不降级成「没查到历史 ⇒ 新密码可用」);
- 秒截断比较(TokenStaleBefore):iat 与改密时间同秒不判陈旧——JWT iat 本就是秒粒度,截断后比较避免时区/亚秒边界把「改密后新签的令牌」误判 陈旧(数据面故障也不放大成 401 全站踢下线);
- 策略违规与服务端配置 bug 分型(#115 同口径):违规是 Violation(Unwrap 为 BizError 400,middleware.Fail 自动映射 400),策略配置非法是普通 error(归 500——配置 bug 归 400 会让排障指错方向)。
弱口令拒绝面是「内嵌常见弱口令表 + 轻量 leet 归一 + 结构性弱模式」, 非 zxcvbn 级熵评估——纯 Go 零依赖是本包的边界(哈希比对器 Matcher 由调用 方注入,典型 bcrypt.CompareHashAndPassword 包装,库层不绑哈希算法)。
改密陈旧吊销(改密后旧 token 失效)的编排钩子在 kitx/auth.RevokeUser—— 落到 kitx/authz 的用户级撤销黑名单;本包的 TokenStaleBefore 是其判定口径 的单点实现(authz.RevokedUser 复用同一份)。
Index ¶
- func ApplyExpiryOnLogin(ctx context.Context, days int, mustChange *bool, changedAt *time.Time, ...) error
- func CheckMinLength(password string, min int, msgFormat string) error
- func IsViolation(err error) bool
- func TokenStaleBefore(iat time.Time, changedAt *time.Time) bool
- type HistoryCheck
- type HistorySource
- type Kind
- type Matcher
- type Policy
- type Violation
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplyExpiryOnLogin ¶
func ApplyExpiryOnLogin(ctx context.Context, days int, mustChange *bool, changedAt *time.Time, persist func(context.Context) error) error
ApplyExpiryOnLogin 登录期密码过期判定(#121):密码服役超过 days 天 ⇒ 置 *mustChange = true 并调用 persist 落库(登录成功路径调用,强制引导改密)。
契约:
- days <= 0:未启用过期策略,no-op(nil);
- mustChange 为 nil:调用方配置 bug,报普通 error(归 500,#115 同口径);
- changedAt 为 nil:无改密记录,不判过期(创建时间口径由调用方折算后 传入——库层不猜「changedAt 缺省=注册时间」);
- 未过期:不动 *mustChange(不清零——标记可能被其它路径(管理员强制) 置真,清零是 fail-open);
- persist 失败:原样上抛(fail-closed:不吞错放行);*mustChange 已置位 (内存态保留,是否重试落库由调用方决定)。
判定用 time.Now()(无时钟注入:days 以天计,亚秒边界无关语义;测试以 相对时刻构造)。
func CheckMinLength ¶
CheckMinLength 最小长度校验(#121 建议形态):rune 口径(用户感知长度, 多字节密码不被字节数误杀)。
msgFormat 是违规文案模板:为空用默认「密码至少 %d 个字符」;含 %d 占位符 时按 fmt.Sprintf(msgFormat, min) 格式化(约定 %d = 最小长度),不含 %d 时 按字面使用(避免 Sprintf 的 EXTRA 噪音)。违规返回 *Violation (Kind=KindLength,Unwrap 为 BizError 400)。min < 1 视为调用方配置 bug, 返回普通 error(归 500,#115 同口径)。
func TokenStaleBefore ¶
TokenStaleBefore 令牌陈旧判定(#121 秒截断比较,本包的单点实现—— authz.RevokedUser 的用户级判定复用同一份,两处各写一份比较正是 #119/#121 要消灭的漂移形态):
iat 严格早于 changedAt ⇒ 陈旧(true)。
两侧截断到秒再比较:JWT 的 iat 本就是秒粒度(签发写 Unix 秒),改密时间 常带亚秒(DB 时间戳),截断后比较避免亚秒边界把「改密后同一秒内新签的 令牌」误判陈旧(误判即全站踢下线,数据面故障不放大成 401 的同款取舍)。 同一秒不判陈旧——秒粒度语义的既定边界,godoc 钉明。
changedAt 为 nil(无改密记录)⇒ 不陈旧。
Types ¶
type HistoryCheck ¶
type HistoryCheck struct {
// Count 回溯条数(必须 ≥ 1)。
Count int
// Fetch 历史取数面(必须非 nil)。
Fetch HistorySource
// Match 哈希比对器(必须非 nil)。
Match Matcher
}
HistoryCheck 历史防复用检查(#121):候选密码不得与最近 Count 条历史密码 重复。
fail-closed 契约(#121 的核心取舍):Fetch/Match 出错 ≠ 放行——历史查询 失败时绝不能降级成「没查到历史 ⇒ 新密码可用」,错误原样上抛(归 500), 让调用方决定重试还是失败。命中任一历史 ⇒ *Violation(Kind=KindHistory, 400)。
零值不设防,Count 必须显式 ≥ 1 且 Fetch/Match 非 nil(Check 期 fail-fast 报配置错,归 500——配置 bug 不归 400,#115 同口径)。
type HistorySource ¶
HistorySource 历史密码取数面:返回该用户最近 count 条历史密码的**存储形态** (典型 bcrypt hash,顺序不限,逐条比对)。存储形态而非明文——历史表落的 本来就是哈希。
type Matcher ¶
Matcher 历史比对器:候选明文与存储形态比对(典型 bcrypt.CompareHashAndPassword 包装:`func(stored, candidate string) (bool, error) { return nil == bcrypt.CompareHashAndPassword([]byte(stored), []byte(candidate)) }`)。
库层不绑哈希算法(纯 Go 零依赖是本包边界):比对语义由调用方注入,错误 形态(哈希格式坏等)经 error 上抛,HistoryCheck 按 fail-closed 处理。
type Policy ¶
type Policy struct {
// MinLength 最小长度(rune 口径,必须 ≥ 1)。
MinLength int
// MaxLength 最大长度(rune 口径;0 = 不限)。防超长输入的存储/哈希层 DoS。
MaxLength int
// RequireUpper 要求含大写字母。
RequireUpper bool
// RequireLower 要求含小写字母。
RequireLower bool
// RequireDigit 要求含数字。
RequireDigit bool
// RequireSpecial 要求含特殊字符(非字母非数字,含空格)。
RequireSpecial bool
}
Policy 是密码策略(#121):长度/复杂度/常见弱口令拒绝面,Check 全量判据。
判定顺序固定(长度 → 复杂度 → 弱口令),一次返回首个违规——密码设置场景 给用户一条可执行的整改指引即可,聚合多条反而淹没重点(与 validator 的 字段聚合场景不同域)。
零值不设防,构造时 MinLength 必须显式 ≥ 1(Check 期 fail-fast 报配置错): 「无最小长度的密码策略」是最危险的缺省(配置零值透传即全员短密码), 与 WithTTL(0) panic(jwt #65)同一纪律——此处以 error 表达(Check 有 error 出口,策略配置 bug 归 500)。