package auth import ( "context" "crypto/sha256" "encoding/hex" "errors" "time" "gorm.io/gorm" "ws_usernode/internal/model" "ws_usernode/internal/pkg" ) // ErrResetTokenInvalid 表示重置令牌无效、已使用或已过期。 var ErrResetTokenInvalid = errors.New("auth: 重置令牌无效或已过期") // ResetTokenStore 为管理员密码重置令牌存储(邮件重置)。 type ResetTokenStore interface { // Create 生成令牌并存储其哈希,返回令牌明文(仅经邮件/日志发出)。 Create(ctx context.Context, adminID uint, ttl time.Duration, ip string) (string, error) // Consume 校验令牌并标记已使用,返回对应的管理员 ID。 Consume(ctx context.Context, token string) (uint, error) } // DBResetTokenStore 基于 model.PasswordResetToken 的存储实现。 type DBResetTokenStore struct { db *gorm.DB } // NewDBResetTokenStore 创建重置令牌存储。 func NewDBResetTokenStore(db *gorm.DB) *DBResetTokenStore { return &DBResetTokenStore{db: db} } func (s *DBResetTokenStore) Create(ctx context.Context, adminID uint, ttl time.Duration, ip string) (string, error) { token, err := pkg.RandomHex(24) if err != nil { return "", err } row := model.PasswordResetToken{ AdminID: adminID, TokenHash: hashToken(token), ExpiresAt: time.Now().Add(ttl), IP: ip, } if err := s.db.WithContext(ctx).Create(&row).Error; err != nil { return "", err } return token, nil } func (s *DBResetTokenStore) Consume(ctx context.Context, token string) (uint, error) { var row model.PasswordResetToken if err := s.db.WithContext(ctx).Where("token_hash = ?", hashToken(token)).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return 0, ErrResetTokenInvalid } return 0, err } if row.UsedAt != nil || time.Now().After(row.ExpiresAt) { return 0, ErrResetTokenInvalid } now := time.Now() if err := s.db.WithContext(ctx).Model(&row).Update("used_at", &now).Error; err != nil { return 0, err } return row.AdminID, nil } // hashToken 计算令牌的 SHA-256 摘要(令牌本身为高熵随机串,无需加盐)。 func hashToken(token string) string { sum := sha256.Sum256([]byte(token)) return hex.EncodeToString(sum[:]) }