Files
ReinLoopTest/ControlPanel/license-manager.js
T
2026-07-30 11:12:31 +08:00

56 lines
2.3 KiB
JavaScript

const crypto = require("node:crypto");
const fs = require("node:fs");
function requiredText(value, fieldName) {
const text = String(value || "").trim();
if (!text) throw new Error(`${fieldName}不能为空`);
return text;
}
function normalizeTimestamp(value, fieldName) {
const timestamp = requiredText(value, fieldName);
const match = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/.exec(timestamp);
if (!match) throw new Error(`${fieldName}格式必须为 YYYY-MM-DD HH:MM`);
const [, year, month, day, hour, minute] = match.map(Number);
const parsed = new Date(year, month - 1, day, hour, minute);
if (parsed.getFullYear() !== year || parsed.getMonth() !== month - 1 ||
parsed.getDate() !== day || parsed.getHours() !== hour || parsed.getMinutes() !== minute) {
throw new Error(`${fieldName}不是有效日期时间`);
}
return timestamp;
}
function signLicense(payload, privateKeyPath) {
const licenseId = payload.license_id || crypto.randomUUID();
const deviceId = requiredText(payload.device_id, "device_id");
const deviceParts = deviceId.split("/");
if (deviceParts.length !== 2 || deviceParts.some((part) => !/^[a-z0-9][a-z0-9_-]{1,63}$/.test(part))) {
throw new Error("device_id 必须是 company-code/line-code 格式");
}
const issued = normalizeTimestamp(payload.issued, "签发时间");
const expiry = normalizeTimestamp(payload.expiry, "到期时间");
if (expiry <= issued) throw new Error("到期时间必须晚于签发时间");
const normalized = {
license_id: licenseId,
customer: requiredText(payload.customer, "customer"),
company_id: requiredText(payload.company_id, "company_id"),
production_line_id: requiredText(payload.production_line_id, "production_line_id"),
device_id: deviceId,
issued,
expiry,
features: requiredText(payload.features || "*", "features")
};
const payloadBase64 = Buffer.from(JSON.stringify(normalized)).toString("base64");
const privateKey = fs.readFileSync(privateKeyPath);
const signature = crypto.sign("sha256", Buffer.from(payloadBase64), {
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
});
return {
payload: normalized,
content: `${payloadBase64}|${signature.toString("base64")}`
};
}
module.exports = { signLicense };