mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
This change is needed so that the newly introduced floating licenses will be attempted to be claimed when n8n starts up.
256 lines
6.2 KiB
TypeScript
256 lines
6.2 KiB
TypeScript
import type { TEntitlement, TLicenseBlock } from '@n8n_io/license-sdk';
|
|
import { LicenseManager } from '@n8n_io/license-sdk';
|
|
import type { ILogger } from 'n8n-workflow';
|
|
import { getLogger } from './Logger';
|
|
import config from '@/config';
|
|
import * as Db from '@/Db';
|
|
import {
|
|
LICENSE_FEATURES,
|
|
LICENSE_QUOTAS,
|
|
N8N_VERSION,
|
|
SETTINGS_LICENSE_CERT_KEY,
|
|
UNLIMITED_LICENSE_QUOTA,
|
|
} from './constants';
|
|
import Container, { Service } from 'typedi';
|
|
import type { BooleanLicenseFeature, N8nInstanceType, NumericLicenseFeature } from './Interfaces';
|
|
import type { RedisServicePubSubPublisher } from './services/redis/RedisServicePubSubPublisher';
|
|
import { RedisService } from './services/redis.service';
|
|
|
|
type FeatureReturnType = Partial<
|
|
{
|
|
planName: string;
|
|
} & { [K in NumericLicenseFeature]: number } & { [K in BooleanLicenseFeature]: boolean }
|
|
>;
|
|
|
|
@Service()
|
|
export class License {
|
|
private logger: ILogger;
|
|
|
|
private manager: LicenseManager | undefined;
|
|
|
|
instanceId: string | undefined;
|
|
|
|
private redisPublisher: RedisServicePubSubPublisher;
|
|
|
|
constructor() {
|
|
this.logger = getLogger();
|
|
}
|
|
|
|
async init(instanceId: string, instanceType: N8nInstanceType = 'main') {
|
|
if (this.manager) {
|
|
return;
|
|
}
|
|
|
|
this.instanceId = instanceId;
|
|
const isMainInstance = instanceType === 'main';
|
|
const server = config.getEnv('license.serverUrl');
|
|
const autoRenewEnabled = isMainInstance && config.getEnv('license.autoRenewEnabled');
|
|
const offlineMode = !isMainInstance;
|
|
const autoRenewOffset = config.getEnv('license.autoRenewOffset');
|
|
const saveCertStr = isMainInstance
|
|
? async (value: TLicenseBlock) => this.saveCertStr(value)
|
|
: async () => {};
|
|
|
|
try {
|
|
this.manager = new LicenseManager({
|
|
server,
|
|
tenantId: config.getEnv('license.tenantId'),
|
|
productIdentifier: `n8n-${N8N_VERSION}`,
|
|
autoRenewEnabled,
|
|
renewOnInit: autoRenewEnabled,
|
|
autoRenewOffset,
|
|
offlineMode,
|
|
logger: this.logger,
|
|
loadCertStr: async () => this.loadCertStr(),
|
|
saveCertStr,
|
|
deviceFingerprint: () => instanceId,
|
|
});
|
|
|
|
await this.manager.initialize();
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
this.logger.error('Could not initialize license manager sdk', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
async loadCertStr(): Promise<TLicenseBlock> {
|
|
// if we have an ephemeral license, we don't want to load it from the database
|
|
const ephemeralLicense = config.get('license.cert');
|
|
if (ephemeralLicense) {
|
|
return ephemeralLicense;
|
|
}
|
|
const databaseSettings = await Db.collections.Settings.findOne({
|
|
where: {
|
|
key: SETTINGS_LICENSE_CERT_KEY,
|
|
},
|
|
});
|
|
|
|
return databaseSettings?.value ?? '';
|
|
}
|
|
|
|
async saveCertStr(value: TLicenseBlock): Promise<void> {
|
|
// if we have an ephemeral license, we don't want to save it to the database
|
|
if (config.get('license.cert')) return;
|
|
await Db.collections.Settings.upsert(
|
|
{
|
|
key: SETTINGS_LICENSE_CERT_KEY,
|
|
value,
|
|
loadOnStartup: false,
|
|
},
|
|
['key'],
|
|
);
|
|
if (config.getEnv('executions.mode') === 'queue') {
|
|
if (!this.redisPublisher) {
|
|
this.logger.debug('Initializing Redis publisher for License Service');
|
|
this.redisPublisher = await Container.get(RedisService).getPubSubPublisher();
|
|
}
|
|
await this.redisPublisher.publishToCommandChannel({
|
|
command: 'reloadLicense',
|
|
});
|
|
}
|
|
}
|
|
|
|
async activate(activationKey: string): Promise<void> {
|
|
if (!this.manager) {
|
|
return;
|
|
}
|
|
|
|
await this.manager.activate(activationKey);
|
|
}
|
|
|
|
async reload(): Promise<void> {
|
|
if (!this.manager) {
|
|
return;
|
|
}
|
|
this.logger.debug('Reloading license');
|
|
await this.manager.reload();
|
|
}
|
|
|
|
async renew() {
|
|
if (!this.manager) {
|
|
return;
|
|
}
|
|
|
|
await this.manager.renew();
|
|
}
|
|
|
|
async shutdown() {
|
|
if (!this.manager) {
|
|
return;
|
|
}
|
|
|
|
await this.manager.shutdown();
|
|
}
|
|
|
|
isFeatureEnabled(feature: BooleanLicenseFeature) {
|
|
return this.manager?.hasFeatureEnabled(feature) ?? false;
|
|
}
|
|
|
|
isSharingEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.SHARING);
|
|
}
|
|
|
|
isLogStreamingEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.LOG_STREAMING);
|
|
}
|
|
|
|
isLdapEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.LDAP);
|
|
}
|
|
|
|
isSamlEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.SAML);
|
|
}
|
|
|
|
isAdvancedExecutionFiltersEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS);
|
|
}
|
|
|
|
isDebugInEditorLicensed() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.DEBUG_IN_EDITOR);
|
|
}
|
|
|
|
isVariablesEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.VARIABLES);
|
|
}
|
|
|
|
isSourceControlLicensed() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.SOURCE_CONTROL);
|
|
}
|
|
|
|
isExternalSecretsEnabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.EXTERNAL_SECRETS);
|
|
}
|
|
|
|
isWorkflowHistoryLicensed() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.WORKFLOW_HISTORY);
|
|
}
|
|
|
|
isAPIDisabled() {
|
|
return this.isFeatureEnabled(LICENSE_FEATURES.API_DISABLED);
|
|
}
|
|
|
|
getCurrentEntitlements() {
|
|
return this.manager?.getCurrentEntitlements() ?? [];
|
|
}
|
|
|
|
getFeatureValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] {
|
|
return this.manager?.getFeatureValue(feature) as FeatureReturnType[T];
|
|
}
|
|
|
|
getManagementJwt(): string {
|
|
if (!this.manager) {
|
|
return '';
|
|
}
|
|
return this.manager.getManagementJwt();
|
|
}
|
|
|
|
/**
|
|
* Helper function to get the main plan for a license
|
|
*/
|
|
getMainPlan(): TEntitlement | undefined {
|
|
if (!this.manager) {
|
|
return undefined;
|
|
}
|
|
|
|
const entitlements = this.getCurrentEntitlements();
|
|
if (!entitlements.length) {
|
|
return undefined;
|
|
}
|
|
|
|
return entitlements.find(
|
|
(entitlement) => (entitlement.productMetadata?.terms as { isMainPlan?: boolean })?.isMainPlan,
|
|
);
|
|
}
|
|
|
|
// Helper functions for computed data
|
|
getUsersLimit() {
|
|
return this.getFeatureValue(LICENSE_QUOTAS.USERS_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
|
}
|
|
|
|
getTriggerLimit() {
|
|
return this.getFeatureValue(LICENSE_QUOTAS.TRIGGER_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
|
}
|
|
|
|
getVariablesLimit() {
|
|
return this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
|
}
|
|
|
|
getPlanName(): string {
|
|
return this.getFeatureValue('planName') ?? 'Community';
|
|
}
|
|
|
|
getInfo(): string {
|
|
if (!this.manager) {
|
|
return 'n/a';
|
|
}
|
|
|
|
return this.manager.toString();
|
|
}
|
|
|
|
isWithinUsersLimit() {
|
|
return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA;
|
|
}
|
|
}
|