fix: 按评审报告止血支付漏洞并加固授权链路

废除浏览器回跳自助结算,通用 webhook 强制密钥鉴权,Waffo 支持验签与未知事件忽略;重构订单状态机/激活占座/SQLite WAL,并补备份、readyz、限流与前端导出/登录页安全修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-12 21:26:58 +08:00
1 parent 8cc2c83f9d
commit bac72e6945
24 files changed
+682 -391

No files matched your search

@@ -6,6 +6,8 @@ import (
"backend/utils"
"errors"
"time"
"gorm.io/gorm"
)
func inferPlanType(lic model.License) string {
@@ -15,12 +17,13 @@ func inferPlanType(lic model.License) string {
if lic.Duration <= 0 || lic.EndDate.Equal(model.PermanentEndDate) || lic.EndDate.Year() >= 2099 {
return "perpetual"
}
if lic.Duration <= 180 {
return "trial"
}
// 先判月度,再判体验/包年,避免 monthly 成为死代码 // Logan.Li
if lic.Duration <= 31 {
return "monthly"
}
if lic.Duration <= 180 {
return "trial"
}
return "yearly"
}
@@ -75,75 +78,112 @@ func (s *LicenseService) Verify(req dto.LicenseVerifyRequest) (*dto.LicenseVerif
}, nil
}
// Activate 客户端激活 License(机器绑定) // Logan.Li
// Activate 客户端激活 License(事务 + 条件占座,消除 TOCTOU) // Logan.Li
func (s *LicenseService) Activate(req dto.LicenseActivateRequest, clientIP string) (map[string]interface{}, error) {
var lic model.License
if err := s.db.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil {
return nil, errors.New("无效的 License")
}
var payload map[string]interface{}
err := s.db.Transaction(func(tx *gorm.DB) error {
var lic model.License
if err := tx.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil {
return errors.New("无效的 License")
}
if lic.Status == model.LicenseStatusRevoked || lic.Status == model.LicenseStatusSuspended {
return errors.New("License 已被吊销或暂停")
}
if lic.Status == model.LicenseStatusExpired || utils.IsLicenseExpired(lic.EndDate) {
return errors.New("License 已过期")
}
if lic.Status == model.LicenseStatusRevoked {
return nil, errors.New("License 已被吊销")
}
if lic.Status == model.LicenseStatusExpired || utils.IsLicenseExpired(lic.EndDate) {
return nil, errors.New("License 已过期")
}
now := time.Now()
var existing model.Activation
if err := tx.Where("license_id = ? AND device_id = ?", lic.ID, req.DeviceID).
First(&existing).Error; err == nil {
updates := map[string]interface{}{
"last_seen_at": now,
"is_online": true,
"device_name": req.DeviceName,
"device_info": req.DeviceInfo,
"ip": clientIP,
}
if existing.Status != "active" {
updates["status"] = "active"
res := tx.Model(&model.License{}).
Where("id = ? AND current_activations < max_activations", lic.ID).
Update("current_activations", gorm.Expr("current_activations + 1"))
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return errors.New("已达到最大激活数量限制")
}
}
if err := tx.Model(&existing).Updates(updates).Error; err != nil {
return err
}
if err := tx.First(&lic, lic.ID).Error; err != nil {
return err
}
payload = licenseClientPayload(lic)
return nil
}
var existing model.Activation
if err := s.db.Where("license_id = ? AND device_id = ?", lic.ID, req.DeviceID).First(&existing).Error; err == nil {
existing.LastSeenAt = time.Now()
existing.IsOnline = true
existing.DeviceName = req.DeviceName
existing.DeviceInfo = req.DeviceInfo
existing.IP = clientIP
s.db.Save(&existing)
return licenseClientPayload(lic), nil
}
res := tx.Model(&model.License{}).
Where("id = ? AND current_activations < max_activations", lic.ID).
Updates(map[string]interface{}{
"current_activations": gorm.Expr("current_activations + 1"),
"status": model.LicenseStatusActive,
})
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return errors.New("已达到最大激活数量限制")
}
if lic.CurrentActivations >= lic.MaxActivations {
return nil, errors.New("已达到最大激活数量限制")
if err := tx.Create(&model.Activation{
LicenseID: lic.ID,
DeviceID: req.DeviceID,
DeviceName: req.DeviceName,
DeviceInfo: req.DeviceInfo,
IP: clientIP,
Status: "active",
IsOnline: true,
ActivatedAt: now,
LastSeenAt: now,
}).Error; err != nil {
return errors.New("激活失败")
}
if err := tx.First(&lic, lic.ID).Error; err != nil {
return err
}
payload = licenseClientPayload(lic)
return nil
})
if err != nil {
return nil, err
}
activation := model.Activation{
LicenseID: lic.ID,
DeviceID: req.DeviceID,
DeviceName: req.DeviceName,
DeviceInfo: req.DeviceInfo,
IP: clientIP,
Status: "active",
IsOnline: true,
ActivatedAt: time.Now(),
LastSeenAt: time.Now(),
}
if err := s.db.Create(&activation).Error; err != nil {
return nil, errors.New("激活失败")
}
lic.CurrentActivations++
if lic.Status == model.LicenseStatusInactive {
lic.Status = model.LicenseStatusActive
}
s.db.Save(&lic)
return licenseClientPayload(lic), nil
return payload, nil
}
// Deactivate 客户端停用 // Logan.Li
// Deactivate 客户端停用(仅 active→inactive 才减席位) // Logan.Li
func (s *LicenseService) Deactivate(req dto.LicenseDeactivateRequest) error {
var lic model.License
if err := s.db.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil {
return errors.New("无效的 License")
}
result := s.db.Model(&model.Activation{}).
Where("license_id = ? AND device_id = ?", lic.ID, req.DeviceID).
Update("status", "inactive")
if result.RowsAffected > 0 && lic.CurrentActivations > 0 {
lic.CurrentActivations--
s.db.Save(&lic)
}
return nil
return s.db.Transaction(func(tx *gorm.DB) error {
var lic model.License
if err := tx.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil {
return errors.New("无效的 License")
}
res := tx.Model(&model.Activation{}).
Where("license_id = ? AND device_id = ? AND status = ?", lic.ID, req.DeviceID, "active").
Updates(map[string]interface{}{"status": "inactive", "is_online": false})
if res.Error != nil {
return res.Error
}
if res.RowsAffected > 0 {
return tx.Model(&model.License{}).
Where("id = ? AND current_activations > 0", lic.ID).
Update("current_activations", gorm.Expr("current_activations - 1")).Error
}
return nil
})
}
// Heartbeat 客户端心跳 // Logan.Li
@@ -153,8 +193,8 @@ func (s *LicenseService) Heartbeat(req dto.LicenseHeartbeatRequest) (map[string]
return nil, errors.New("无效的 License")
}
if lic.Status == model.LicenseStatusRevoked {
return nil, errors.New("License 已被吊销")
if lic.Status == model.LicenseStatusRevoked || lic.Status == model.LicenseStatusSuspended {
return nil, errors.New("License 已被吊销或暂停")
}
if lic.Status == model.LicenseStatusExpired || utils.IsLicenseExpired(lic.EndDate) {
return nil, errors.New("License 已过期")
@@ -183,4 +223,3 @@ func (s *LicenseService) ListDevicesByKey(licenseKey string) ([]dto.ActivationRe
}
return results, lic.MaxActivations, nil
}