Files
ttstd-infinite-canvas/backend/internal/auth/login_logs.go
2026-10-13 05:31:30 +08:00

97 lines
2.8 KiB
Go

package auth
import (
"errors"
"strings"
"time"
"unicode"
"yingce/backend/internal/kernel"
"yingce/backend/internal/model"
)
type LoginContext struct {
IP string
UserAgent string
}
func loginLogText(value string, max int) string {
value = strings.Map(func(r rune) rune {
if unicode.IsControl(r) {
return -1
}
return r
}, value)
runes := []rune(strings.TrimSpace(value))
if len(runes) > max {
runes = runes[:max]
}
return string(runes)
}
func MaskLoginAccount(account string) string {
account = loginLogText(account, 160)
if at := strings.LastIndex(account, "@"); at >= 0 {
if at == 0 {
return "***" + account[at:]
}
return string([]rune(account[:at])[:1]) + "***" + account[at:]
}
digits := strings.TrimPrefix(account, "+")
if len(digits) >= 8 && strings.Trim(digits, "0123456789") == "" {
return digits[:3] + "****" + digits[len(digits)-4:]
}
if len(digits) >= 4 && strings.Trim(digits, "0123456789") == "" {
return digits[:1] + "****" + digits[len(digits)-1:]
}
return account
}
func NewLoginLog(method, source string, context LoginContext) *model.LoginLog {
return &model.LoginLog{ID: kernel.NewID(), Event: "login", Method: method, Source: source, Result: "success", IP: loginLogText(context.IP, 64), UserAgent: loginLogText(context.UserAgent, 512), CreatedAt: time.Now().UTC()}
}
func loginLogUser(event *model.LoginLog, user *model.User) {
event.UserID = &user.ID
event.Username, event.DisplayName, event.Account = user.Username, user.DisplayName, user.Username
}
// 对外错误文案已由 AppError 约束;内部错误仅记固定分类,避免写入 SQL/上游密钥。
func setLoginFailure(event *model.LoginLog, original error) {
event.Result, event.Reason, event.Message = "failed", "internal_error", "系统处理失败"
if event.Event == "login" || event.UserID == nil {
event.SessionRef = ""
}
var appErr *kernel.AppError
if errors.As(original, &appErr) {
event.Reason = string(appErr.Reason)
event.Message = loginLogText(appErr.Message, 500)
}
}
func (s *Service) recordLoginFailure(event *model.LoginLog, original error) error {
setLoginFailure(event, original)
if err := s.repo.AppendLoginLog(event); err != nil {
return kernel.WrapAppError(500, "登录日志写入失败,请稍后重试", errors.Join(original, err))
}
return original
}
func (s *Service) RecordRejectedLogin(method string, context LoginContext, status int) error {
event := NewLoginLog(method, "login", context)
message := "登录请求被拒绝"
if status == 400 {
message = "登录请求参数无效"
}
if status == 429 {
message = "登录请求过于频繁"
}
if status >= 500 {
message = "登录服务暂时不可用"
}
setLoginFailure(event, kernel.NewAppError(status, message))
if err := s.repo.AppendLoginLog(event); err != nil {
return kernel.WrapAppError(500, "登录日志写入失败", err)
}
return nil
}