Skip to content

Commit

Permalink
chore: License v3 - behavior and limited reached triggers (#30561)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Gazzo <[email protected]>
  • Loading branch information
pierre-lehnen-rc and ggazzo authored Oct 6, 2023
1 parent 6d46183 commit e666487
Show file tree
Hide file tree
Showing 23 changed files with 470 additions and 118 deletions.
1 change: 0 additions & 1 deletion ee/packages/license/__tests__/MockedLicenseBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ export class MockedLicenseBuilder {
export const getReadyLicenseManager = async () => {
const license = new LicenseImp();
await license.setWorkspaceUrl('http://localhost:3000');
await license.setWorkspaceUrl('http://localhost:3000');

license.setLicenseLimitCounter('activeUsers', () => 0);
license.setLicenseLimitCounter('guestUsers', () => 0);
Expand Down
53 changes: 53 additions & 0 deletions ee/packages/license/__tests__/emitter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,57 @@ describe('Event License behaviors', () => {
await expect(license.hasValidLicense()).toBe(true);
await expect(fn).toBeCalledTimes(1);
});

describe('behavior:prevent_action event', () => {
it('should emit `behavior:prevent_action` event when the limit is reached', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();

licenseManager.onBehaviorTriggered('prevent_action', fn);

const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);

await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);

licenseManager.setLicenseLimitCounter('activeUsers', () => 10);

await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);

await expect(fn).toBeCalledTimes(1);

await expect(fn).toBeCalledWith({
reason: 'limit',
limit: 'activeUsers',
});
});

it('should emit `limitReached:activeUsers` event when the limit is reached', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();

licenseManager.onLimitReached('activeUsers', fn);

const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);

await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);

licenseManager.setLicenseLimitCounter('activeUsers', () => 10);

await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);

await expect(fn).toBeCalledTimes(1);

await expect(fn).toBeCalledWith(undefined);
});
});
});
4 changes: 3 additions & 1 deletion ee/packages/license/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@swc/jest": "^0.2.26",
"@types/babel__core": "^7",
"@types/babel__preset-env": "^7",
"@types/bcrypt": "^5.0.0",
"@types/jest": "~29.5.3",
"@types/ws": "^8.5.5",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
Expand Down Expand Up @@ -42,6 +43,7 @@
"dependencies": {
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/jwt": "workspace:^",
"@rocket.chat/logger": "workspace:^"
"@rocket.chat/logger": "workspace:^",
"bcrypt": "^5.0.1"
}
}
17 changes: 13 additions & 4 deletions ee/packages/license/src/definition/LicenseBehavior.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type { LicenseLimitKind } from './ILicenseV3';
import type { LicenseModule } from './LicenseModule';

export type LicenseBehavior = 'invalidate_license' | 'start_fair_policy' | 'prevent_action' | 'prevent_installation' | 'disable_modules';

export type BehaviorWithContext = {
behavior: LicenseBehavior;
modules?: LicenseModule[];
};
export type BehaviorWithContext =
| {
behavior: LicenseBehavior;
modules?: LicenseModule[];
reason: 'limit';
limit?: LicenseLimitKind;
}
| {
behavior: LicenseBehavior;
modules?: LicenseModule[];
reason: 'period' | 'url';
};
11 changes: 11 additions & 0 deletions ee/packages/license/src/definition/LicenseValidationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { LicenseLimitKind } from './ILicenseV3';
import type { LicenseBehavior } from './LicenseBehavior';
import type { LimitContext } from './LimitContext';

export type LicenseValidationOptions = {
behaviors?: LicenseBehavior[];
limits?: LicenseLimitKind[];
suppressLog?: boolean;
isNewLicense?: boolean;
context?: Partial<{ [K in LicenseLimitKind]: Partial<LimitContext<LicenseLimitKind>> }>;
};
4 changes: 3 additions & 1 deletion ee/packages/license/src/definition/LimitContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import type { IUser } from '@rocket.chat/core-typings';

import type { LicenseLimitKind } from './ILicenseV3';

export type LimitContext<T extends LicenseLimitKind> = T extends 'roomsPerGuest' ? { userId: IUser['_id'] } : Record<string, never>;
export type LimitContext<T extends LicenseLimitKind> = { extraCount?: number } & (T extends 'roomsPerGuest'
? { userId: IUser['_id'] }
: Record<string, never>);
15 changes: 15 additions & 0 deletions ee/packages/license/src/definition/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { LicenseLimitKind } from './ILicenseV3';
import type { BehaviorWithContext, LicenseBehavior } from './LicenseBehavior';
import type { LicenseModule } from './LicenseModule';

type ModuleValidation = Record<`${'invalid' | 'valid'}:${LicenseModule}`, undefined>;
type BehaviorTriggered = Record<`behavior:${LicenseBehavior}`, { reason: BehaviorWithContext['reason']; limit?: LicenseLimitKind }>;
type LimitReached = Record<`limitReached:${LicenseLimitKind}`, undefined>;

export type LicenseEvents = ModuleValidation &
BehaviorTriggered &
LimitReached & {
validate: undefined;
invalidate: undefined;
module: { module: LicenseModule; valid: boolean };
};
40 changes: 37 additions & 3 deletions ee/packages/license/src/events/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { LicenseLimitKind } from '../definition/ILicenseV3';
import type { BehaviorWithContext } from '../definition/LicenseBehavior';
import type { LicenseModule } from '../definition/LicenseModule';
import type { LicenseManager } from '../license';
import { logger } from '../logger';
Expand All @@ -21,10 +21,44 @@ export function moduleRemoved(this: LicenseManager, module: LicenseModule) {
}
}

