feat(auth): 用户账户 - 完善个人资料与登录日志 (#752)
Co-authored-by: wuligao <1247662379@qq.com>
This commit is contained in:
1 parent
e70fe1b9c9
commit
4c1d2b4012
83 files changed
+5836
-328
No files matched your search
@@ -0,0 +1,87 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
"yingce/backend/internal/kernel"
|
||||
"yingce/backend/internal/model"
|
||||
"yingce/backend/internal/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LoginLogQuery struct{ Keyword, Event, Method, Result, IP, From, To string }
|
||||
type LoginLogPage struct {
|
||||
Logs []model.LoginLog `json:"logs"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
func (s *Service) AdminLoginLogs(actor *model.User, query LoginLogQuery, page, pageSize int) (*LoginLogPage, error) {
|
||||
if err := s.RequireAdmin(actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter := repository.LoginLogFilter{Keyword: strings.TrimSpace(query.Keyword), Event: strings.TrimSpace(query.Event), Method: strings.TrimSpace(query.Method), Result: strings.TrimSpace(query.Result), IP: strings.TrimSpace(query.IP)}
|
||||
for _, enum := range []struct {
|
||||
value string
|
||||
options []string
|
||||
}{
|
||||
{filter.Event, []string{"", "login", "logout"}},
|
||||
{filter.Result, []string{"", "success", "failed"}},
|
||||
{filter.Method, []string{"", "password", "email", "sms", "linuxdo", "verification", "unknown"}},
|
||||
} {
|
||||
valid := false
|
||||
for _, option := range enum.options {
|
||||
if option == enum.value {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return nil, kernel.BadAuthRequest("登录日志筛选参数无效")
|
||||
}
|
||||
}
|
||||
if len([]rune(filter.Keyword)) > 160 || (filter.IP != "" && net.ParseIP(filter.IP) == nil) {
|
||||
return nil, kernel.BadAuthRequest("关键词或 IP 参数无效")
|
||||
}
|
||||
for _, bound := range []struct {
|
||||
raw string
|
||||
value **time.Time
|
||||
}{{query.From, &filter.From}, {query.To, &filter.To}} {
|
||||
if bound.raw == "" {
|
||||
continue
|
||||
}
|
||||
parsed, err := time.Parse(time.RFC3339, bound.raw)
|
||||
if err != nil {
|
||||
return nil, kernel.BadAuthRequest("时间必须使用 RFC3339 格式")
|
||||
}
|
||||
*bound.value = &parsed
|
||||
}
|
||||
if filter.From != nil && filter.To != nil && filter.From.After(*filter.To) {
|
||||
return nil, kernel.BadAuthRequest("开始时间不能晚于结束时间")
|
||||
}
|
||||
page, pageSize = normalizeAdminPage(page, pageSize)
|
||||
logs, total, err := s.repo.LoginLogs(filter, pageSize, (page-1)*pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LoginLogPage{Logs: logs, Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (s *Service) AdminLoginLog(actor *model.User, id string) (*model.LoginLog, error) {
|
||||
if err := s.RequireAdmin(actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event, err := s.repo.LoginLog(strings.TrimSpace(id))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, kernel.NotFound("登录日志不存在")
|
||||
}
|
||||
return event, err
|
||||
}
|
||||
|
||||
func (s *Service) RecordRejectedLogin(method string, context LoginContext, status int) error {
|
||||
return s.authDomain().RecordRejectedLogin(method, context, status)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type (
|
||||
EmailCodeCooldownError = auth.EmailCodeCooldownError
|
||||
RegisterRequest = auth.RegisterRequest
|
||||
LoginRequest = auth.LoginRequest
|
||||
LoginContext = auth.LoginContext
|
||||
PublicAuthSettings = auth.PublicAuthSettings
|
||||
AuthSessionResult = auth.AuthSessionResult
|
||||
AuthUser = auth.AuthUser
|
||||
@@ -138,8 +139,8 @@ func (s *Service) LoginRateLimitSubject(account string) (string, error) {
|
||||
return s.authDomain().LoginRateLimitSubject(account)
|
||||
}
|
||||
|
||||
func (s *Service) Logout(cookieValue string) error {
|
||||
return s.authDomain().Logout(cookieValue)
|
||||
func (s *Service) Logout(cookieValue string, contexts ...LoginContext) error {
|
||||
return s.authDomain().Logout(cookieValue, contexts...)
|
||||
}
|
||||
|
||||
func (s *Service) CurrentUser(cookieValue string) (*model.User, error) {
|
||||
@@ -206,8 +207,8 @@ func (s *Service) BeginLinuxDOLogin(nextPath string, acceptedTerms bool) (string
|
||||
return s.authDomain().BeginLinuxDOLogin(nextPath, acceptedTerms)
|
||||
}
|
||||
|
||||
func (s *Service) CompleteLinuxDOLogin(stateValue string, code string) (*LinuxDOCallbackResult, error) {
|
||||
return s.authDomain().CompleteLinuxDOLogin(stateValue, code)
|
||||
func (s *Service) CompleteLinuxDOLogin(stateValue string, code string, cookieValue string, contexts ...LoginContext) (*LinuxDOCallbackResult, error) {
|
||||
return s.authDomain().CompleteLinuxDOLogin(stateValue, code, cookieValue, contexts...)
|
||||
}
|
||||
|
||||
func (s *Service) AdminLibTVSetting(actor *model.User) (*PublicLibTVSetting, error) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"yingce/backend/internal/auth"
|
||||
"yingce/backend/internal/model"
|
||||
)
|
||||
|
||||
type UserProfile = auth.UserProfile
|
||||
type ProfilePasswordRequest = auth.ProfilePasswordRequest
|
||||
type ProfileEmailCodeRequest = auth.ProfileEmailCodeRequest
|
||||
type ProfileEmailChangeRequest = auth.ProfileEmailChangeRequest
|
||||
type ProfileEmailChangeResult = auth.ProfileEmailChangeResult
|
||||
type VerificationTicket = auth.VerificationTicket
|
||||
|
||||
func (s *Service) UserProfile(actor *model.User, cookie string) (*UserProfile, error) {
|
||||
return s.authDomain().UserProfile(actor, cookie)
|
||||
}
|
||||
func (s *Service) ChangeProfilePassword(actor *model.User, cookie string, req ProfilePasswordRequest) error {
|
||||
return s.authDomain().ChangeProfilePassword(actor, cookie, req)
|
||||
}
|
||||
func (s *Service) SendProfileEmailCode(ctx context.Context, actor *model.User, cookie string, req ProfileEmailCodeRequest) (*VerificationTicket, error) {
|
||||
return s.authDomain().SendProfileEmailCode(ctx, actor, cookie, req)
|
||||
}
|
||||
func (s *Service) ChangeProfileEmail(actor *model.User, cookie string, req ProfileEmailChangeRequest) (*ProfileEmailChangeResult, error) {
|
||||
return s.authDomain().ChangeProfileEmail(actor, cookie, req)
|
||||
}
|
||||
func (s *Service) BeginLinuxDOReauthentication(actor *model.User, cookie string) (string, error) {
|
||||
return s.authDomain().BeginLinuxDOReauthentication(actor, cookie)
|
||||
}
|
||||
func (s *Service) LinuxDOReauthenticationState(stateValue string) (bool, error) {
|
||||
return s.authDomain().LinuxDOReauthenticationState(stateValue)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"yingce/backend/internal/model"
|
||||
)
|
||||
|
||||
func (s *Service) UpdateProfileAvatar(actor *model.User, header *multipart.FileHeader) (*AuthUser, error) {
|
||||
return s.authDomain().UpdateProfileAvatar(actor, header)
|
||||
}
|
||||
|
||||
func (s *Service) UseDefaultProfileAvatar(actor *model.User) (*AuthUser, error) {
|
||||
return s.authDomain().UseDefaultProfileAvatar(actor)
|
||||
}
|
||||
|
||||
func (s *Service) SelectDefaultProfileAvatar(actor *model.User, preset string) (*AuthUser, error) {
|
||||
return s.authDomain().SelectDefaultProfileAvatar(actor, preset)
|
||||
}
|
||||
|
||||
func (s *Service) ProfileAvatarContent(actor *model.User, targetUserID string) ([]byte, error) {
|
||||
return s.authDomain().ProfileAvatarContent(actor, targetUserID)
|
||||
}
|
||||
Reference in new issue
Block a user