mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-07 21:46:38 +00:00
add quantity check
This commit is contained in:
@@ -12,6 +12,10 @@ export type LicenseStatus = {
|
|||||||
isLicenseValid: boolean; // Is the license key valid?
|
isLicenseValid: boolean; // Is the license key valid?
|
||||||
hostId: string; // Host ID
|
hostId: string; // Host ID
|
||||||
tier?: LicenseKeyTier;
|
tier?: LicenseKeyTier;
|
||||||
|
maxSites?: number;
|
||||||
|
usedSites?: number;
|
||||||
|
maxUsers?: number;
|
||||||
|
usedUsers?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LicenseKeyCache = {
|
export type LicenseKeyCache = {
|
||||||
@@ -22,12 +26,14 @@ export type LicenseKeyCache = {
|
|||||||
type?: LicenseKeyType;
|
type?: LicenseKeyType;
|
||||||
tier?: LicenseKeyTier;
|
tier?: LicenseKeyTier;
|
||||||
terminateAt?: Date;
|
terminateAt?: Date;
|
||||||
|
quantity?: number;
|
||||||
|
quantity_2?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class License {
|
export class License {
|
||||||
private serverSecret!: string;
|
private serverSecret!: string;
|
||||||
|
|
||||||
constructor(private hostMeta: HostMeta) {}
|
constructor(private hostMeta: HostMeta) { }
|
||||||
|
|
||||||
public async check(): Promise<LicenseStatus> {
|
public async check(): Promise<LicenseStatus> {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
* This file is not licensed under the AGPLv3.
|
* This file is not licensed under the AGPLv3.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { db, HostMeta } from "@server/db";
|
import { db, HostMeta, sites, users } from "@server/db";
|
||||||
import { hostMeta, licenseKey } from "@server/db";
|
import { hostMeta, licenseKey } from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import NodeCache from "node-cache";
|
import NodeCache from "node-cache";
|
||||||
import { validateJWT } from "./licenseJwt";
|
import { validateJWT } from "./licenseJwt";
|
||||||
import { eq } from "drizzle-orm";
|
import { count, eq } from "drizzle-orm";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { encrypt, decrypt } from "@server/lib/crypto";
|
import { encrypt, decrypt } from "@server/lib/crypto";
|
||||||
import {
|
import {
|
||||||
@@ -54,6 +54,7 @@ type TokenPayload = {
|
|||||||
type: LicenseKeyType;
|
type: LicenseKeyType;
|
||||||
tier: LicenseKeyTier;
|
tier: LicenseKeyTier;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
quantity_2: number;
|
||||||
terminateAt: string; // ISO
|
terminateAt: string; // ISO
|
||||||
iat: number; // Issued at
|
iat: number; // Issued at
|
||||||
};
|
};
|
||||||
@@ -140,10 +141,20 @@ LQIDAQAB
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count used sites and users for license comparison
|
||||||
|
const [siteCountRes] = await db
|
||||||
|
.select({ value: count() })
|
||||||
|
.from(sites);
|
||||||
|
const [userCountRes] = await db
|
||||||
|
.select({ value: count() })
|
||||||
|
.from(users);
|
||||||
|
|
||||||
const status: LicenseStatus = {
|
const status: LicenseStatus = {
|
||||||
hostId: this.hostMeta.hostMetaId,
|
hostId: this.hostMeta.hostMetaId,
|
||||||
isHostLicensed: true,
|
isHostLicensed: true,
|
||||||
isLicenseValid: false
|
isLicenseValid: false,
|
||||||
|
usedSites: siteCountRes?.value ?? 0,
|
||||||
|
usedUsers: userCountRes?.value ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.checkInProgress = true;
|
this.checkInProgress = true;
|
||||||
@@ -151,6 +162,8 @@ LQIDAQAB
|
|||||||
try {
|
try {
|
||||||
if (!this.doRecheck && this.statusCache.has(this.statusKey)) {
|
if (!this.doRecheck && this.statusCache.has(this.statusKey)) {
|
||||||
const res = this.statusCache.get("status") as LicenseStatus;
|
const res = this.statusCache.get("status") as LicenseStatus;
|
||||||
|
res.usedSites = status.usedSites;
|
||||||
|
res.usedUsers = status.usedUsers;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
logger.debug("Checking license status...");
|
logger.debug("Checking license status...");
|
||||||
@@ -193,7 +206,9 @@ LQIDAQAB
|
|||||||
type: payload.type,
|
type: payload.type,
|
||||||
tier: payload.tier,
|
tier: payload.tier,
|
||||||
iat: new Date(payload.iat * 1000),
|
iat: new Date(payload.iat * 1000),
|
||||||
terminateAt: new Date(payload.terminateAt)
|
terminateAt: new Date(payload.terminateAt),
|
||||||
|
quantity: payload.quantity,
|
||||||
|
quantity_2: payload.quantity_2
|
||||||
});
|
});
|
||||||
|
|
||||||
if (payload.type === "host") {
|
if (payload.type === "host") {
|
||||||
@@ -292,6 +307,8 @@ LQIDAQAB
|
|||||||
cached.tier = payload.tier;
|
cached.tier = payload.tier;
|
||||||
cached.iat = new Date(payload.iat * 1000);
|
cached.iat = new Date(payload.iat * 1000);
|
||||||
cached.terminateAt = new Date(payload.terminateAt);
|
cached.terminateAt = new Date(payload.terminateAt);
|
||||||
|
cached.quantity = payload.quantity;
|
||||||
|
cached.quantity_2 = payload.quantity_2;
|
||||||
|
|
||||||
// Encrypt the updated token before storing
|
// Encrypt the updated token before storing
|
||||||
const encryptedKey = encrypt(
|
const encryptedKey = encrypt(
|
||||||
@@ -317,7 +334,7 @@ LQIDAQAB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute host status
|
// Compute host status: quantity = users, quantity_2 = sites
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const cached = newCache.get(key.licenseKey)!;
|
const cached = newCache.get(key.licenseKey)!;
|
||||||
|
|
||||||
@@ -329,6 +346,28 @@ LQIDAQAB
|
|||||||
if (!cached.valid) {
|
if (!cached.valid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only consider quantity if defined and >= 0 (quantity = users, quantity_2 = sites)
|
||||||
|
if (
|
||||||
|
cached.quantity_2 !== undefined &&
|
||||||
|
cached.quantity_2 >= 0
|
||||||
|
) {
|
||||||
|
status.maxSites =
|
||||||
|
(status.maxSites ?? 0) + cached.quantity_2;
|
||||||
|
}
|
||||||
|
if (cached.quantity !== undefined && cached.quantity >= 0) {
|
||||||
|
status.maxUsers = (status.maxUsers ?? 0) + cached.quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate license if over user or site limits
|
||||||
|
if (
|
||||||
|
(status.maxSites !== undefined &&
|
||||||
|
(status.usedSites ?? 0) > status.maxSites) ||
|
||||||
|
(status.maxUsers !== undefined &&
|
||||||
|
(status.usedUsers ?? 0) > status.maxUsers)
|
||||||
|
) {
|
||||||
|
status.isLicenseValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate old cache and set new cache
|
// Invalidate old cache and set new cache
|
||||||
@@ -502,7 +541,7 @@ LQIDAQAB
|
|||||||
// Calculate exponential backoff delay
|
// Calculate exponential backoff delay
|
||||||
const retryDelay = Math.floor(
|
const retryDelay = Math.floor(
|
||||||
initialRetryDelay *
|
initialRetryDelay *
|
||||||
Math.pow(exponentialFactor, attempt - 1)
|
Math.pow(exponentialFactor, attempt - 1)
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type NewLicenseKey = {
|
|||||||
tier: string;
|
tier: string;
|
||||||
type: string;
|
type: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
quantity_2: number;
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user