diff --git a/server/login_lockout.go b/server/login_lockout.go index ecfcbc2..4dd8892 100644 --- a/server/login_lockout.go +++ b/server/login_lockout.go @@ -41,13 +41,27 @@ func tenantLoginRateLimitPolicy(tenantCode string) (types.LoginRateLimitPolicy, return policy.LoginRateLimit.Normalized() } -// loginLockedMessage renders the user-facing lockout message with the rounded -// down remaining minutes; sub-minute remainders are reported as "within 1 minute". +// loginLockedMessage renders the user-facing lockout message: sub-minute +// remainders are reported as "within 1 minute", sub-hour as "within 1 hour", +// sub-day rounded down to whole hours, otherwise rounded down to whole days. +// A non-positive remaining time (e.g. the lock already expired while rendering) +// falls back to a generic message instead of a misleading duration. func loginLockedMessage(retryAfter time.Duration) string { - if minutes := int(retryAfter.Minutes()); minutes >= 1 { - return fmt.Sprintf("登录错误次数过多,账号已锁定,请 %d 分钟后再试", minutes) + const fallback = "登录错误次数过多,账号已锁定,请稍后再试或联系管理员" + if retryAfter <= 0 { + return fallback + } + prefix := "登录错误次数过多,账号已锁定,请 " + switch { + case retryAfter < time.Minute: + return prefix + "1 分钟以内再试" + case retryAfter < time.Hour: + return prefix + "1 小时以内再试" + case retryAfter < 24*time.Hour: + return fmt.Sprintf("%s%d 小时后再试", prefix, int(retryAfter.Hours())) + default: + return fmt.Sprintf("%s%d 天后再试", prefix, int(retryAfter.Hours())/24) } - return "登录错误次数过多,账号已锁定,请 1 分钟内再试" } // loginLockoutGuard enforces the configured login failure lockout for one