2023-09-26 04:58:06 -07:00
import type { TEntitlement , TFeatures , TLicenseBlock } from '@n8n_io/license-sdk' ;
2023-01-27 05:56:56 -08:00
import { LicenseManager } from '@n8n_io/license-sdk' ;
import type { ILogger } from 'n8n-workflow' ;
2022-11-21 06:41:24 -08:00
import { getLogger } from './Logger' ;
import config from '@/config' ;
import * as Db from '@/Db' ;
2023-04-18 03:41:55 -07:00
import {
LICENSE_FEATURES ,
LICENSE_QUOTAS ,
N8N_VERSION ,
SETTINGS_LICENSE_CERT_KEY ,
2023-07-12 05:11:46 -07:00
UNLIMITED_LICENSE_QUOTA ,
2023-04-18 03:41:55 -07:00
} from './constants' ;
2023-09-17 02:05:54 -07:00
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' ;
2023-10-05 06:25:17 -07:00
import { ObjectStoreService } from 'n8n-core' ;
2023-07-12 05:11:46 -07:00
type FeatureReturnType = Partial <
{
planName : string ;
} & { [ K in NumericLicenseFeature ] : number } & { [ K in BooleanLicenseFeature ] : boolean }
> ;
2022-11-21 06:41:24 -08:00
2023-03-16 07:34:13 -07:00
@Service ( )
2022-11-21 06:41:24 -08:00
export class License {
private logger : ILogger ;
private manager : LicenseManager | undefined ;
2023-09-17 02:05:54 -07:00
instanceId : string | undefined ;
private redisPublisher : RedisServicePubSubPublisher ;
2022-11-21 06:41:24 -08:00
constructor ( ) {
this . logger = getLogger ( ) ;
}
2023-09-17 02:05:54 -07:00
async init ( instanceId : string , instanceType : N8nInstanceType = 'main' ) {
2022-11-21 06:41:24 -08:00
if ( this . manager ) {
return ;
}
2023-09-17 02:05:54 -07:00
this . instanceId = instanceId ;
const isMainInstance = instanceType === 'main' ;
2022-11-21 06:41:24 -08:00
const server = config . getEnv ( 'license.serverUrl' ) ;
2023-09-17 02:05:54 -07:00
const autoRenewEnabled = isMainInstance && config . getEnv ( 'license.autoRenewEnabled' ) ;
const offlineMode = ! isMainInstance ;
2022-11-21 06:41:24 -08:00
const autoRenewOffset = config . getEnv ( 'license.autoRenewOffset' ) ;
2023-09-17 02:05:54 -07:00
const saveCertStr = isMainInstance
? async ( value : TLicenseBlock ) = > this . saveCertStr ( value )
: async ( ) = > { } ;
2023-09-26 04:58:06 -07:00
const onFeatureChange = isMainInstance
? async ( features : TFeatures ) = > this . onFeatureChange ( features )
: async ( ) = > { } ;
2022-11-21 06:41:24 -08:00
try {
this . manager = new LicenseManager ( {
server ,
2022-11-28 08:39:34 -08:00
tenantId : config.getEnv ( 'license.tenantId' ) ,
2023-01-04 02:38:48 -08:00
productIdentifier : ` n8n- ${ N8N_VERSION } ` ,
2022-11-21 06:41:24 -08:00
autoRenewEnabled ,
2023-09-19 03:10:23 -07:00
renewOnInit : autoRenewEnabled ,
2022-11-21 06:41:24 -08:00
autoRenewOffset ,
2023-09-17 02:05:54 -07:00
offlineMode ,
2022-11-21 06:41:24 -08:00
logger : this.logger ,
2023-04-21 08:10:10 -07:00
loadCertStr : async ( ) = > this . loadCertStr ( ) ,
2023-09-17 02:05:54 -07:00
saveCertStr ,
2022-11-21 06:41:24 -08:00
deviceFingerprint : ( ) = > instanceId ,
2023-09-26 04:58:06 -07:00
onFeatureChange ,
2022-11-21 06:41:24 -08:00
} ) ;
await this . manager . initialize ( ) ;
} catch ( e : unknown ) {
if ( e instanceof Error ) {
this . logger . error ( 'Could not initialize license manager sdk' , e ) ;
}
}
}
2023-04-21 08:10:10 -07:00
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 ? ? '' ;
}
2023-09-26 04:58:06 -07:00
async onFeatureChange ( _features : TFeatures ) : Promise < void > {
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' ,
} ) ;
}
2023-10-05 06:25:17 -07:00
const isS3Selected = config . getEnv ( 'binaryDataManager.mode' ) === 's3' ;
const isS3Available = config . getEnv ( 'binaryDataManager.availableModes' ) . includes ( 's3' ) ;
const isS3Licensed = _features [ 'feat:binaryDataS3' ] ;
if ( isS3Selected && isS3Available && ! isS3Licensed ) {
this . logger . debug (
'License changed with no support for external storage - blocking writes on object store. To restore writes, please upgrade to a license that supports this feature.' ,
) ;
Container . get ( ObjectStoreService ) . setReadonly ( true ) ;
}
2023-09-26 04:58:06 -07:00
}
2023-04-21 08:10:10 -07:00
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' ] ,
) ;
}
2022-11-21 06:41:24 -08:00
async activate ( activationKey : string ) : Promise < void > {
if ( ! this . manager ) {
return ;
}
2022-12-20 01:52:01 -08:00
await this . manager . activate ( activationKey ) ;
2022-11-21 06:41:24 -08:00
}
2023-09-17 02:05:54 -07:00
async reload ( ) : Promise < void > {
if ( ! this . manager ) {
return ;
}
this . logger . debug ( 'Reloading license' ) ;
await this . manager . reload ( ) ;
}
2022-11-21 06:41:24 -08:00
async renew() {
if ( ! this . manager ) {
return ;
}
2022-12-20 01:52:01 -08:00
await this . manager . renew ( ) ;
2022-11-21 06:41:24 -08:00
}
2023-09-04 06:56:20 -07:00
async shutdown() {
if ( ! this . manager ) {
return ;
}
await this . manager . shutdown ( ) ;
}
2023-07-12 05:11:46 -07:00
isFeatureEnabled ( feature : BooleanLicenseFeature ) {
return this . manager ? . hasFeatureEnabled ( feature ) ? ? false ;
2022-11-21 06:41:24 -08:00
}
isSharingEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . SHARING ) ;
}
2022-12-20 01:52:01 -08:00
2023-01-04 00:47:48 -08:00
isLogStreamingEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . LOG_STREAMING ) ;
}
2023-01-24 17:18:39 -08:00
isLdapEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . LDAP ) ;
}
2023-02-16 06:05:39 -08:00
isSamlEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . SAML ) ;
}
2023-03-07 09:35:52 -08:00
isAdvancedExecutionFiltersEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . ADVANCED_EXECUTION_FILTERS ) ;
2023-03-07 05:18:10 -08:00
}
2023-08-09 07:38:17 -07:00
isDebugInEditorLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . DEBUG_IN_EDITOR ) ;
}
2023-04-18 03:41:55 -07:00
isVariablesEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . VARIABLES ) ;
}
2023-06-20 10:13:18 -07:00
isSourceControlLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . SOURCE_CONTROL ) ;
2023-04-18 04:29:26 -07:00
}
2023-08-25 01:33:46 -07:00
isExternalSecretsEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . EXTERNAL_SECRETS ) ;
}
2023-08-04 03:27:06 -07:00
isWorkflowHistoryLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . WORKFLOW_HISTORY ) ;
}
2023-05-15 14:16:13 -07:00
isAPIDisabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . API_DISABLED ) ;
}
2022-12-20 01:52:01 -08:00
getCurrentEntitlements() {
return this . manager ? . getCurrentEntitlements ( ) ? ? [ ] ;
}
2023-07-12 05:11:46 -07:00
getFeatureValue < T extends keyof FeatureReturnType > ( feature : T ) : FeatureReturnType [ T ] {
return this . manager ? . getFeatureValue ( feature ) as FeatureReturnType [ T ] ;
2022-12-20 01:52:01 -08:00
}
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 (
2023-05-03 01:43:13 -07:00
( entitlement ) = > ( entitlement . productMetadata ? . terms as { isMainPlan? : boolean } ) ? . isMainPlan ,
2022-12-20 01:52:01 -08:00
) ;
}
// Helper functions for computed data
2023-07-12 05:11:46 -07:00
getUsersLimit() {
return this . getFeatureValue ( LICENSE_QUOTAS . USERS_LIMIT ) ? ? UNLIMITED_LICENSE_QUOTA ;
2023-04-18 03:41:55 -07:00
}
2023-07-12 05:11:46 -07:00
getTriggerLimit() {
return this . getFeatureValue ( LICENSE_QUOTAS . TRIGGER_LIMIT ) ? ? UNLIMITED_LICENSE_QUOTA ;
2022-12-20 01:52:01 -08:00
}
2023-07-12 05:11:46 -07:00
getVariablesLimit() {
return this . getFeatureValue ( LICENSE_QUOTAS . VARIABLES_LIMIT ) ? ? UNLIMITED_LICENSE_QUOTA ;
2023-06-21 04:22:00 -07:00
}
2023-10-04 05:57:21 -07:00
getWorkflowHistoryPruneLimit() {
return (
this . getFeatureValue ( LICENSE_QUOTAS . WORKFLOW_HISTORY_PRUNE_LIMIT ) ? ? UNLIMITED_LICENSE_QUOTA
) ;
}
2022-12-20 01:52:01 -08:00
getPlanName ( ) : string {
2023-07-12 05:11:46 -07:00
return this . getFeatureValue ( 'planName' ) ? ? 'Community' ;
2022-12-20 01:52:01 -08:00
}
2023-04-21 08:10:10 -07:00
getInfo ( ) : string {
if ( ! this . manager ) {
return 'n/a' ;
}
return this . manager . toString ( ) ;
}
2023-07-12 05:11:46 -07:00
isWithinUsersLimit() {
return this . getUsersLimit ( ) === UNLIMITED_LICENSE_QUOTA ;
}
2022-11-21 06:41:24 -08:00
}