@interopio/desktop-cli
Version:
io.Connect Desktop Seed Repository CLI Tools
245 lines • 10.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LicenseValidator = void 0;
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const utils_1 = require("../utils");
class LicenseValidator {
async validate() {
try {
const licenseData = await this.loadLicenseData();
if (!licenseData) {
utils_1.Logger.error('No license.json file found');
return false;
}
// Check if license data contains a JWT token
let license;
if (this.isJWTToken(licenseData)) {
utils_1.Logger.debug('Detected JWT token in license.json, decoding...');
license = await this.decodeJWTLicense(licenseData);
}
else {
utils_1.Logger.debug('Using plain JSON license format');
license = licenseData;
}
const validationResult = this.validateLicenseStructure(license);
if (!validationResult.isValid) {
validationResult.errors.forEach(error => utils_1.Logger.error(error));
return false;
}
// Check expiration
if (license.expiresAt) {
const expirationDate = new Date(license.expiresAt);
const now = new Date();
if (expirationDate < now) {
utils_1.Logger.error('License has expired');
return false;
}
// Warn if expiring soon (within 30 days)
const daysUntilExpiration = Math.ceil((expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
if (daysUntilExpiration <= 30) {
utils_1.Logger.warning(`License expires in ${daysUntilExpiration} days`);
}
}
return true;
}
catch (error) {
utils_1.Logger.error(`License validation error: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
async validateComponentAccess(componentName) {
try {
const licenseData = await this.loadLicenseData();
if (!licenseData)
return false;
// Decode license if it's a JWT token
let license;
if (this.isJWTToken(licenseData)) {
license = await this.decodeJWTLicense(licenseData);
}
else {
license = licenseData;
}
// Check if component is allowed by license
if (license.allowedComponents && license.allowedComponents.length > 0) {
return license.allowedComponents.includes(componentName) || license.allowedComponents.includes('*');
}
return true; // No restrictions specified
}
catch (error) {
utils_1.Logger.error(`Component access validation error: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
async getLicenseInfo() {
try {
const licenseData = await this.loadLicenseData();
if (!licenseData)
return null;
// Decode license if it's a JWT token
if (this.isJWTToken(licenseData)) {
return await this.decodeJWTLicense(licenseData);
}
else {
return licenseData;
}
}
catch (error) {
utils_1.Logger.error(`Failed to get license info: ${error instanceof Error ? error.message : String(error)}`);
return null;
}
}
validateLicenseStructure(license) {
const errors = [];
const warnings = [];
// Required fields
if (!license.id) {
errors.push('License ID is required');
}
if (!license.type || !['development', 'production', 'enterprise', 'trial'].includes(license.type)) {
errors.push('Valid license type is required (development, production, enterprise, trial)');
}
if (!Array.isArray(license.features)) {
errors.push('License features must be an array');
}
if (!Array.isArray(license.allowedComponents)) {
errors.push('Allowed components must be an array');
}
// Warnings for optional fields
if (license.type === 'trial' && !license.expiresAt) {
warnings.push('Trial licenses should have an expiration date');
}
if (license.type === 'enterprise' && !license.organization) {
warnings.push('Enterprise licenses should specify an organization');
}
return {
isValid: errors.length === 0,
errors,
warnings
};
}
async loadLicenseData() {
try {
const licensePath = path_1.default.join(process.cwd(), 'license.json');
if (!(await fs_extra_1.default.pathExists(licensePath))) {
return null;
}
const rawData = await fs_extra_1.default.readFile(licensePath, 'utf8');
return JSON.parse(rawData);
}
catch (error) {
utils_1.Logger.error(`Failed to load license data: ${error instanceof Error ? error.message : String(error)}`);
return null;
}
}
isJWTToken(data) {
// Check if the data contains a JWT token
if (typeof data === 'string') {
// If the entire file is just a JWT token
return this.isValidJWTFormat(data);
}
if (typeof data === 'object' && data !== null) {
// Check for common JWT token field names
const tokenFields = ['token', 'jwt', 'license_token', 'licenseToken'];
for (const field of tokenFields) {
if (data[field] && typeof data[field] === 'string' && this.isValidJWTFormat(data[field])) {
return true;
}
}
}
return false;
}
isValidJWTFormat(token) {
// JWT tokens have 3 parts separated by dots
const parts = token.split('.');
return parts.length === 3 && parts.every(part => part.length > 0);
}
async decodeJWTLicense(data) {
try {
let token;
if (typeof data === 'string') {
token = data;
}
else {
// Extract token from object
const tokenFields = ['token', 'jwt', 'license_token', 'licenseToken'];
const tokenField = tokenFields.find(field => data[field] && this.isValidJWTFormat(data[field]));
if (!tokenField) {
throw new Error('No valid JWT token found in license data');
}
token = data[tokenField];
}
// Get public key for verification
const publicKey = await this.getPublicKey();
// Verify and decode the JWT token
const decoded = jsonwebtoken_1.default.verify(token, publicKey, {
algorithms: ['RS256', 'ES256', 'PS256'] // Only allow asymmetric algorithms
});
// Extract license information from JWT payload
const license = {
id: decoded.sub || decoded.licenseId || decoded.id,
type: decoded.licenseType || decoded.type || 'trial',
expiresAt: decoded.exp ? new Date(decoded.exp * 1000).toISOString() : decoded.expiresAt,
features: decoded.features || [],
allowedComponents: decoded.allowedComponents || decoded.components || ['*'],
maxUsers: decoded.maxUsers,
organization: decoded.org || decoded.organization || decoded.iss
};
utils_1.Logger.debug('JWT license decoded successfully');
return license;
}
catch (error) {
if (error instanceof jsonwebtoken_1.default.JsonWebTokenError) {
throw new Error(`Invalid JWT token: ${error.message}`);
}
else if (error instanceof jsonwebtoken_1.default.TokenExpiredError) {
throw new Error('License token has expired');
}
else if (error instanceof jsonwebtoken_1.default.NotBeforeError) {
throw new Error('License token is not yet valid');
}
else {
throw new Error(`Failed to decode JWT license: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
async getPublicKey() {
try {
// Try multiple locations for the public key
const possiblePaths = [
path_1.default.join(process.cwd(), 'license-public-key.pem'),
path_1.default.join(process.cwd(), 'keys', 'public.pem'),
path_1.default.join(process.cwd(), '.keys', 'license-public.pem'),
path_1.default.join(process.cwd(), 'certs', 'license-public.pem')
];
for (const keyPath of possiblePaths) {
if (await fs_extra_1.default.pathExists(keyPath)) {
utils_1.Logger.debug(`Using public key from: ${keyPath}`);
return await fs_extra_1.default.readFile(keyPath, 'utf8');
}
}
// Check environment variable
if (process.env.IOCD_LICENSE_PUBLIC_KEY) {
utils_1.Logger.debug('Using public key from environment variable');
return process.env.IOCD_LICENSE_PUBLIC_KEY.replace(/\\n/g, '\n');
}
// For development/testing, allow embedded public key in license.json
const licenseData = await this.loadLicenseData();
if (licenseData && licenseData.publicKey) {
utils_1.Logger.debug('Using embedded public key from license.json');
return licenseData.publicKey;
}
throw new Error('No public key found for JWT verification');
}
catch (error) {
throw new Error(`Failed to load public key: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
exports.LicenseValidator = LicenseValidator;
//# sourceMappingURL=license-validator.js.map