功能更新
This commit is contained in:
142
internal/service/auth.go
Normal file
142
internal/service/auth.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"nl-pms-api/internal/commonservice"
|
||||
"nl-pms-api/internal/config"
|
||||
"nl-pms-api/internal/model"
|
||||
)
|
||||
|
||||
// AuthService 注册 / 登录 / 刷新 / 改密。
|
||||
type AuthService struct {
|
||||
DB *gorm.DB
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
// AuthTokens 登录/刷新返回的令牌与用户信息。
|
||||
type AuthTokens struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
UserID int64 `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func validAccount(username, password string) error {
|
||||
username = strings.TrimSpace(username)
|
||||
if len(username) < 3 || len(username) > 64 {
|
||||
return commonservice.BadRequest("SYNC_USERNAME_INVALID")
|
||||
}
|
||||
if len(password) < 6 {
|
||||
return commonservice.BadRequest("SYNC_PASSWORD_TOO_SHORT")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register 注册新账号。
|
||||
func (s *AuthService) Register(username, password string) error {
|
||||
if err := validAccount(username, password); err != nil {
|
||||
return err
|
||||
}
|
||||
username = strings.TrimSpace(username)
|
||||
var n int64
|
||||
s.DB.Model(&model.User{}).Where("username = ?", username).Count(&n)
|
||||
if n > 0 {
|
||||
return commonservice.Conflict("SYNC_USER_EXISTS")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return commonservice.Internal("INTERNAL")
|
||||
}
|
||||
u := model.User{
|
||||
Username: username,
|
||||
PasswordHash: string(hash),
|
||||
CreatedAt: commonservice.NowRFC(),
|
||||
}
|
||||
if err := s.DB.Create(&u).Error; err != nil {
|
||||
// 并发注册由唯一索引兜底。
|
||||
return commonservice.Conflict("SYNC_USER_EXISTS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login 校验账号并签发双令牌。
|
||||
func (s *AuthService) Login(username, password, clientIP string) (*AuthTokens, error) {
|
||||
username = strings.TrimSpace(username)
|
||||
var u model.User
|
||||
if err := s.DB.Where("username = ?", username).First(&u).Error; err != nil {
|
||||
return nil, commonservice.Unauthorized("SYNC_BAD_CREDENTIALS")
|
||||
}
|
||||
if u.Disabled != 0 {
|
||||
return nil, commonservice.Forbidden("ACCOUNT_DISABLED")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)) != nil {
|
||||
return nil, commonservice.Unauthorized("SYNC_BAD_CREDENTIALS")
|
||||
}
|
||||
tok, err := s.issueTokens(u.ID, u.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = (&ActivityService{DB: s.DB}).Ping(u.ID, clientIP)
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// Refresh 用 refresh token 换新的双令牌。
|
||||
func (s *AuthService) Refresh(refreshToken, clientIP string) (*AuthTokens, error) {
|
||||
claims, err := commonservice.Parse(s.Cfg.JWTSecret, strings.TrimSpace(refreshToken), "refresh")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var u model.User
|
||||
if err := s.DB.First(&u, claims.UserID).Error; err != nil {
|
||||
return nil, commonservice.Unauthorized("UNAUTHORIZED")
|
||||
}
|
||||
if u.Disabled != 0 {
|
||||
return nil, commonservice.Forbidden("ACCOUNT_DISABLED")
|
||||
}
|
||||
tok, err := s.issueTokens(u.ID, u.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = (&ActivityService{DB: s.DB}).Ping(u.ID, clientIP)
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// ChangePassword 在线修改密码。
|
||||
func (s *AuthService) ChangePassword(userID int64, oldPassword, newPassword string) error {
|
||||
if len(newPassword) < 6 {
|
||||
return commonservice.BadRequest("SYNC_PASSWORD_TOO_SHORT")
|
||||
}
|
||||
var u model.User
|
||||
if err := s.DB.First(&u, userID).Error; err != nil {
|
||||
return commonservice.Unauthorized("UNAUTHORIZED")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(oldPassword)) != nil {
|
||||
return commonservice.BadRequest("SYNC_OLD_PASSWORD_WRONG")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return commonservice.Internal("INTERNAL")
|
||||
}
|
||||
return s.DB.Model(&u).Update("password_hash", string(hash)).Error
|
||||
}
|
||||
|
||||
func (s *AuthService) issueTokens(userID int64, username string) (*AuthTokens, error) {
|
||||
access, err := commonservice.IssueAccess(s.Cfg.JWTSecret, userID, username, s.Cfg.AccessTTLHours)
|
||||
if err != nil {
|
||||
return nil, commonservice.Internal("INTERNAL")
|
||||
}
|
||||
refresh, err := commonservice.IssueRefresh(s.Cfg.JWTSecret, userID, username, s.Cfg.RefreshTTLDays)
|
||||
if err != nil {
|
||||
return nil, commonservice.Internal("INTERNAL")
|
||||
}
|
||||
return &AuthTokens{
|
||||
AccessToken: access,
|
||||
RefreshToken: refresh,
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user