110 lines
3.8 KiB
JavaScript
110 lines
3.8 KiB
JavaScript
const crypto = require('crypto');
|
|
|
|
/**
|
|
* Generate a signed license key (Base64 Payload + Base64 Signature)
|
|
* @param {string} moduleCode - 'asset', 'production', 'monitoring'
|
|
* @param {string} type - 'dev', 'sub', 'demo'
|
|
* @param {string} subscriberId - Subscriber ID
|
|
* @param {number} [activationDays] - Optional
|
|
* @param {string} [customExpiryDate] - Optional YYYY-MM-DD
|
|
* @param {string|Buffer} privateKey - PEM formatted private key
|
|
* @returns {string} Signed License Token
|
|
*/
|
|
function generateLicense(moduleCode, type, subscriberId, activationDays = null, customExpiryDate = null, privateKey) {
|
|
if (!privateKey) {
|
|
throw new Error('Private Key is required to generate license.');
|
|
}
|
|
|
|
const payloadData = {
|
|
m: moduleCode, // Module
|
|
t: type, // Type
|
|
s: subscriberId, // Subscriber ID
|
|
d: Date.now() // Issue Date
|
|
};
|
|
|
|
if (customExpiryDate) {
|
|
const date = new Date(customExpiryDate + 'T23:59:59');
|
|
if (!isNaN(date.getTime())) payloadData.e = date.getTime();
|
|
} else {
|
|
if (type === 'demo') payloadData.e = Date.now() + (30 * 24 * 60 * 60 * 1000);
|
|
else if (type === 'sub') payloadData.e = Date.now() + (365 * 24 * 60 * 60 * 1000);
|
|
}
|
|
|
|
if (activationDays) {
|
|
payloadData.ad = Date.now() + (activationDays * 24 * 60 * 60 * 1000);
|
|
}
|
|
|
|
const payloadStr = JSON.stringify(payloadData);
|
|
const sign = crypto.createSign('SHA256');
|
|
sign.update(payloadStr);
|
|
sign.end();
|
|
|
|
const signature = sign.sign(privateKey, 'base64');
|
|
const payloadB64 = Buffer.from(payloadStr).toString('base64');
|
|
|
|
return `${payloadB64}.${signature}`;
|
|
}
|
|
|
|
/**
|
|
* Verify a signed license key
|
|
* @param {string} licenseToken - The token string (Payload.Signature)
|
|
* @param {string|Buffer} publicKey - PEM formatted public key
|
|
* @returns {object} { isValid, reason, ...payload }
|
|
*/
|
|
function verifyLicense(licenseToken, publicKey) {
|
|
if (!publicKey) {
|
|
return { isValid: false, reason: 'Server Configuration Error: Public Key missing' };
|
|
}
|
|
|
|
if (!licenseToken || !licenseToken.includes('.')) {
|
|
return { isValid: false, reason: 'Invalid Key Format' };
|
|
}
|
|
|
|
const [payloadB64, signature] = licenseToken.split('.');
|
|
|
|
try {
|
|
const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
|
|
|
|
// Verify Signature
|
|
const verify = crypto.createVerify('SHA256');
|
|
verify.update(payloadStr);
|
|
verify.end();
|
|
|
|
const isValidSignature = verify.verify(publicKey, signature, 'base64');
|
|
if (!isValidSignature) {
|
|
return { isValid: false, reason: 'Invalid Signature (Key Modified or Wrong Public Key)' };
|
|
}
|
|
|
|
// Logic Checks (Expiry, Activation)
|
|
const payload = JSON.parse(payloadStr);
|
|
const { m, t, d, s, ad, e } = payload;
|
|
const subscriberId = s || null;
|
|
|
|
if (ad && Date.now() > ad) {
|
|
return { isValid: false, reason: `Activation Period Expired (${new Date(ad).toLocaleDateString()})`, module: m, type: t, subscriberId };
|
|
}
|
|
|
|
let expiryDate = null;
|
|
if (e) {
|
|
expiryDate = new Date(e);
|
|
if (Date.now() > e) {
|
|
return { isValid: false, reason: 'Expired', expiryDate, subscriberId };
|
|
}
|
|
} else {
|
|
// Fallback for old keys? No, this is a breaking change. Old keys fail signature anyway.
|
|
// Just handle 'dev' type (no expiry)
|
|
if (t !== 'dev') {
|
|
// If not dev and no e, treat as permanent or error?
|
|
// Logic above always sets 'e' for sub/demo.
|
|
}
|
|
}
|
|
|
|
return { isValid: true, module: m, type: t, subscriberId, expiryDate };
|
|
|
|
} catch (e) {
|
|
return { isValid: false, reason: 'Corrupted Key Payload' };
|
|
}
|
|
}
|
|
|
|
module.exports = { generateLicense, verifyLicense };
|