export function limitReached(this: LicenseManager, limitKind: LicenseLimitKind) {
export function behaviorTriggered(this: LicenseManager, options: BehaviorWithContext) {
const { behavior, reason, modules: _, ...rest } = options;
try {
this.emit(`limitReached:${limitKind}`);
this.emit(`behavior:${behavior}`, {
reason,
...rest,
});
} catch (error) {
logger.error({ msg: 'Error running behavior triggered event', error });
}

if (behavior !== 'prevent_action') {
return;
}

if (reason !== 'limit' || !(`limit` in rest) || !rest.limit) {
return;
}

try {
this.emit(`limitReached:${rest.limit}`);
} catch (error) {
logger.error({ msg: 'Error running limit reached event', error });
}
}

export function licenseValidated(this: LicenseManager) {
try {
this.emit('validate');
} catch (error) {
logger.error({ msg: 'Error running license validated event', error });
}
}

export function licenseInvalidated(this: LicenseManager) {
try {
this.emit('invalidate');
} catch (error) {
logger.error({ msg: 'Error running license invalidated event', error });
}
}
17 changes: 13 additions & 4 deletions ee/packages/license/src/events/listeners.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { LicenseLimitKind } from '../definition/ILicenseV3';
import type { BehaviorWithContext, LicenseBehavior } from '../definition/LicenseBehavior';
import type { LicenseModule } from '../definition/LicenseModule';
import type { LicenseManager } from '../license';
import { hasModule } from '../modules';
Expand Down Expand Up @@ -58,18 +59,26 @@ export function onToggledFeature(
};
}

export function onModule(this: LicenseManager, cb: (...args: any[]) => void) {
export function onModule(this: LicenseManager, cb: (data: { module: LicenseModule; valid: boolean }) => void) {
this.on('module', cb);
}

export function onValidateLicense(this: LicenseManager, cb: (...args: any[]) => void) {
export function onValidateLicense(this: LicenseManager, cb: () => void) {
this.on('validate', cb);
}

export function onInvalidateLicense(this: LicenseManager, cb: (...args: any[]) => void) {
export function onInvalidateLicense(this: LicenseManager, cb: () => void) {
this.on('invalidate', cb);
}

export function onLimitReached(this: LicenseManager, limitKind: LicenseLimitKind, cb: (...args: any[]) => void) {
export function onBehaviorTriggered(
this: LicenseManager,
behavior: Exclude<LicenseBehavior, 'prevent_installation'>,
cb: (data: { reason: BehaviorWithContext['reason']; limit?: LicenseLimitKind }) => void,
) {
this.on(`behavior:${behavior}`, cb);
}

export function onLimitReached(this: LicenseManager, limitKind: LicenseLimitKind, cb: () => void) {
this.on(`limitReached:${limitKind}`, cb);
}
7 changes: 6 additions & 1 deletion ee/packages/license/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { LimitContext } from './definition/LimitContext';
import { getAppsConfig, getMaxActiveUsers, getUnmodifiedLicenseAndModules } from './deprecated';
import { onLicense } from './events/deprecated';
import {
onBehaviorTriggered,
onInvalidFeature,
onInvalidateLicense,
onLimitReached,
Expand Down Expand Up @@ -45,6 +46,8 @@ interface License {
onValidateLicense: typeof onValidateLicense;
onInvalidateLicense: typeof onInvalidateLicense;
onLimitReached: typeof onLimitReached;
onBehaviorTriggered: typeof onBehaviorTriggered;
revalidateLicense: () => Promise<void>;

getInfo: (loadCurrentValues: boolean) => Promise<{
license: ILicenseV3 | undefined;
Expand Down Expand Up @@ -78,7 +81,7 @@ export class LicenseImp extends LicenseManager implements License {

getCurrentValueForLicenseLimit = getCurrentValueForLicenseLimit;

public async isLimitReached<T extends LicenseLimitKind>(action: T, context?: Partial<LimitContext<T>>) {
public async isLimitReached<T extends LicenseLimitKind>(action: T, context?: Partial<LimitContext<T>>): Promise<boolean> {
return this.shouldPreventAction(action, context, 0);
}

Expand All @@ -96,6 +99,8 @@ export class LicenseImp extends LicenseManager implements License {

onLimitReached = onLimitReached;

onBehaviorTriggered = onBehaviorTriggered;

// Deprecated:
onLicense = onLicense;

Expand Down
12 changes: 12 additions & 0 deletions ee/packages/license/src/isItemAllowed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { LicenseLimitKind } from './definition/ILicenseV3';
import type { LicenseBehavior } from './definition/LicenseBehavior';
import type { LicenseValidationOptions } from './definition/LicenseValidationOptions';

const isItemAllowed = <T>(item: T, allowList?: T[]): boolean => {
return !allowList || allowList.includes(item);
};

export const isLimitAllowed = (item: LicenseLimitKind, options: LicenseValidationOptions): boolean => isItemAllowed(item, options.limits);

export const isBehaviorAllowed = (item: LicenseBehavior, options: LicenseValidationOptions): boolean =>
isItemAllowed(item, options.behaviors) && (options.isNewLicense || item !== 'prevent_installation');
Loading

0 comments on commit e666487

Please sign in to comment.