97 lines
2.8 KiB
Go
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
|
|
}
|