初始化

This commit is contained in:
Ran
2026-08-25 17:59:42 +08:00
commit 4b7380dd9b
408 changed files with 327400 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
package service
import (
"encoding/json"
"errors"
"strings"
"time"
"juhe-factory/api/internal/model"
"juhe-factory/api/internal/security"
"github.com/google/uuid"
"gorm.io/gorm"
)
var ErrUnauthorized = errors.New("账号或密码错误")
var ErrReauthFailed = errors.New("当前管理员密码错误")
var ErrAdminPasswordConfirmation = errors.New("两次输入的新密码不一致")
var ErrAdminPasswordLength = errors.New("新密码长度必须为 820 位")
type Auth struct {
db *gorm.DB
passwords security.PasswordHasher
tokens security.TokenService
}
type TokenPair struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresAt time.Time `json:"expires_at"`
Admin map[string]any `json:"admin"`
}
func NewAuth(db *gorm.DB, passwords security.PasswordHasher, tokens security.TokenService) *Auth {
return &Auth{db: db, passwords: passwords, tokens: tokens}
}
func (s *Auth) Bootstrap(username, password string) error {
if strings.TrimSpace(password) == "" {
return nil
}
username = strings.TrimSpace(username)
var admin model.AdminUser
err := s.db.Where("username = ? AND deleted_at IS NULL", username).First(&admin).Error
if err == nil {
if s.passwords.Verify(admin.PasswordHash, password) {
return nil
}
hash, hashErr := s.passwords.Hash(password)
if hashErr != nil {
return hashErr
}
return s.db.Model(&admin).Update("password_hash", hash).Error
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
var count int64
if err := s.db.Model(&model.AdminUser{}).Count(&count).Error; err != nil || count > 0 {
return err
}
hash, err := s.passwords.Hash(password)
if err != nil {
return err
}
return s.db.Create(&model.AdminUser{Username: username, PasswordHash: hash, Enabled: true}).Error
}
func (s *Auth) Login(username, password string) (*TokenPair, error) {
var admin model.AdminUser
if err := s.db.Where("username = ? AND deleted_at IS NULL", strings.TrimSpace(username)).First(&admin).Error; err != nil || !admin.Enabled || !s.passwords.Verify(admin.PasswordHash, password) {
return nil, ErrUnauthorized
}
return s.issue(&admin, true)
}
func (s *Auth) Refresh(refreshToken string) (*TokenPair, error) {
hash := security.HashToken(refreshToken)
var record model.AdminRefreshToken
if err := s.db.Where("token_hash = ? AND revoked_at IS NULL AND expires_at > ?", hash, time.Now()).First(&record).Error; err != nil {
return nil, ErrUnauthorized
}
var admin model.AdminUser
if err := s.db.First(&admin, "id = ? AND enabled = true", record.AdminID).Error; err != nil {
return nil, ErrUnauthorized
}
now := time.Now()
if err := s.db.Model(&record).Update("revoked_at", now).Error; err != nil {
return nil, err
}
return s.issue(&admin, false)
}
func (s *Auth) Logout(refreshToken string) error {
if refreshToken == "" {
return nil
}
now := time.Now()
return s.db.Model(&model.AdminRefreshToken{}).Where("token_hash = ? AND revoked_at IS NULL", security.HashToken(refreshToken)).Update("revoked_at", now).Error
}
func (s *Auth) Authenticate(raw string) (*model.AdminUser, error) {
claims, err := s.tokens.ParseAccessToken(raw)
if err != nil {
return nil, ErrUnauthorized
}
id, err := uuid.Parse(claims.AdminID)
if err != nil {
return nil, ErrUnauthorized
}
var admin model.AdminUser
if err := s.db.First(&admin, "id = ? AND enabled = true", id).Error; err != nil {
return nil, ErrUnauthorized
}
return &admin, nil
}
func (s *Auth) ChangePassword(adminID uuid.UUID, currentPassword, newPassword, confirmPassword string) error {
if newPassword != confirmPassword {
return ErrAdminPasswordConfirmation
}
if len(newPassword) < 8 || len(newPassword) > 20 {
return ErrAdminPasswordLength
}
var admin model.AdminUser
if err := s.db.Where("id = ? AND enabled = true AND deleted_at IS NULL", adminID).First(&admin).Error; err != nil {
return ErrUnauthorized
}
if !s.passwords.Verify(admin.PasswordHash, currentPassword) {
return ErrReauthFailed
}
hash, err := s.passwords.Hash(newPassword)
if err != nil {
return err
}
now := time.Now()
return s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&admin).Update("password_hash", hash).Error; err != nil {
return err
}
return tx.Model(&model.AdminRefreshToken{}).
Where("admin_id = ? AND revoked_at IS NULL", adminID).
Update("revoked_at", now).Error
})
}
func (s *Auth) Audit(admin *model.AdminUser, action, resource, resourceID, reason, ip, traceID string, detail any) error {
return WriteAudit(s.db, admin, action, resource, resourceID, reason, ip, traceID, detail)
}
func (s *Auth) issue(admin *model.AdminUser, updateLogin bool) (*TokenPair, error) {
access, expiresAt, err := s.tokens.NewAccessToken(admin.ID.String(), admin.Username)
if err != nil {
return nil, err
}
refresh, hash, refreshExpires, err := s.tokens.NewRefreshToken()
if err != nil {
return nil, err
}
record := model.AdminRefreshToken{AdminID: admin.ID, TokenHash: hash, ExpiresAt: refreshExpires}
if err := s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&record).Error; err != nil {
return err
}
if updateLogin {
return tx.Model(admin).Update("last_login_at", time.Now()).Error
}
return nil
}); err != nil {
return nil, err
}
return &TokenPair{AccessToken: access, RefreshToken: refresh, ExpiresAt: expiresAt, Admin: map[string]any{"id": admin.ID, "username": admin.Username}}, nil
}
func WriteAudit(db *gorm.DB, admin *model.AdminUser, action, resource, resourceID, reason, ip, traceID string, detail any) error {
data, _ := json.Marshal(detail)
log := model.AdminAuditLog{AdminID: &admin.ID, AdminUsername: admin.Username, Action: action, ResourceType: resource, ResourceID: resourceID, Reason: reason, Detail: string(data), IPAddress: ip, TraceID: traceID}
return db.Create(&log).Error
}