feat: 实现登录失败锁定功能
新增了登录失败次数统计和临时锁定机制,包括: 1. 定义登录锁定数据结构和数据库表结构 2. 实现租户级别的锁定状态持久化接口 3. 添加登录速率限制安全策略配置 4. 在登录流程中加入锁定前置检查和失败计数逻辑 5. 提供管理员手动解锁的内部API接口 6. 新增中文本地化的锁定提示文案
This commit is contained in:
+35
-1
@@ -117,6 +117,8 @@ func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix
|
||||
serveInternalUserCreate(wrt, req, tenant.ID)
|
||||
case req.Method == http.MethodPost && len(parts) == 5 && parts[2] == "users" && parts[4] == "action":
|
||||
serveInternalUserAction(wrt, req, tenant.ID, parts[3])
|
||||
case req.Method == http.MethodPost && len(parts) == 4 && parts[2] == "login-lockouts" && parts[3] == "release":
|
||||
serveInternalLoginLockoutRelease(wrt, req, tenant.ID)
|
||||
case req.Method == http.MethodGet && len(parts) == 3 && parts[2] == "users":
|
||||
serveInternalAdminResourceList(wrt, req, tenant.ID, "users", "")
|
||||
case len(parts) == 4 && parts[2] == "users":
|
||||
@@ -164,7 +166,8 @@ func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix
|
||||
|
||||
func isInternalAdminPostRoute(parts []string) bool {
|
||||
return (len(parts) == 3 && parts[2] == "users") ||
|
||||
(len(parts) == 5 && parts[2] == "users" && parts[4] == "action")
|
||||
(len(parts) == 5 && parts[2] == "users" && parts[4] == "action") ||
|
||||
(len(parts) == 4 && parts[2] == "login-lockouts" && parts[3] == "release")
|
||||
}
|
||||
|
||||
type internalUserCreateRequest struct {
|
||||
@@ -329,6 +332,37 @@ func isInternalUserActionSupported(eventType string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
type internalLoginLockoutReleaseRequest struct {
|
||||
AccountHash string `json:"account_hash"`
|
||||
}
|
||||
|
||||
// serveInternalLoginLockoutRelease clears the login failure lockout for one
|
||||
// account so the user can sign in again immediately.
|
||||
func serveInternalLoginLockoutRelease(wrt http.ResponseWriter, req *http.Request, tenantID types.TenantID) {
|
||||
var body internalLoginLockoutReleaseRequest
|
||||
if err := json.NewDecoder(io.LimitReader(req.Body, 1<<20)).Decode(&body); err != nil {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
accountHash := strings.ToLower(strings.TrimSpace(body.AccountHash))
|
||||
if len(accountHash) != 64 {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "account_hash 格式不正确", nil)
|
||||
return
|
||||
}
|
||||
rec, err := store.LoginLockouts.Get(tenantID, accountHash)
|
||||
if err != nil && !errors.Is(err, types.ErrNotFound) && !errors.Is(err, types.ErrUnsupported) {
|
||||
writeInternalQueryError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if err = store.LoginLockouts.Release(tenantID, accountHash, time.Now().UTC().Round(time.Millisecond)); err != nil {
|
||||
writeInternalQueryError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
logs.Info.Printf("login lockout released tenant:%d account_hash:%s had_lock:%v", tenantID, accountHash,
|
||||
rec != nil && rec.LockedUntil != nil)
|
||||
writeIMSuccess(wrt, req, map[string]any{"account_hash": accountHash, "status": "released"})
|
||||
}
|
||||
|
||||
func authorizeInternalAPI(wrt http.ResponseWriter, req *http.Request, capability string) bool {
|
||||
if !internalAPI.config.Enabled {
|
||||
writeIMError(wrt, req, http.StatusNotFound, 30004, imReasonMalformedRequest, "resource not found", nil)
|
||||
|
||||
Reference in New Issue
Block a user