161 lines
5.0 KiB
Go

package auth
import (
"errors"
"time"
"hyapp-admin-server/internal/config"
"hyapp-admin-server/internal/model"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/repository"
authcore "hyapp-admin-server/internal/service"
"gorm.io/gorm"
)
type AuthFlowService struct {
store authStore
auth *authcore.AuthService
cfg config.Config
}
type authStore interface {
FindUserByUsername(username string) (*model.User, error)
FindUserByID(id uint) (*model.User, error)
CreateRefreshToken(token model.RefreshToken) error
RotateRefreshToken(oldTokenID uint, token model.RefreshToken) error
FindRefreshToken(hash string) (*model.RefreshToken, error)
RevokeRefreshToken(hash string) error
TouchUserLogin(id uint) error
CreateLoginLog(log model.LoginLog) error
ResetUserPassword(id uint, password string) error
}
type LoginInput struct {
Username string
Password string
IP string
UserAgent string
}
type LoginResult struct {
AccessToken string
RefreshToken string
ExpiresAtMS int64
User model.User
Permissions []string
}
func NewService(store authStore, auth *authcore.AuthService, cfg config.Config) *AuthFlowService {
return &AuthFlowService{store: store, auth: auth, cfg: cfg}
}
func (s *AuthFlowService) Login(input LoginInput) (LoginResult, error) {
user, err := s.store.FindUserByUsername(input.Username)
if err != nil || !authcore.CheckPassword(user.PasswordHash, input.Password) {
s.loginLog(input, nil, "failed", "用户名或密码错误")
return LoginResult{}, errors.New("用户名或密码错误")
}
if user.Status != model.UserStatusActive {
s.loginLog(input, &user.ID, "failed", "账号不可登录")
return LoginResult{}, errors.New("账号不可登录")
}
permissions := repository.PermissionsForUser(*user)
accessToken, expiresAt, err := s.auth.GenerateAccessToken(user.ID, user.Username, permissions)
if err != nil {
return LoginResult{}, err
}
refreshToken, refreshRecord, err := s.newRefreshToken(user.ID, input.IP, input.UserAgent)
if err != nil {
return LoginResult{}, err
}
if err := s.store.CreateRefreshToken(refreshRecord); err != nil {
return LoginResult{}, err
}
_ = s.store.TouchUserLogin(user.ID)
s.loginLog(input, &user.ID, "success", "登录成功")
return LoginResult{AccessToken: accessToken, RefreshToken: refreshToken, ExpiresAtMS: expiresAt.UnixMilli(), User: *user, Permissions: permissions}, nil
}
func (s *AuthFlowService) Logout(refreshToken string) error {
if refreshToken == "" {
return nil
}
return s.store.RevokeRefreshToken(authcore.HashToken(refreshToken))
}
func (s *AuthFlowService) Refresh(refreshToken string, input LoginInput) (LoginResult, error) {
if refreshToken == "" {
return LoginResult{}, errors.New("刷新会话不存在")
}
stored, err := s.store.FindRefreshToken(authcore.HashToken(refreshToken))
if err != nil {
return LoginResult{}, errors.New("刷新会话已失效")
}
user, err := s.store.FindUserByID(stored.UserID)
if err != nil || user.Status != model.UserStatusActive {
return LoginResult{}, errors.New("账号不可登录")
}
permissions := repository.PermissionsForUser(*user)
accessToken, expiresAt, err := s.auth.GenerateAccessToken(user.ID, user.Username, permissions)
if err != nil {
return LoginResult{}, err
}
newRefreshToken, refreshRecord, err := s.newRefreshToken(user.ID, input.IP, input.UserAgent)
if err != nil {
return LoginResult{}, err
}
if err := s.store.RotateRefreshToken(stored.ID, refreshRecord); err != nil {
return LoginResult{}, err
}
return LoginResult{AccessToken: accessToken, RefreshToken: newRefreshToken, ExpiresAtMS: expiresAt.UnixMilli(), User: *user, Permissions: permissions}, nil
}
func (s *AuthFlowService) newRefreshToken(userID uint, ip string, userAgent string) (string, model.RefreshToken, error) {
refreshToken, refreshHash, err := authcore.NewRefreshToken()
if err != nil {
return "", model.RefreshToken{}, err
}
return refreshToken, model.RefreshToken{
UserID: userID,
TokenHash: refreshHash,
UserAgent: userAgent,
IP: ip,
ExpiresAtMS: time.Now().UTC().Add(s.cfg.RefreshTokenTTL).UnixMilli(),
}, nil
}
func (s *AuthFlowService) Me(actor shared.Actor) (*model.User, []string, error) {
user, err := s.store.FindUserByID(actor.UserID)
if err != nil {
return nil, nil, err
}
return user, repository.PermissionsForUser(*user), nil
}
func (s *AuthFlowService) ChangePassword(actor shared.Actor, oldPassword string, newPassword string) error {
user, err := s.store.FindUserByID(actor.UserID)
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("用户不存在")
}
if err != nil {
return err
}
if !authcore.CheckPassword(user.PasswordHash, oldPassword) {
return errors.New("旧密码不正确")
}
return s.store.ResetUserPassword(user.ID, newPassword)
}
func (s *AuthFlowService) loginLog(input LoginInput, userID *uint, status string, message string) {
_ = s.store.CreateLoginLog(model.LoginLog{
Username: input.Username,
UserID: userID,
IP: input.IP,
UserAgent: input.UserAgent,
Status: status,
Message: message,
})
}