diff --git a/apps/meteor/ee/app/license/server/settings.ts b/apps/meteor/ee/app/license/server/settings.ts index 1bec7126ae85e..ead088fd546de 100644 --- a/apps/meteor/ee/app/license/server/settings.ts +++ b/apps/meteor/ee/app/license/server/settings.ts @@ -1,8 +1,6 @@ -import { License } from '@rocket.chat/license'; -import { Settings } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; -import { settings, settingsRegistry } from '../../../../app/settings/server'; +import { settingsRegistry } from '../../../../app/settings/server'; Meteor.startup(async () => { await settingsRegistry.addGroup('Enterprise', async function () { @@ -11,6 +9,12 @@ Meteor.startup(async () => { type: 'string', i18nLabel: 'Enterprise_License', }); + await this.add('Enterprise_License_Data', '', { + type: 'string', + hidden: true, + blocked: true, + public: false, + }); await this.add('Enterprise_License_Status', '', { readonly: true, type: 'string', @@ -19,42 +23,3 @@ Meteor.startup(async () => { }); }); }); - -settings.watch('Enterprise_License', async (license) => { - if (!license || String(license).trim() === '') { - return; - } - - if (license === process.env.ROCKETCHAT_LICENSE) { - return; - } - - try { - if (!(await License.setLicense(license))) { - await Settings.updateValueById('Enterprise_License_Status', 'Invalid'); - return; - } - } catch (_error) { - // do nothing - } - - await Settings.updateValueById('Enterprise_License_Status', 'Valid'); -}); - -if (process.env.ROCKETCHAT_LICENSE) { - try { - await License.setLicense(process.env.ROCKETCHAT_LICENSE); - } catch (_error) { - // do nothing - } - - Meteor.startup(async () => { - if (settings.get('Enterprise_License')) { - console.warn( - 'Rocket.Chat Enterprise: The license from your environment variable was ignored, please use only the admin setting from now on.', - ); - return; - } - await Settings.updateValueById('Enterprise_License', process.env.ROCKETCHAT_LICENSE); - }); -} diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index d3523282d1e87..765a4205a1a77 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -1,6 +1,8 @@ import { License } from '@rocket.chat/license'; -import { Subscriptions, Users } from '@rocket.chat/models'; +import { Subscriptions, Users, Settings } from '@rocket.chat/models'; +import { wrapExceptions } from '@rocket.chat/tools'; +import { syncWorkspace } from '../../../../app/cloud/server/functions/syncWorkspace'; import { settings } from '../../../../app/settings/server'; import { callbacks } from '../../../../lib/callbacks'; import { getAppCount } from './lib/getAppCount'; @@ -11,12 +13,87 @@ settings.watch('Site_Url', (value) => { } }); -callbacks.add('workspaceLicenseChanged', async (updatedLicense) => { +License.onValidateLicense(async () => { + await Settings.updateValueById('Enterprise_License', License.encryptedLicense); + await Settings.updateValueById('Enterprise_License_Status', 'Valid'); +}); + +License.onInvalidateLicense(async () => { + await Settings.updateValueById('Enterprise_License_Status', 'Invalid'); +}); + +const applyLicense = async (license: string, isNewLicense: boolean): Promise => { + const enterpriseLicense = (license ?? '').trim(); + if (!enterpriseLicense) { + return false; + } + + if (enterpriseLicense === License.encryptedLicense) { + return false; + } + try { - await License.setLicense(updatedLicense); - } catch (_error) { - // Ignore + return License.setLicense(enterpriseLicense, isNewLicense); + } catch { + return false; + } +}; + +const syncByTrigger = async (context: string) => { + if (!License.encryptedLicense) { + return; } + + const existingData = wrapExceptions(() => JSON.parse(settings.get('Enterprise_License_Data'))).catch(() => ({})) ?? {}; + + const date = new Date(); + + const day = date.getDate(); + const month = date.getMonth() + 1; + const year = date.getFullYear(); + + const period = `${year}-${month}-${day}`; + + const [, , signed] = License.encryptedLicense.split('.'); + + // Check if this sync has already been done. Based on License, behavior. + if (existingData.signed === signed && existingData[context] === period) { + return; + } + + await Settings.updateValueById( + 'Enterprise_License_Data', + JSON.stringify({ + ...(existingData.signed === signed && existingData), + ...existingData, + [context]: period, + signed, + }), + ); + + await syncWorkspace(); +}; + +// When settings are loaded, apply the current license if there is one. +settings.onReady(async () => { + if (!(await applyLicense(settings.get('Enterprise_License') ?? '', false))) { + // License from the envvar is always treated as new, because it would have been saved on the setting if it was already in use. + if (process.env.ROCKETCHAT_LICENSE && !License.hasValidLicense()) { + await applyLicense(process.env.ROCKETCHAT_LICENSE, true); + } + } + + // After the current license is already loaded, watch the setting value to react to new licenses being applied. + settings.watch('Enterprise_License', async (license) => applyLicense(license, true)); + + callbacks.add('workspaceLicenseChanged', async (updatedLicense) => applyLicense(updatedLicense, true)); + + License.onBehaviorTriggered('prevent_action', (context) => syncByTrigger(`prevent_action_${context.limit}`)); + + License.onBehaviorTriggered('start_fair_policy', async (context) => syncByTrigger(`start_fair_policy_${context.limit}`)); + + License.onBehaviorTriggered('disable_modules', async (context) => syncByTrigger(`disable_modules_${context.limit}`)); + }); License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount()); diff --git a/ee/packages/license/src/license.ts b/ee/packages/license/src/license.ts index f2d8eef362bdc..8449d4136810b 100644 --- a/ee/packages/license/src/license.ts +++ b/ee/packages/license/src/license.ts @@ -66,6 +66,14 @@ export class LicenseManager extends Emitter { return this._valid; } + public get encryptedLicense(): string | undefined { + if (!this.hasValidLicense()) { + return undefined; + } + + return this._lockedLicense; + } + public async setWorkspaceUrl(url: string) { this.workspaceUrl = url.replace(/\/$/, '').replace(/^https?:\/\/(.*)$/, '$1'); @@ -106,7 +114,12 @@ export class LicenseManager extends Emitter { invalidateAll.call(this); } - private async setLicenseV3(newLicense: ILicenseV3, encryptedLicense: string, originalLicense?: ILicenseV2 | ILicenseV3): Promise { + private async setLicenseV3( + newLicense: ILicenseV3, + encryptedLicense: string, + originalLicense?: ILicenseV2 | ILicenseV3, + isNewLicense?: boolean, + ): Promise { const hadValidLicense = this.hasValidLicense(); this.clearLicenseData(); @@ -114,7 +127,6 @@ export class LicenseManager extends Emitter { this._unmodifiedLicense = originalLicense || newLicense; this._license = newLicense; - const isNewLicense = encryptedLicense !== this._lockedLicense; this._lockedLicense = encryptedLicense; await this.validateLicense({ isNewLicense }); @@ -127,8 +139,8 @@ export class LicenseManager extends Emitter { } } - private async setLicenseV2(newLicense: ILicenseV2, encryptedLicense: string): Promise { - return this.setLicenseV3(convertToV3(newLicense), encryptedLicense, newLicense); + private async setLicenseV2(newLicense: ILicenseV2, encryptedLicense: string, isNewLicense?: boolean): Promise { + return this.setLicenseV3(convertToV3(newLicense), encryptedLicense, newLicense, isNewLicense); } private isLicenseDuplicated(encryptedLicense: string): boolean { @@ -180,7 +192,7 @@ export class LicenseManager extends Emitter { licenseValidated.call(this); } - public async setLicense(encryptedLicense: string): Promise { + public async setLicense(encryptedLicense: string, isNewLicense = true): Promise { if (!(await validateFormat(encryptedLicense))) { throw new InvalidLicenseError(); } @@ -209,10 +221,10 @@ export class LicenseManager extends Emitter { logger.debug({ msg: 'license', decrypted }); if (!encryptedLicense.startsWith('RCV3_')) { - await this.setLicenseV2(decrypted, encryptedLicense); + await this.setLicenseV2(decrypted, encryptedLicense, isNewLicense); return true; } - await this.setLicenseV3(decrypted, encryptedLicense); + await this.setLicenseV3(decrypted, encryptedLicense, decrypted, isNewLicense); return true; } catch (e) {