diff --git a/server/private/license/license.ts b/server/private/license/license.ts index 61ea23b48..0ff77bc4f 100644 --- a/server/private/license/license.ts +++ b/server/private/license/license.ts @@ -50,6 +50,27 @@ type ValidateLicenseAPIResponse = { status: number; }; +// Ranks license tiers so that when multiple license keys are active, the +// highest tier among them wins. Order: personal < tier1 < tier2 < ... < +// tier[n] < enterprise. Tier numbers are parsed so this scales to any +// tier[n] without needing updates here. +function tierRank(tier?: LicenseKeyTier): number { + if (!tier) { + return -1; + } + if (tier === "enterprise") { + return Number.MAX_SAFE_INTEGER; + } + if (tier === "personal") { + return 0; + } + const match = /^tier(\d+)$/.exec(tier); + if (match) { + return parseInt(match[1], 10); + } + return 0; +} + type TokenPayload = { valid: boolean; type: LicenseKeyType; @@ -371,12 +392,22 @@ LQIDAQAB } // Compute host status: quantity = users, quantity_2 = sites + // When multiple host keys are active, prefer a valid key over an + // invalid one, and among equally-valid keys prefer the highest tier. + let selectedHostKey: LicenseKeyCache | undefined; for (const key of keys) { const cached = newCache.get(key.licenseKey)!; if (cached.type === "host") { - status.isLicenseValid = cached.valid; - status.tier = cached.tier; + if ( + !selectedHostKey || + (cached.valid && !selectedHostKey.valid) || + (cached.valid === selectedHostKey.valid && + tierRank(cached.tier) > + tierRank(selectedHostKey.tier)) + ) { + selectedHostKey = cached; + } } if (!cached.valid) { @@ -393,6 +424,11 @@ LQIDAQAB } } + if (selectedHostKey) { + status.isLicenseValid = selectedHostKey.valid; + status.tier = selectedHostKey.tier; + } + // Invalidate license if over user or site limits if ( (status.maxSites !== undefined &&