mirror of
https://github.com/fosrl/pangolin.git
synced 2026-10-21 20:19:07 +02:00
License machines
This commit is contained in:
@@ -20,6 +20,7 @@ type EnterpriseEditionKeyGeneratedProps = {
|
||||
personalUseOnly: boolean;
|
||||
users: number;
|
||||
sites: number;
|
||||
machines: number;
|
||||
modifySubscriptionLink?: string;
|
||||
};
|
||||
|
||||
@@ -28,6 +29,7 @@ export const EnterpriseEditionKeyGenerated = ({
|
||||
personalUseOnly,
|
||||
users,
|
||||
sites,
|
||||
machines,
|
||||
modifySubscriptionLink
|
||||
}: EnterpriseEditionKeyGeneratedProps) => {
|
||||
const previewText = personalUseOnly
|
||||
@@ -69,6 +71,10 @@ export const EnterpriseEditionKeyGenerated = ({
|
||||
{
|
||||
label: "Licensed sites",
|
||||
value: sites
|
||||
},
|
||||
{
|
||||
label: "Licensed machine clients",
|
||||
value: machines
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,8 @@ export type LicenseStatus = {
|
||||
usedSites?: number;
|
||||
maxUsers?: number;
|
||||
usedUsers?: number;
|
||||
maxMachines?: number;
|
||||
usedMachines?: number;
|
||||
};
|
||||
|
||||
export type LicenseKeyCache = {
|
||||
@@ -28,6 +30,7 @@ export type LicenseKeyCache = {
|
||||
terminateAt?: Date;
|
||||
quantity?: number;
|
||||
quantity_2?: number;
|
||||
quantity_3?: number;
|
||||
};
|
||||
|
||||
export class License {
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { db, HostMeta, primaryDb, sites, users } from "@server/db";
|
||||
import { clients, db, HostMeta, primaryDb, sites, users } from "@server/db";
|
||||
import { hostMeta, licenseKey } from "@server/db";
|
||||
import logger from "@server/logger";
|
||||
import { createLocalCache } from "@server/lib/createLocalCache";
|
||||
import { validateJWT } from "./licenseJwt";
|
||||
import { count, eq } from "drizzle-orm";
|
||||
import { count, eq, isNull } from "drizzle-orm";
|
||||
import moment from "moment";
|
||||
import { encrypt, decrypt } from "@server/lib/crypto";
|
||||
import {
|
||||
@@ -77,6 +77,7 @@ type TokenPayload = {
|
||||
tier: LicenseKeyTier;
|
||||
quantity: number;
|
||||
quantity_2: number;
|
||||
quantity_3: number;
|
||||
terminateAt: string; // ISO
|
||||
iat: number; // Issued at
|
||||
};
|
||||
@@ -190,9 +191,13 @@ LQIDAQAB
|
||||
};
|
||||
}
|
||||
|
||||
// Count used sites and users for license comparison
|
||||
// Count used sites, users, and machine clients for license comparison
|
||||
const [siteCountRes] = await db.select({ value: count() }).from(sites);
|
||||
const [userCountRes] = await db.select({ value: count() }).from(users);
|
||||
const [machineCountRes] = await db
|
||||
.select({ value: count() })
|
||||
.from(clients)
|
||||
.where(isNull(clients.userId));
|
||||
const keyIdsRes = await db
|
||||
.select({ licenseKeyId: licenseKey.licenseKeyId })
|
||||
.from(licenseKey);
|
||||
@@ -202,7 +207,8 @@ LQIDAQAB
|
||||
isHostLicensed: true,
|
||||
isLicenseValid: false,
|
||||
usedSites: siteCountRes?.value ?? 0,
|
||||
usedUsers: userCountRes?.value ?? 0
|
||||
usedUsers: userCountRes?.value ?? 0,
|
||||
usedMachines: machineCountRes?.value ?? 0
|
||||
};
|
||||
|
||||
this.checkInProgress = true;
|
||||
@@ -216,6 +222,7 @@ LQIDAQAB
|
||||
const res = this.statusCache.get("status") as LicenseStatus;
|
||||
res.usedSites = status.usedSites;
|
||||
res.usedUsers = status.usedUsers;
|
||||
res.usedMachines = status.usedMachines;
|
||||
return res;
|
||||
}
|
||||
logger.debug("Checking license status...");
|
||||
@@ -273,7 +280,8 @@ LQIDAQAB
|
||||
iat: new Date(payload.iat * 1000),
|
||||
terminateAt: new Date(payload.terminateAt),
|
||||
quantity: payload.quantity,
|
||||
quantity_2: payload.quantity_2
|
||||
quantity_2: payload.quantity_2,
|
||||
quantity_3: payload.quantity_3
|
||||
});
|
||||
|
||||
if (payload.type === "host") {
|
||||
@@ -391,6 +399,7 @@ LQIDAQAB
|
||||
cached.terminateAt = new Date(payload.terminateAt);
|
||||
cached.quantity = payload.quantity;
|
||||
cached.quantity_2 = payload.quantity_2;
|
||||
cached.quantity_3 = payload.quantity_3;
|
||||
|
||||
// Encrypt the updated token before storing
|
||||
const encryptedKey = encrypt(
|
||||
@@ -445,7 +454,8 @@ LQIDAQAB
|
||||
}
|
||||
}
|
||||
|
||||
// Compute host status: quantity = users, quantity_2 = sites
|
||||
// Compute host status: quantity = users, quantity_2 = sites,
|
||||
// quantity_3 = machine clients
|
||||
// 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;
|
||||
@@ -468,7 +478,10 @@ LQIDAQAB
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only consider quantity if defined and >= 0 (quantity = users, quantity_2 = sites)
|
||||
// Only consider quantity if defined and >= 0 (quantity = users,
|
||||
// quantity_2 = sites, quantity_3 = machine clients). Keys issued
|
||||
// before machine limits existed omit quantity_3 and so impose no
|
||||
// machine limit.
|
||||
if (cached.quantity_2 !== undefined && cached.quantity_2 >= 0) {
|
||||
status.maxSites =
|
||||
(status.maxSites ?? 0) + cached.quantity_2;
|
||||
@@ -476,6 +489,10 @@ LQIDAQAB
|
||||
if (cached.quantity !== undefined && cached.quantity >= 0) {
|
||||
status.maxUsers = (status.maxUsers ?? 0) + cached.quantity;
|
||||
}
|
||||
if (cached.quantity_3 !== undefined && cached.quantity_3 >= 0) {
|
||||
status.maxMachines =
|
||||
(status.maxMachines ?? 0) + cached.quantity_3;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedHostKey) {
|
||||
@@ -483,12 +500,14 @@ LQIDAQAB
|
||||
status.tier = selectedHostKey.tier;
|
||||
}
|
||||
|
||||
// Invalidate license if over user or site limits
|
||||
// Invalidate license if over user, site, or machine client limits
|
||||
if (
|
||||
(status.maxSites !== undefined &&
|
||||
(status.usedSites ?? 0) > status.maxSites) ||
|
||||
(status.maxUsers !== undefined &&
|
||||
(status.usedUsers ?? 0) > status.maxUsers)
|
||||
(status.usedUsers ?? 0) > status.maxUsers) ||
|
||||
(status.maxMachines !== undefined &&
|
||||
(status.usedMachines ?? 0) > status.maxMachines)
|
||||
) {
|
||||
status.isLicenseValid = false;
|
||||
}
|
||||
|
||||
@@ -222,15 +222,18 @@ export async function handleSubscriptionCreated(
|
||||
|
||||
let numUsers: number;
|
||||
let numSites: number;
|
||||
let numMachines: number;
|
||||
let tier = "enterprise";
|
||||
|
||||
if (subscriptionPriceId === priceSet[LicenseId.TIER1]) {
|
||||
numUsers = 25;
|
||||
numSites = 25;
|
||||
numMachines = 25;
|
||||
tier = "tier1";
|
||||
} else if (subscriptionPriceId === priceSet[LicenseId.TIER2]) {
|
||||
numUsers = 50;
|
||||
numSites = 100;
|
||||
numMachines = 50;
|
||||
tier = "tier2";
|
||||
} else {
|
||||
logger.error(
|
||||
@@ -240,7 +243,7 @@ export async function handleSubscriptionCreated(
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`License type determined: ${numUsers} users, ${numSites} sites for subscription ${subscription.id}`
|
||||
`License type determined: ${numUsers} users, ${numSites} sites, ${numMachines} machines for subscription ${subscription.id}`
|
||||
);
|
||||
|
||||
// Grace period of 5 days added on top of the current billing
|
||||
@@ -266,6 +269,7 @@ export async function handleSubscriptionCreated(
|
||||
paidFor: true,
|
||||
users: numUsers,
|
||||
sites: numSites,
|
||||
machines: numMachines,
|
||||
tier: tier,
|
||||
expiresAt: expiresAt
|
||||
})
|
||||
@@ -293,6 +297,7 @@ export async function handleSubscriptionCreated(
|
||||
personalUseOnly: false,
|
||||
users: numUsers,
|
||||
sites: numSites,
|
||||
machines: numMachines,
|
||||
modifySubscriptionLink: `${config.getRawConfig().app.dashboard_url}/${customer.orgId}/settings/billing`
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface LicenseKey {
|
||||
type: string;
|
||||
quantity: number;
|
||||
quantity_2: number;
|
||||
quantity_3: number;
|
||||
isValid: boolean;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
|
||||
@@ -8,6 +8,7 @@ export type GeneratedLicenseKey = {
|
||||
type: string;
|
||||
users: number;
|
||||
sites: number;
|
||||
machines: number;
|
||||
};
|
||||
|
||||
export type ListGeneratedLicenseKeysResponse = GeneratedLicenseKey[];
|
||||
@@ -22,6 +23,7 @@ export type NewLicenseKey = {
|
||||
type: string;
|
||||
quantity: number;
|
||||
quantity_2: number;
|
||||
quantity_3: number;
|
||||
isValid: boolean;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
|
||||
Reference in New Issue
Block a user