@mui/x-license
Version:
MUI X License verification.
294 lines (285 loc) • 9.98 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.decodeLicense = decodeLicense;
exports.decodeLicenseVersion2 = decodeLicenseVersion2;
exports.decodeLicenseVersion3 = decodeLicenseVersion3;
exports.parseLicenseTokens = parseLicenseTokens;
exports.verifyLicense = verifyLicense;
var _formatErrorMessage2 = _interopRequireDefault(require("@mui/x-internals/formatErrorMessage"));
var _base = require("../encoding/base64");
var _md = require("../encoding/md5");
var _licenseStatus = require("../utils/licenseStatus");
var _licensePlan = require("../utils/licensePlan");
var _licenseModel = require("../utils/licenseModel");
function isPlanScopeSufficient(packageName, planScope) {
let acceptedScopes;
if (packageName.includes('-pro')) {
acceptedScopes = ['pro', 'premium'];
} else if (packageName.includes('-premium')) {
acceptedScopes = ['premium'];
} else {
acceptedScopes = [];
}
return acceptedScopes.includes(planScope);
}
const expiryReg = /^.*EXPIRY=([0-9]+),.*$/;
const orderReg = /^.*ORDER:([0-9]+),.*$/;
const PRO_PACKAGES_AVAILABLE_IN_INITIAL_PRO_PLAN = ['x-data-grid-pro', 'x-date-pickers-pro'];
const MAX_V8_PLAN_VERSION = 'Q3-2024';
/**
* Format: ORDER:${orderNumber},EXPIRY=${expiryTimestamp},KEYVERSION=1
*/
function decodeLicenseVersion1(license) {
let expiryTimestamp;
let orderId;
try {
expiryTimestamp = parseInt(license.match(expiryReg)[1], 10);
if (!expiryTimestamp || Number.isNaN(expiryTimestamp)) {
expiryTimestamp = null;
}
orderId = parseInt(license.match(orderReg)[1], 10);
if (!orderId || Number.isNaN(orderId)) {
orderId = null;
}
} catch (err) {
expiryTimestamp = null;
orderId = null;
}
return {
keyVersion: 1,
licenseModel: 'perpetual',
planScope: 'pro',
planVersion: 'initial',
expiryTimestamp,
expiryDate: expiryTimestamp ? new Date(expiryTimestamp) : null,
orderId: orderId != null ? String(orderId) : null,
appType: 'multi',
quantity: null,
isTestKey: license.includes('T=true')
};
}
/**
* Parse a comma-separated key=value license string into a NullableLicenseDetails object.
* Shared by v2 and v3 decoders.
*/
function parseLicenseTokens(license, licenseInfo) {
license.split(',').map(token => token.split('=')).filter(el => el.length === 2).forEach(([key, value]) => {
if (key === 'S') {
licenseInfo.planScope = value;
}
if (key === 'LM') {
licenseInfo.licenseModel = value;
}
if (key === 'E') {
const expiryTimestamp = parseInt(value, 10);
if (expiryTimestamp && !Number.isNaN(expiryTimestamp)) {
licenseInfo.expiryTimestamp = expiryTimestamp;
licenseInfo.expiryDate = new Date(expiryTimestamp);
}
}
if (key === 'PV') {
licenseInfo.planVersion = value;
}
if (key === 'O') {
licenseInfo.orderId = value;
}
if (key === 'Q') {
const qty = parseInt(value, 10);
if (qty && !Number.isNaN(qty)) {
licenseInfo.quantity = qty;
}
}
if (key === 'AT') {
licenseInfo.appType = value;
}
if (key === 'T') {
licenseInfo.isTestKey = value === 'true';
}
});
}
/**
* Format: O=${orderNumber},E=${expiryTimestamp},S=${planScope},LM=${licenseModel},PV=${planVersion},KV=2
*/
function decodeLicenseVersion2(license) {
const licenseInfo = {
keyVersion: 2,
licenseModel: null,
planScope: null,
planVersion: 'initial',
expiryTimestamp: null,
expiryDate: null,
orderId: null,
appType: 'multi',
quantity: null,
isTestKey: false
};
parseLicenseTokens(license, licenseInfo);
return licenseInfo;
}
/**
* Format: O=${orderNumber},E=${expiryTimestamp},S=${planScope},LM=${licenseModel},PV=${planVersion},Q=${quantity},AT=${appType},KV=3
*/
function decodeLicenseVersion3(license) {
const licenseInfo = {
keyVersion: 3,
licenseModel: null,
planScope: null,
planVersion: 'initial',
expiryTimestamp: null,
expiryDate: null,
orderId: null,
appType: null,
quantity: null,
isTestKey: false
};
parseLicenseTokens(license, licenseInfo);
return licenseInfo;
}
/**
* Decode the license based on its key version and return a version-agnostic `NullableLicenseDetails` object.
*/
function decodeLicense(encodedLicense) {
const license = (0, _base.base64Decode)(encodedLicense);
if (license.includes('KEYVERSION=1')) {
return decodeLicenseVersion1(license);
}
if (license.includes('KV=2')) {
return decodeLicenseVersion2(license);
}
if (license.includes('KV=3')) {
return decodeLicenseVersion3(license);
}
return null;
}
function verifyLicense({
packageInfo,
licenseKey
}) {
const {
name: packageName,
releaseDate,
version: packageVersion
} = packageInfo;
const packageMajorVersion = parseInt(packageVersion ?? '', 10);
if (!releaseDate) {
throw new Error(process.env.NODE_ENV !== "production" ? 'MUI X: The release information is missing and license validation cannot proceed. ' + 'This is an internal error that should not occur in normal usage. ' + 'Please report this issue if you encounter it.' : (0, _formatErrorMessage2.default)(188));
}
if (!licenseKey) {
return {
status: _licenseStatus.LICENSE_STATUS.NotFound
};
}
const hash = licenseKey.slice(0, 32);
const encoded = licenseKey.slice(32);
if (hash !== (0, _md.md5)(encoded)) {
return {
status: _licenseStatus.LICENSE_STATUS.Invalid
};
}
const license = decodeLicense(encoded);
if (license == null) {
console.error('MUI X: Error checking license. Key version not found!');
return {
status: _licenseStatus.LICENSE_STATUS.Invalid
};
}
// Reject test license keys outside of test environments.
// Gets replaced with `false` during production builds, making it impossible
// for users of published packages to use test licenses.
// @ts-ignore
if (license.isTestKey && !false) {
console.error('MUI X: Error checking license. Test license key used in a non-test environment!');
return {
status: _licenseStatus.LICENSE_STATUS.Invalid
};
}
if (license.licenseModel == null || !_licenseModel.LICENSE_MODELS.includes(license.licenseModel)) {
console.error('MUI X: Error checking license. License model not found or invalid!');
return {
status: _licenseStatus.LICENSE_STATUS.Invalid
};
}
if (license.expiryTimestamp == null) {
console.error('MUI X: Error checking license. Expiry timestamp not found or invalid!');
return {
status: _licenseStatus.LICENSE_STATUS.Invalid
};
}
if (license.licenseModel === 'perpetual' || process.env.NODE_ENV === 'production') {
const pkgTimestamp = parseInt((0, _base.base64Decode)(releaseDate), 10);
if (Number.isNaN(pkgTimestamp)) {
// TODO: fix mui/no-guarded-throw
// eslint-disable-next-line mui/no-guarded-throw
throw new Error(process.env.NODE_ENV !== "production" ? 'MUI X: The release information is invalid and license validation cannot proceed. ' + 'The package release timestamp could not be parsed. ' + 'This may indicate a corrupted package. Try reinstalling the MUI X packages.' : (0, _formatErrorMessage2.default)(189));
}
if (license.expiryTimestamp < pkgTimestamp) {
// Perpetual v8 (or older) licenses whose expiry predates this package release
// are not valid for v9 packages.
if (packageMajorVersion != null && packageMajorVersion >= 9 && license.licenseModel === 'perpetual' && (0, _licensePlan.isPlanVersionOlderOrEqual)(license.planVersion, MAX_V8_PLAN_VERSION)) {
return {
status: _licenseStatus.LICENSE_STATUS.NotValidForPackage,
meta: {
packageMajorVersion
}
};
}
return {
status: _licenseStatus.LICENSE_STATUS.ExpiredVersion
};
}
} else if (license.licenseModel === 'subscription' || license.licenseModel === 'annual') {
if (new Date().getTime() > license.expiryTimestamp) {
if (
// 30 days grace
new Date().getTime() < license.expiryTimestamp + 1000 * 3600 * 24 * 30 || process.env.NODE_ENV !== 'development') {
return {
status: _licenseStatus.LICENSE_STATUS.ExpiredAnnualGrace,
meta: {
expiryTimestamp: license.expiryTimestamp,
licenseKey
}
};
}
return {
status: _licenseStatus.LICENSE_STATUS.ExpiredAnnual,
meta: {
expiryTimestamp: license.expiryTimestamp,
licenseKey
}
};
}
}
if (license.planScope == null || !_licensePlan.PLAN_SCOPES.includes(license.planScope)) {
console.error('MUI X: Error checking license. planScope not found or invalid!');
return {
status: _licenseStatus.LICENSE_STATUS.Invalid
};
}
if (!isPlanScopeSufficient(packageName, license.planScope)) {
return {
status: _licenseStatus.LICENSE_STATUS.OutOfScope
};
}
// 'charts-pro' or 'tree-view-pro' can only be used with a newer Pro license
if (license.planVersion === 'initial' && license.planScope === 'pro' && !PRO_PACKAGES_AVAILABLE_IN_INITIAL_PRO_PLAN.includes(packageName)) {
return {
status: _licenseStatus.LICENSE_STATUS.NotAvailableInInitialProPlan
};
}
// v8 licenses (Q1-2026 and older) are not valid for v9 packages.
// Perpetual licenses are exempt as they are already gated by the expiry date check.
if (packageMajorVersion != null && packageMajorVersion >= 9 && license.licenseModel !== 'perpetual' && (0, _licensePlan.isPlanVersionOlderOrEqual)(license.planVersion, MAX_V8_PLAN_VERSION)) {
return {
status: _licenseStatus.LICENSE_STATUS.NotValidForPackage,
meta: {
packageMajorVersion
}
};
}
return {
status: _licenseStatus.LICENSE_STATUS.Valid
};
}