feat(im security): add login failure lockout configuration feature

1. add APIs for login lockout config get and save
2. add backend service and controller for login lockout config
3. refactor frontend to separate login lockout config from main policy
4. update default login lockout policy values
5. add permission sql and docking documentation
This commit is contained in:
Austin
2026-09-21 09:28:03 +08:00
parent e5ab79c40d
commit 6348ace38e
10 changed files with 376 additions and 63 deletions
@@ -222,7 +222,7 @@ func (in *ImSecurityPolicySaveInp) Filter(ctx context.Context) error {
// {enabled, password_max_attempts, lock_seconds, window_seconds}。
// enabled=false 时输出仅含 enabled,行为与未配置一致。
func normalizeLoginRateLimit(value *gjson.Json) (*gjson.Json, error) {
policy := map[string]any{"enabled": false, "password_max_attempts": 5, "lock_seconds": 900, "window_seconds": 1800}
policy := map[string]any{"enabled": false, "password_max_attempts": 5, "lock_seconds": 86400, "window_seconds": 3600}
if value != nil && value.String() != "" && value.String() != "null" {
for key, item := range value.Map() {
policy[key] = item
@@ -240,14 +240,14 @@ func normalizeLoginRateLimit(value *gjson.Json) (*gjson.Json, error) {
}
lockSeconds := gconv.Int(policy["lock_seconds"])
if lockSeconds == 0 {
lockSeconds = 900
lockSeconds = 86400
}
if lockSeconds < 60 || lockSeconds > 86400 {
return nil, gerror.New("账号锁定时长必须在1至1440分钟之间")
}
windowSeconds := gconv.Int(policy["window_seconds"])
if windowSeconds == 0 {
windowSeconds = 1800
windowSeconds = 3600
}
if windowSeconds < 60 || windowSeconds > 14400 {
return nil, gerror.New("失败计数窗口必须在1至240分钟之间")
@@ -293,6 +293,53 @@ func (in *ImSecurityPolicyRestoreInp) Filter(ctx context.Context) error {
return nil
}
type ImLoginLockoutConfigModel struct {
Enabled bool `json:"enabled"`
PasswordMaxAttempts int `json:"passwordMaxAttempts"`
LockMinutes int `json:"lockMinutes"`
WindowMinutes int `json:"windowMinutes"`
PolicyId int64 `json:"policyId"`
PolicyVersion uint `json:"policyVersion"`
}
type ImLoginLockoutConfigGetInp struct {
TenantId int64 `json:"tenantId"`
}
func (in *ImLoginLockoutConfigGetInp) Filter(ctx context.Context) error {
if in.TenantId < 0 {
return gerror.New("租户ID不合法")
}
return nil
}
type ImLoginLockoutConfigSaveInp struct {
TenantId int64 `json:"tenantId"`
Enabled bool `json:"enabled"`
PasswordMaxAttempts int `json:"passwordMaxAttempts"`
LockMinutes int `json:"lockMinutes"`
WindowMinutes int `json:"windowMinutes"`
}
func (in *ImLoginLockoutConfigSaveInp) Filter(ctx context.Context) error {
if in.TenantId < 0 {
return gerror.New("租户ID不合法")
}
if !in.Enabled {
return nil
}
if in.PasswordMaxAttempts < 1 || in.PasswordMaxAttempts > 20 {
return gerror.New("登录失败最大次数必须在1至20之间")
}
if in.LockMinutes < 1 || in.LockMinutes > 1440 {
return gerror.New("账号锁定时长必须在1至1440分钟之间")
}
if in.WindowMinutes < 1 || in.WindowMinutes > 240 {
return gerror.New("失败计数窗口必须在1至240分钟之间")
}
return nil
}
type ImSecurityIPRuleListInp struct {
form.PageReq
TenantId int64 `json:"tenantId"`