feat: 新增共库模式下登录失败日志表及相关功能

- 新增登录失败日志表(共库多租户版),添加 tenant_id 列实现租户隔离
- 调整登录失败日志逻辑,仅记录触发账号锁定的事件
- 新增管理端接口功能,为登录失败日志补充实时锁定状态
- 更新测试用例适配共库模式下的租户过滤逻辑
This commit is contained in:
Austin
2026-09-23 13:18:28 +08:00
parent b8b21d3548
commit 6bbe955e75
5 changed files with 102 additions and 12 deletions
+38
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"strconv"
"strings"
"time"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/types"
@@ -156,6 +157,9 @@ func serveInternalAdminResourceList(wrt http.ResponseWriter, req *http.Request,
return
}
}
if resource == "login-failure-logs" {
attachLoginLockoutState(tenantID, result.Items)
}
writeIMSuccess(wrt, req, map[string]any{
"items": normalizeInternalAdminRowsForAPI(result.Items),
"total": result.Total,
@@ -637,6 +641,40 @@ func normalizeInternalAdminRowForAPI(row map[string]any) map[string]any {
return row
}
// internalAdminRowString 把 DB 行字段安全转为字符串(driver 可能返回 string 或 []byte)。
func internalAdminRowString(row map[string]any, key string) string {
switch v := row[key].(type) {
case string:
return strings.TrimSpace(v)
case []byte:
return strings.TrimSpace(string(v))
default:
return strings.TrimSpace(fmt.Sprint(v))
}
}
// attachLoginLockoutState 为登录失败日志的锁定行补充 Redis 实时锁定状态。
// 锁定记录存于 Redis(im_login_lockouts 表已废弃写入),日志行是历史快照不会更新,
// 管理端解锁按钮依赖 locked_now 判断"当前是否仍锁定中",解锁成功后按钮随刷新消失。
func attachLoginLockoutState(tenantID types.TenantID, items []map[string]any) {
now := time.Now()
for _, item := range items {
if item == nil || !strings.EqualFold(internalAdminRowString(item, "risk_action"), loginAuditRiskActionLocked) {
continue
}
accountHash := internalAdminRowString(item, "account_hash")
if accountHash == "" {
continue
}
locked := false
if lockout, err := store.LoginLockouts.Get(tenantID, accountHash); err == nil && lockout != nil &&
lockout.LockedUntil != nil && lockout.LockedUntil.After(now) {
locked = true
}
item["locked_now"] = locked
}
}
func parseInternalAdminDBID(raw string) (int64, bool) {
raw = strings.TrimSpace(raw)
if raw == "" {