166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"github.com/google/uuid"
|
||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||
"github.com/ocdp/cluster-service/internal/domain/repository"
|
||
)
|
||
|
||
// AuthService 认证领域服务
|
||
type AuthService struct {
|
||
userRepo repository.UserRepository
|
||
passwordHasher PasswordHasher
|
||
tokenGenerator TokenGenerator
|
||
}
|
||
|
||
// PasswordHasher 密码哈希接口
|
||
type PasswordHasher interface {
|
||
Hash(password string) (string, error)
|
||
Verify(password, hash string) error
|
||
}
|
||
|
||
// TokenGenerator Token 生成器接口
|
||
type TokenGenerator interface {
|
||
Generate(userID, username string) (accessToken, refreshToken string, err error)
|
||
Verify(token string) (userID, username string, err error)
|
||
VerifyWithIssuedAt(token string) (userID, username string, issuedAt int64, err error)
|
||
Refresh(refreshToken string) (newAccessToken string, err error)
|
||
}
|
||
|
||
// NewAuthService 创建认证服务
|
||
func NewAuthService(
|
||
userRepo repository.UserRepository,
|
||
passwordHasher PasswordHasher,
|
||
tokenGenerator TokenGenerator,
|
||
) *AuthService {
|
||
return &AuthService{
|
||
userRepo: userRepo,
|
||
passwordHasher: passwordHasher,
|
||
tokenGenerator: tokenGenerator,
|
||
}
|
||
}
|
||
|
||
// Register 注册新用户(仅需用户名和密码,邮箱将自动补全)
|
||
func (s *AuthService) Register(ctx context.Context, username, password string) (*entity.User, error) {
|
||
// 检查用户是否已存在
|
||
existingUser, _ := s.userRepo.GetByUsername(ctx, username)
|
||
if existingUser != nil {
|
||
return nil, entity.ErrUserExists
|
||
}
|
||
|
||
// 哈希密码
|
||
passwordHash, err := s.passwordHasher.Hash(password)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 默认生成占位邮箱,避免数据库约束失败
|
||
email := username + "@local.ocdp"
|
||
|
||
// 创建用户
|
||
user := entity.NewUser(username, passwordHash, email)
|
||
user.ID = uuid.New().String()
|
||
|
||
if err := user.Validate(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := s.userRepo.Create(ctx, user); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return user, nil
|
||
}
|
||
|
||
// Login 用户登录
|
||
func (s *AuthService) Login(ctx context.Context, username, password string) (accessToken, refreshToken string, err error) {
|
||
// 查找用户
|
||
user, err := s.userRepo.GetByUsername(ctx, username)
|
||
if err != nil {
|
||
return "", "", entity.ErrUserNotFound
|
||
}
|
||
|
||
// 验证密码
|
||
if err := s.passwordHasher.Verify(password, user.PasswordHash); err != nil {
|
||
return "", "", entity.ErrInvalidPassword
|
||
}
|
||
|
||
// 生成 Token
|
||
accessToken, refreshToken, err = s.tokenGenerator.Generate(user.ID, user.Username)
|
||
if err != nil {
|
||
return "", "", err
|
||
}
|
||
|
||
return accessToken, refreshToken, nil
|
||
}
|
||
|
||
// RefreshToken 刷新 Token
|
||
func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (string, error) {
|
||
return s.tokenGenerator.Refresh(refreshToken)
|
||
}
|
||
|
||
// GetUserByID 根据 ID 获取用户
|
||
func (s *AuthService) GetUserByID(ctx context.Context, id string) (*entity.User, error) {
|
||
return s.userRepo.GetByID(ctx, id)
|
||
}
|
||
|
||
// VerifyAccessToken 验证 Access Token(包括 revoked_after 检查)
|
||
func (s *AuthService) VerifyAccessToken(ctx context.Context, token string) (userID, username string, err error) {
|
||
// 1. JWT 自验证
|
||
userID, username, issuedAt, err := s.tokenGenerator.VerifyWithIssuedAt(token)
|
||
if err != nil {
|
||
return "", "", err
|
||
}
|
||
|
||
// 2. 检查用户级别的撤销时间
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return "", "", entity.ErrUserNotFound
|
||
}
|
||
|
||
// 3. 如果 Token 签发时间早于 revoked_after,则失效
|
||
if issuedAt < user.RevokedAfter.Unix() {
|
||
return "", "", entity.ErrTokenRevoked
|
||
}
|
||
|
||
return userID, username, nil
|
||
}
|
||
|
||
// ChangePassword 修改密码(会触发全局登出)
|
||
func (s *AuthService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
|
||
// 1. 获取用户
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return entity.ErrUserNotFound
|
||
}
|
||
|
||
// 2. 验证旧密码
|
||
if err := s.passwordHasher.Verify(oldPassword, user.PasswordHash); err != nil {
|
||
return entity.ErrInvalidPassword
|
||
}
|
||
|
||
// 3. 哈希新密码
|
||
newPasswordHash, err := s.passwordHasher.Hash(newPassword)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 4. 更新密码(会自动触发 revoked_after 更新)
|
||
user.UpdatePassword(newPasswordHash)
|
||
|
||
// 5. 保存到数据库
|
||
return s.userRepo.Update(ctx, user)
|
||
}
|
||
|
||
// ForceLogoutAll 强制全局登出(管理员操作)
|
||
func (s *AuthService) ForceLogoutAll(ctx context.Context, userID string) error {
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return entity.ErrUserNotFound
|
||
}
|
||
|
||
user.RevokeAllTokens()
|
||
return s.userRepo.Update(ctx, user)
|
||
}
|