202 lines
5.8 KiB
TypeScript
202 lines
5.8 KiB
TypeScript
import * as crypto from 'crypto';
|
|
import { signData } from './crypto';
|
|
import type { LicenseType } from './types';
|
|
import {
|
|
canLicenseUseModule,
|
|
getDefaultModuleGrantSpecs,
|
|
getModuleDefinition,
|
|
getModuleLimits,
|
|
getPremiumModuleArtifact,
|
|
type PremiumModuleArtifact,
|
|
} from './premium-module-catalog';
|
|
|
|
export const PREMIUM_MODULE_MANIFEST_VERSION = 1;
|
|
export const PREMIUM_MODULE_ENTITLEMENT_VERSION = 1;
|
|
|
|
export interface PremiumModuleManifest {
|
|
version: number;
|
|
licenseType: LicenseType;
|
|
instanceId: string;
|
|
modules: Array<{
|
|
id: string;
|
|
minLicenseType: LicenseType;
|
|
artifact: PremiumModuleArtifact;
|
|
sizeBytes: number;
|
|
sha256: string;
|
|
entitlementVersion: number;
|
|
limits?: {
|
|
maxUsers: number;
|
|
maxMaterials: number;
|
|
};
|
|
}>;
|
|
issuedAt: string;
|
|
expiresAt: string;
|
|
}
|
|
|
|
export interface SignedPremiumModuleManifest {
|
|
manifest: PremiumModuleManifest;
|
|
signature: string;
|
|
}
|
|
|
|
export interface PremiumModuleEntitlement {
|
|
moduleId: string;
|
|
entitlementBase64: string;
|
|
sha256: string;
|
|
sizeBytes: number;
|
|
expiresAt: string;
|
|
artifact: PremiumModuleArtifact;
|
|
}
|
|
|
|
export interface AuthorizedPremiumModuleGrant {
|
|
moduleId: string;
|
|
artifact: PremiumModuleArtifact;
|
|
limits?: {
|
|
maxUsers: number;
|
|
maxMaterials: number;
|
|
};
|
|
}
|
|
|
|
function getEntitlementSecret(): string {
|
|
const secret = process.env.PREMIUM_MODULE_PACKAGE_SECRET || process.env.AES_SECRET_KEY || '';
|
|
if (!secret || secret.length < 32) {
|
|
throw new Error('PREMIUM_MODULE_PACKAGE_SECRET 或 AES_SECRET_KEY 未配置或长度不足');
|
|
}
|
|
|
|
return secret;
|
|
}
|
|
|
|
function encryptModuleEntitlement(payload: Record<string, unknown>): string {
|
|
const plaintext = JSON.stringify(payload);
|
|
const packageSecret = getEntitlementSecret();
|
|
const key = crypto.createHash('sha256').update(packageSecret).digest();
|
|
const iv = crypto
|
|
.createHash('sha256')
|
|
.update(`${packageSecret}:${plaintext}`)
|
|
.digest()
|
|
.subarray(0, 12);
|
|
|
|
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
|
|
encrypted += cipher.final('base64');
|
|
|
|
const envelope = {
|
|
version: PREMIUM_MODULE_ENTITLEMENT_VERSION,
|
|
algorithm: 'aes-256-gcm',
|
|
iv: iv.toString('base64'),
|
|
authTag: cipher.getAuthTag().toString('base64'),
|
|
encrypted,
|
|
};
|
|
|
|
return Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64');
|
|
}
|
|
|
|
export function buildPremiumModuleEntitlement(params: {
|
|
moduleId: string;
|
|
licenseType: LicenseType;
|
|
instanceId: string;
|
|
leaseExpiresAt: Date;
|
|
artifact?: PremiumModuleArtifact;
|
|
limits?: {
|
|
maxUsers: number;
|
|
maxMaterials: number;
|
|
};
|
|
authorizedByGrant?: boolean;
|
|
}): PremiumModuleEntitlement {
|
|
const module = getModuleDefinition(params.moduleId);
|
|
if (!module || (!params.authorizedByGrant && !canLicenseUseModule(params.licenseType, module))) {
|
|
throw new Error('授权模块不存在或许可证类型不允许使用');
|
|
}
|
|
|
|
const expiresAt = params.leaseExpiresAt.toISOString();
|
|
const artifact = params.artifact || getPremiumModuleArtifact(module.id);
|
|
const entitlementBase64 = encryptModuleEntitlement({
|
|
payloadType: 'flnxi-premium-module-entitlement',
|
|
entitlementVersion: PREMIUM_MODULE_ENTITLEMENT_VERSION,
|
|
moduleId: module.id,
|
|
licenseType: params.licenseType,
|
|
minLicenseType: module.minLicenseType,
|
|
capabilities: module.capabilities,
|
|
limits: params.limits ?? getModuleLimits(module.id, params.licenseType),
|
|
instanceId: params.instanceId,
|
|
artifact,
|
|
expiresAt,
|
|
});
|
|
|
|
return {
|
|
moduleId: module.id,
|
|
entitlementBase64,
|
|
sha256: crypto.createHash('sha256').update(entitlementBase64).digest('hex'),
|
|
sizeBytes: Buffer.byteLength(entitlementBase64, 'utf8'),
|
|
expiresAt,
|
|
artifact,
|
|
};
|
|
}
|
|
|
|
function buildModuleManifestItems(params: {
|
|
licenseType: LicenseType;
|
|
instanceId: string;
|
|
leaseExpiresAt: Date;
|
|
grants?: AuthorizedPremiumModuleGrant[];
|
|
}): PremiumModuleManifest['modules'] {
|
|
const grants = params.grants || getDefaultModuleGrantSpecs(params.licenseType);
|
|
return grants.map((grant) => {
|
|
const module = getModuleDefinition(grant.moduleId);
|
|
if (!module) {
|
|
throw new Error(`授权模块不存在:${grant.moduleId}`);
|
|
}
|
|
|
|
const entitlement = buildPremiumModuleEntitlement({
|
|
moduleId: grant.moduleId,
|
|
licenseType: params.licenseType,
|
|
instanceId: params.instanceId,
|
|
leaseExpiresAt: params.leaseExpiresAt,
|
|
artifact: grant.artifact,
|
|
limits: grant.limits,
|
|
authorizedByGrant: true,
|
|
});
|
|
|
|
return {
|
|
id: module.id,
|
|
minLicenseType: module.minLicenseType,
|
|
artifact: entitlement.artifact,
|
|
sizeBytes: entitlement.sizeBytes,
|
|
sha256: entitlement.sha256,
|
|
entitlementVersion: PREMIUM_MODULE_ENTITLEMENT_VERSION,
|
|
limits: grant.limits,
|
|
};
|
|
});
|
|
}
|
|
|
|
export function buildSignedPremiumModuleManifestForGrants(
|
|
licenseType: LicenseType,
|
|
leaseExpiresAt: Date,
|
|
instanceId: string,
|
|
grants?: AuthorizedPremiumModuleGrant[]
|
|
): SignedPremiumModuleManifest {
|
|
const manifest: PremiumModuleManifest = {
|
|
version: PREMIUM_MODULE_MANIFEST_VERSION,
|
|
licenseType,
|
|
instanceId,
|
|
modules: buildModuleManifestItems({ licenseType, instanceId, leaseExpiresAt, grants }),
|
|
issuedAt: new Date().toISOString(),
|
|
expiresAt: leaseExpiresAt.toISOString(),
|
|
};
|
|
|
|
return {
|
|
manifest,
|
|
signature: signData(JSON.stringify(manifest)),
|
|
};
|
|
}
|
|
|
|
export function buildSignedPremiumModuleManifest(
|
|
licenseType: LicenseType,
|
|
leaseExpiresAt: Date,
|
|
instanceId: string
|
|
): SignedPremiumModuleManifest {
|
|
return buildSignedPremiumModuleManifestForGrants(licenseType, leaseExpiresAt, instanceId);
|
|
}
|
|
|
|
// Backward-compatible export name for older internal tests/imports. This now
|
|
// returns an entitlement token, not the paid module core artifact itself.
|
|
export const buildPremiumModulePackage = buildPremiumModuleEntitlement;
|