// Package license defines the Vantage licence payload and its offline // verification. // // A licence is a signed blob (ECDSA P-384 with SHA-256). The server checks a // signature, an expiry, a deployment mode and an instance ID, and asks nobody's // permission. That buys air-gapped self-hosting and means no instance depends // on the licensing service being reachable. // // It costs revocation: once issued, a licence is valid until it expires // whatever the billing system later says. Self Hosted is sold annually only so // that window is bounded. package license import "time" const ( TierFree = "free" TierProfessional = "professional" TierSelfHosted = "self_hosted" DeploymentCloud = "cloud" DeploymentSelfHosted = "self_hosted" FeatureConsole = "console" // browser SSH/RDP/VNC FeatureOIDC = "oidc" // per-instance single sign-on ) // Unlimited is the sentinel for "no cap" in every Limits field. const Unlimited = -1 // Limits are the countable caps a licence grants. type Limits struct { MaxServers int `json:"max_servers"` MaxSecretGroups int `json:"max_secret_groups"` MaxChannels int `json:"max_channels"` } // License is the signed payload. // // InstanceID is always populated: the self-hosted purchase flow links the // instance UUID before the licence is signed, so there is no unbound licence // and no claim protocol. type License struct { ID string `json:"id"` // uuid, for support and audit InstanceID string `json:"instance_id"` // the instance this licence is bound to AccountID string `json:"account_id"` // admin-side customer, informational InstanceName string `json:"instance_name"` // display only Tier string `json:"tier"` Deployment string `json:"deployment"` IssuedAt time.Time `json:"issued_at"` ExpiresAt time.Time `json:"expires_at"` Limits Limits `json:"limits"` Features []string `json:"features"` } // HasFeature reports whether the licence grants a named feature. // // Callers must use this rather than switching on Tier. Adding a tier, or // changing what a tier includes, must never require a server release. func (l License) HasFeature(name string) bool { for _, f := range l.Features { if f == name { return true } } return false } // WithinLimit reports whether one more of something is allowed. // A max of Unlimited always allows. func WithinLimit(current, max int) bool { if max == Unlimited { return true } return current < max }