161 lines
5.0 KiB
Go
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,
|
|
})
|
|
}
|