2024-10-17 01:47:17 -07:00
import { GlobalConfig } from '@n8n/config' ;
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' ;
2023-10-25 07:35:22 -07:00
import { InstanceSettings , ObjectStoreService } from 'n8n-core' ;
import Container , { Service } from 'typedi' ;
2024-09-12 09:07:18 -07:00
2022-11-21 06:41:24 -08:00
import config from '@/config' ;
2024-09-12 09:07:18 -07:00
import { SettingsRepository } from '@/databases/repositories/settings.repository' ;
import { OnShutdown } from '@/decorators/on-shutdown' ;
2024-10-01 03:16:09 -07:00
import { Logger } from '@/logging/logger.service' ;
2024-09-12 09:07:18 -07:00
import { LicenseMetricsService } from '@/metrics/license-metrics.service' ;
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' ;
2024-09-16 04:37:14 -07:00
import type { BooleanLicenseFeature , NumericLicenseFeature } from './interfaces' ;
2023-07-12 05:11:46 -07:00
2024-09-17 05:10:22 -07:00
export type FeatureReturnType = Partial <
2023-07-12 05:11:46 -07:00
{
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 manager : LicenseManager | undefined ;
2023-12-22 02:39:58 -08:00
private isShuttingDown = false ;
2023-10-25 07:35:22 -07:00
constructor (
private readonly logger : Logger ,
private readonly instanceSettings : InstanceSettings ,
2023-11-10 06:04:26 -08:00
private readonly settingsRepository : SettingsRepository ,
2024-07-15 03:42:49 -07:00
private readonly licenseMetricsService : LicenseMetricsService ,
2024-10-17 01:47:17 -07:00
private readonly globalConfig : GlobalConfig ,
2024-10-09 03:56:31 -07:00
) {
2024-10-22 08:20:14 -07:00
this . logger = this . logger . scoped ( 'license' ) ;
2024-10-09 03:56:31 -07:00
}
2022-11-21 06:41:24 -08:00
2024-05-06 00:04:16 -07:00
/ * *
* Whether this instance should renew the license - on init and periodically .
* /
2024-09-16 04:37:14 -07:00
private renewalEnabled() {
if ( this . instanceSettings . instanceType !== 'main' ) return false ;
2024-10-28 02:52:31 -07:00
const autoRenewEnabled = this . globalConfig . license . autoRenewalEnabled ;
2024-05-06 00:04:16 -07:00
/ * *
* In multi - main setup , all mains start off with ` unset ` status and so renewal disabled .
* On becoming leader or follower , each will enable or disable renewal , respectively .
* This ensures the mains do not cause a 429 ( too many requests ) on license init .
* /
2024-10-17 01:47:17 -07:00
if ( this . globalConfig . multiMainSetup . enabled ) {
2024-08-02 06:18:33 -07:00
return autoRenewEnabled && this . instanceSettings . isLeader ;
2024-05-06 00:04:16 -07:00
}
return autoRenewEnabled ;
}
2024-09-16 04:37:14 -07:00
async init ( forceRecreate = false ) {
2024-05-20 05:02:08 -07:00
if ( this . manager && ! forceRecreate ) {
2023-12-22 02:39:58 -08:00
this . logger . warn ( 'License manager already initialized or shutting down' ) ;
return ;
}
if ( this . isShuttingDown ) {
this . logger . warn ( 'License manager already shutting down' ) ;
2022-11-21 06:41:24 -08:00
return ;
}
2024-09-16 04:37:14 -07:00
const { instanceType } = this . instanceSettings ;
2023-09-17 02:05:54 -07:00
const isMainInstance = instanceType === 'main' ;
2024-10-28 02:52:31 -07:00
const server = this . globalConfig . license . serverUrl ;
2023-09-17 02:05:54 -07:00
const offlineMode = ! isMainInstance ;
2024-10-28 02:52:31 -07:00
const autoRenewOffset = this . globalConfig . license . autoRenewOffset ;
2023-09-17 02:05:54 -07:00
const saveCertStr = isMainInstance
2024-01-17 07:08:50 -08:00
? async ( value : TLicenseBlock ) = > await this . saveCertStr ( value )
2023-09-17 02:05:54 -07:00
: async ( ) = > { } ;
2023-09-26 04:58:06 -07:00
const onFeatureChange = isMainInstance
2024-01-17 07:08:50 -08:00
? async ( features : TFeatures ) = > await this . onFeatureChange ( features )
2023-09-26 04:58:06 -07:00
: async ( ) = > { } ;
2023-10-23 12:39:29 -07:00
const collectUsageMetrics = isMainInstance
2024-07-15 03:42:49 -07:00
? async ( ) = > await this . licenseMetricsService . collectUsageMetrics ( )
2023-10-23 12:39:29 -07:00
: async ( ) = > [ ] ;
2024-06-19 03:35:42 -07:00
const collectPassthroughData = isMainInstance
2024-07-15 03:42:49 -07:00
? async ( ) = > await this . licenseMetricsService . collectPassthroughData ( )
2024-06-19 03:35:42 -07:00
: async ( ) = > ( { } ) ;
2022-11-21 06:41:24 -08:00
2024-09-16 04:37:14 -07:00
const renewalEnabled = this . renewalEnabled ( ) ;
2024-05-06 00:04:16 -07:00
2022-11-21 06:41:24 -08:00
try {
this . manager = new LicenseManager ( {
server ,
2024-10-28 02:52:31 -07:00
tenantId : this.globalConfig.license.tenantId ,
2023-01-04 02:38:48 -08:00
productIdentifier : ` n8n- ${ N8N_VERSION } ` ,
2024-05-06 00:04:16 -07:00
autoRenewEnabled : renewalEnabled ,
renewOnInit : renewalEnabled ,
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 ,
2024-01-17 07:08:50 -08:00
loadCertStr : async ( ) = > await this . loadCertStr ( ) ,
2023-09-17 02:05:54 -07:00
saveCertStr ,
2023-10-23 04:39:35 -07:00
deviceFingerprint : ( ) = > this . instanceSettings . instanceId ,
2023-10-23 12:39:29 -07:00
collectUsageMetrics ,
2024-06-19 03:35:42 -07:00
collectPassthroughData ,
2023-09-26 04:58:06 -07:00
onFeatureChange ,
2022-11-21 06:41:24 -08:00
} ) ;
await this . manager . initialize ( ) ;
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License initialized' ) ;
2024-10-09 03:56:31 -07:00
} catch ( error : unknown ) {
if ( error instanceof Error ) {
this . logger . error ( 'Could not initialize license manager sdk' , { error } ) ;
2022-11-21 06:41:24 -08:00
}
}
}
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
2024-10-28 02:52:31 -07:00
const ephemeralLicense = this . globalConfig . license . cert ;
2023-04-21 08:10:10 -07:00
if ( ephemeralLicense ) {
return ephemeralLicense ;
}
2023-11-10 06:04:26 -08:00
const databaseSettings = await this . settingsRepository . findOne ( {
2023-04-21 08:10:10 -07:00
where : {
key : SETTINGS_LICENSE_CERT_KEY ,
} ,
} ) ;
return databaseSettings ? . value ? ? '' ;
}
2023-09-26 04:58:06 -07:00
async onFeatureChange ( _features : TFeatures ) : Promise < void > {
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License feature change detected' , _features ) ;
2024-10-17 01:47:17 -07:00
if ( config . getEnv ( 'executions.mode' ) === 'queue' && this . globalConfig . multiMainSetup . enabled ) {
2024-12-11 09:25:22 -08:00
const isMultiMainLicensed =
( _features [ LICENSE_FEATURES . MULTIPLE_MAIN_INSTANCES ] as boolean | undefined ) ? ? false ;
2023-11-02 06:16:22 -07:00
2024-12-11 09:25:22 -08:00
this . instanceSettings . setMultiMainLicensed ( isMultiMainLicensed ) ;
2023-11-06 03:03:35 -08:00
2024-12-11 09:25:22 -08:00
if ( this . instanceSettings . isMultiMain && ! this . instanceSettings . isLeader ) {
this . logger
. scoped ( [ 'scaling' , 'multi-main-setup' , 'license' ] )
. debug ( 'Instance is not leader, skipping sending of "reload-license" command...' ) ;
2023-11-17 06:58:50 -08:00
return ;
}
2023-11-06 03:03:35 -08:00
2024-12-11 09:25:22 -08:00
if ( this . globalConfig . multiMainSetup . enabled && ! isMultiMainLicensed ) {
this . logger
. scoped ( [ 'scaling' , 'multi-main-setup' , 'license' ] )
. debug (
'License changed with no support for multi-main setup - no new followers will be allowed to init. To restore multi-main setup, please upgrade to a license that supports this feature.' ,
) ;
2023-11-02 06:16:22 -07:00
}
2023-11-17 06:58:50 -08:00
}
2023-11-02 06:16:22 -07:00
2023-11-17 06:58:50 -08:00
if ( config . getEnv ( 'executions.mode' ) === 'queue' ) {
2024-09-17 06:45:42 -07:00
const { Publisher } = await import ( '@/scaling/pubsub/publisher.service' ) ;
2024-09-27 03:35:01 -07:00
await Container . get ( Publisher ) . publishCommand ( { command : 'reload-license' } ) ;
2023-09-26 04:58:06 -07:00
}
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
2024-10-28 02:52:31 -07:00
if ( this . globalConfig . license . cert ) return ;
2023-11-10 06:04:26 -08:00
await this . settingsRepository . upsert (
2023-04-21 08:10:10 -07:00
{
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 ) ;
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License activated' ) ;
2022-11-21 06:41:24 -08:00
}
2023-09-17 02:05:54 -07:00
async reload ( ) : Promise < void > {
if ( ! this . manager ) {
return ;
}
await this . manager . reload ( ) ;
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License reloaded' ) ;
2023-09-17 02:05:54 -07:00
}
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 ( ) ;
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License renewed' ) ;
2022-11-21 06:41:24 -08:00
}
2023-12-22 02:39:58 -08:00
@OnShutdown ( )
2023-09-04 06:56:20 -07:00
async shutdown() {
2023-12-22 02:39:58 -08:00
// Shut down License manager to unclaim any floating entitlements
// Note: While this saves a new license cert to DB, the previous entitlements are still kept in memory so that the shutdown process can complete
this . isShuttingDown = true ;
2023-09-04 06:56:20 -07:00
if ( ! this . manager ) {
return ;
}
await this . manager . shutdown ( ) ;
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License shut down' ) ;
2023-09-04 06:56:20 -07:00
}
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 ) ;
}
2024-08-14 05:59:11 -07:00
isAiAssistantEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . AI_ASSISTANT ) ;
}
2024-10-09 08:24:33 -07:00
isAskAiEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . ASK_AI ) ;
}
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-11-28 05:16:47 -08:00
isAdvancedPermissionsLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . ADVANCED_PERMISSIONS ) ;
}
2023-08-09 07:38:17 -07:00
isDebugInEditorLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . DEBUG_IN_EDITOR ) ;
}
2023-10-13 04:16:43 -07:00
isBinaryDataS3Licensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . BINARY_DATA_S3 ) ;
}
2023-10-30 08:22:32 -07:00
isMultipleMainInstancesLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . MULTIPLE_MAIN_INSTANCES ) ;
}
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 ) ;
}
2023-11-10 14:48:31 -08:00
isWorkerViewLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . WORKER_VIEW ) ;
}
2024-05-17 01:53:15 -07:00
isProjectRoleAdminLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . PROJECT_ROLE_ADMIN ) ;
}
isProjectRoleEditorLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . PROJECT_ROLE_EDITOR ) ;
}
isProjectRoleViewerLicensed() {
return this . isFeatureEnabled ( LICENSE_FEATURES . PROJECT_ROLE_VIEWER ) ;
}
2024-08-14 00:44:19 -07:00
isCustomNpmRegistryEnabled() {
return this . isFeatureEnabled ( LICENSE_FEATURES . COMMUNITY_NODES_CUSTOM_REGISTRY ) ;
}
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
) ;
}
2024-07-03 00:38:21 -07:00
getConsumerId() {
return this . manager ? . getConsumerId ( ) ? ? 'unknown' ;
}
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
) ;
}
2024-05-17 01:53:15 -07:00
getTeamProjectLimit() {
return this . getFeatureValue ( LICENSE_QUOTAS . TEAM_PROJECT_LIMIT ) ? ? 0 ;
}
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 ;
}
2024-05-06 00:04:16 -07:00
async reinit() {
this . manager ? . reset ( ) ;
2024-09-16 04:37:14 -07:00
await this . init ( true ) ;
2024-09-03 02:51:29 -07:00
this . logger . debug ( 'License reinitialized' ) ;
2024-05-06 00:04:16 -07:00
}
2022-11-21 06:41:24 -08:00
}