Files
LokiStack/backend/models/license.go
T
opsx 62d9b81a70 fix(license): shared invalid/grace banners + instance copy
Surface expired/over_instance/revoked red banners with CTAs via a
shared LicenseStatusBanners used by settings and ProGate. Hide seats
in product UI; keep HMAC dual-path and never silent-clear keys.
2026-11-01 03:08:37 +08:00

80 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package models
import "time"
// License Pro 授权。
// Source: hmac(旧本地签发,迁移窗口内仍接受)| opsx(opsx-licence LIC-/LICOFF-)。
// Plan: free | pro; Edition: personal | team(opsx 侧用 plan_type 映射)。
type License struct {
Plan string `json:"plan"` // free | pro
Edition string `json:"edition"` // personal | team
Seats int `json:"seats"` // 历史登录席位字段;产品侧按实例计,勿再展示
Instances int `json:"instances"` // 部署实例上限(0 = 不限)
Customer string `json:"customer"` // 客户标识
// ExpiresAt 0 = 永久;否则 Unix 秒
ExpiresAt int64 `json:"expiresAt"`
IssuedAt time.Time `json:"issuedAt"`
ActivatedAt time.Time `json:"activatedAt"`
// --- opsx-licence 对接字段(JSON 向后兼容,缺省为零值) ---
Source string `json:"source,omitempty"` // hmac | opsx
LicenseKey string `json:"licenseKey,omitempty"` // 完整密钥(周检/心跳需要;Sanitized 不外泄)
InstanceID string `json:"instanceId,omitempty"` // 机器指纹,无 PII
PlanType string `json:"planType,omitempty"` // trial | yearly | perpetual
MaxDevices int `json:"maxDevices,omitempty"` // 服务端 max_devices(实例数)
LastVerifiedAt int64 `json:"lastVerifiedAt,omitempty"` // unix 秒
LastServerTime int64 `json:"lastServerTime,omitempty"` // 防时钟回拨
ActivationToken string `json:"activationToken,omitempty"` // 解绑令牌
DeprecationNotice string `json:"deprecationNotice,omitempty"` // 旧 HMAC 迁移提示
InGrace bool `json:"inGrace,omitempty"`
GraceDaysLeft int `json:"graceDaysLeft,omitempty"`
// InvalidReason 非空且非有效 Pro 时给前端红条:expired | over_instance | revoked
// 服务端明确拒绝才写;禁止静默清空 LicenseKey。
InvalidReason string `json:"invalidReason,omitempty"`
}
func (l *License) Sanitized() map[string]interface{} {
if l == nil {
return map[string]interface{}{"plan": "free"}
}
expired := l.ExpiresAt > 0 && l.ExpiresAt < time.Now().Unix()
plan := l.Plan
if expired && !l.InGrace {
plan = "free"
}
out := map[string]interface{}{
"plan": plan, "edition": l.Edition, "instances": l.Instances,
"customer": l.Customer, "expiresAt": l.ExpiresAt, "expired": expired,
}
// seats 故意不外抛给产品 UI(按实例计)
if l.Source != "" {
out["source"] = l.Source
}
if l.PlanType != "" {
out["planType"] = l.PlanType
}
if l.MaxDevices > 0 {
out["maxDevices"] = l.MaxDevices
out["instances"] = l.MaxDevices
} else if l.Instances > 0 {
out["instances"] = l.Instances
}
if l.DeprecationNotice != "" {
out["deprecationNotice"] = l.DeprecationNotice
}
if l.InGrace {
out["inGrace"] = true
out["graceDaysLeft"] = l.GraceDaysLeft
}
if l.LastVerifiedAt > 0 {
out["lastVerifiedAt"] = l.LastVerifiedAt
}
if l.InstanceID != "" {
out["instanceId"] = l.InstanceID
}
if l.InvalidReason != "" {
out["invalidReason"] = l.InvalidReason
}
return out
}