Skip to content

Commit

Permalink
license update proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
michelengelen committed Jun 12, 2024
1 parent 2fb6124 commit 190a18e
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 5 deletions.
22 changes: 19 additions & 3 deletions packages/x-license/src/generateLicense/generateLicense.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LICENSE_UPDATE_TIMESTAMPS } from '@mui/x-license/generateLicense/licenseUpdateTimestamps';
import { md5 } from '../encoding/md5';
import { base64Encode } from '../encoding/base64';
import { LICENSE_SCOPES, LicenseScope } from '../utils/licenseScope';
Expand All @@ -8,6 +9,7 @@ const licenseVersion = '2';
export interface LicenseDetails {
orderNumber: string;
expiryDate: Date;
purchaseDate?: Date;
scope: LicenseScope;
licensingModel: LicensingModel;
}
Expand All @@ -21,9 +23,23 @@ function getClearLicenseString(details: LicenseDetails) {
throw new Error('MUI X: Invalid licensing model');
}

return `O=${details.orderNumber},E=${details.expiryDate.getTime()},S=${details.scope},LM=${
details.licensingModel
},KV=${licenseVersion}`;
if (!details.purchaseDate || new Date().getTime() < LICENSE_UPDATE_TIMESTAMPS['2024-07']) {
throw new Error('MUI X: Licenses generated without a purchaseDate are not supported');
}

const parts = [
`O=${details.orderNumber}`,
`E=${details.expiryDate.getTime()}`,
`S=${details.scope}`,
`LM=${details.licensingModel}`,
`KV=${licenseVersion}`,
];

if (details.purchaseDate) {
parts.splice(1, 0, `P=${details.purchaseDate.getTime()}`);
}

return parts.join(',');
}

export function generateLicense(details: LicenseDetails) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// according to this section on the licensing notion page:
// https://www.notion.so/mui-org/mui-x-License-validation-after-Pro-plan-with-no-cap-91fa2d16a1eb4c58825f332654196c1a?pvs=4#d26e7747aa1341d299eac49145d57edb
export const LICENSE_UPDATE_TIMESTAMPS = {
// 2024-06-20: to include charts-pro and tree-view-pro, but allow for legacy licenses
'2024-06': new Date(2024, 5, 20, 0, 0, 0, 0).getTime(),
// 2024-07-20: to fully support charts-pro and tree-view-pro on all licenses generated after this date
'2024-07': new Date(2024, 6, 20, 0, 0, 0, 0).getTime(),
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function useLicenseVerifier(
releaseInfo,
licenseKey,
acceptedScopes,
packageName,
});

const fullPackageName = `@mui/${packageName}`;
Expand Down
2 changes: 2 additions & 0 deletions packages/x-license/src/utils/licenseScope.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const LICENSE_SCOPES = ['pro', 'premium'] as const;
export const PRODUCT_SCOPES = ['data-grid', 'date-pickers', 'charts', 'tree-view'] as const;

export type LicenseScope = (typeof LICENSE_SCOPES)[number];
export type ProductScope = (typeof PRODUCT_SCOPES)[number];
1 change: 1 addition & 0 deletions packages/x-license/src/utils/licenseStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum LICENSE_STATUS {
ExpiredVersion = 'ExpiredVersion',
Valid = 'Valid',
OutOfScope = 'OutOfScope',
ProductScope = 'ProductScope',
}

export type LicenseStatus = keyof typeof LICENSE_STATUS;
50 changes: 48 additions & 2 deletions packages/x-license/src/verifyLicense/verifyLicense.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { LICENSE_UPDATE_TIMESTAMPS } from '@mui/x-license/generateLicense/licenseUpdateTimestamps';
import { base64Decode, base64Encode } from '../encoding/base64';
import { md5 } from '../encoding/md5';
import { LICENSE_STATUS, LicenseStatus } from '../utils/licenseStatus';
import { LicenseScope, LICENSE_SCOPES } from '../utils/licenseScope';
import { LicenseScope, LICENSE_SCOPES, ProductScope } from '../utils/licenseScope';
import { LicensingModel, LICENSING_MODELS } from '../utils/licensingModel';

const getDefaultReleaseDate = () => {
Expand All @@ -21,6 +22,7 @@ interface MuiLicense {
licensingModel: LicensingModel | null;
scope: LicenseScope | null;
expiryTimestamp: number | null;
purchaseTimestamp?: number | null;
}

/**
Expand All @@ -45,7 +47,8 @@ const decodeLicenseVersion1 = (license: string): MuiLicense => {
};

/**
* Format: O=${orderNumber},E=${expiryTimestamp},S=${scope},LM=${licensingModel},KV=2`;
* Format: O=${orderNumber}[,P=${purchaseTimestamp}],E=${expiryTimestamp},S=${scope},LM=${licensingModel},KV=2`;
* purchaseTimestamp is optional.
*/
const decodeLicenseVersion2 = (license: string): MuiLicense => {
const licenseInfo: MuiLicense = {
Expand Down Expand Up @@ -73,6 +76,13 @@ const decodeLicenseVersion2 = (license: string): MuiLicense => {
licenseInfo.expiryTimestamp = expiryTimestamp;
}
}

if (key === 'P') {
const purchaseTimestamp = parseInt(value, 10);
if (purchaseTimestamp && !Number.isNaN(purchaseTimestamp)) {
licenseInfo.purchaseTimestamp = purchaseTimestamp;
}
}
});

return licenseInfo;
Expand All @@ -95,14 +105,22 @@ const decodeLicense = (encodedLicense: string): MuiLicense | null => {
return null;
};

const extractProductScope = (packageName: string): ProductScope | null => {
const regex = /x-(.*?)(-pro|-premium)?$/;
const match = packageName.match(regex);
return match ? (match[1] as ProductScope) : null;
};

export function verifyLicense({
releaseInfo,
licenseKey,
acceptedScopes,
packageName,
}: {
releaseInfo: string;
licenseKey: string | undefined;
acceptedScopes: LicenseScope[];
packageName: string;
}): { status: LicenseStatus; meta?: any } {
if (!releaseInfo) {
throw new Error('MUI X: The release information is missing. Not able to validate license.');
Expand Down Expand Up @@ -136,6 +154,11 @@ export function verifyLicense({
return { status: LICENSE_STATUS.Invalid };
}

if (license.purchaseTimestamp && license.purchaseTimestamp > license.expiryTimestamp) {
console.error('MUI X: Error checking license. Purchase timestamp cannot be later than expiry!');
return { status: LICENSE_STATUS.Invalid };
}

if (license.licensingModel === 'perpetual' || process.env.NODE_ENV === 'production') {
const pkgTimestamp = parseInt(base64Decode(releaseInfo), 10);
if (Number.isNaN(pkgTimestamp)) {
Expand Down Expand Up @@ -173,5 +196,28 @@ export function verifyLicense({
return { status: LICENSE_STATUS.OutOfScope };
}

if (packageName) {
const productScope = extractProductScope(packageName);

switch (productScope) {
case 'charts':
case 'tree-view':
if (
license.purchaseTimestamp &&
license.purchaseTimestamp >= LICENSE_UPDATE_TIMESTAMPS['2024-06']
) {
// NEW LICENSE
// therefore usage of charts-pro and tree-view-pro is allowed
break;
}

return { status: LICENSE_STATUS.ProductScope };
case 'data-grid':
case 'date-pickers':
default:
break;
}
}

return { status: LICENSE_STATUS.Valid };
}

0 comments on commit 190a18e

Please sign in to comment.