@frontegg/entitlements-javascript-commons 1.1.0-alpha.1 → 1.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +3 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/plans/index.d.ts +2 -0
  5. package/dist/plans/index.js +18 -0
  6. package/dist/plans/index.js.map +1 -0
  7. package/dist/plans/plan.evaluator.d.ts +2 -0
  8. package/dist/plans/plan.evaluator.js +21 -0
  9. package/dist/plans/plan.evaluator.js.map +1 -0
  10. package/dist/plans/types.d.ts +8 -0
  11. package/dist/plans/types.js +3 -0
  12. package/dist/plans/types.js.map +1 -0
  13. package/dist/user-entitlements/index.d.ts +3 -3
  14. package/dist/user-entitlements/index.js +3 -3
  15. package/dist/user-entitlements/index.js.map +1 -1
  16. package/dist/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.d.ts +2 -0
  17. package/dist/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.js +20 -0
  18. package/dist/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.js.map +1 -0
  19. package/dist/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.d.ts +2 -0
  20. package/dist/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.js +23 -0
  21. package/dist/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.js.map +1 -0
  22. package/dist/user-entitlements/is-entitled-to-feature/evaluators/index.d.ts +2 -0
  23. package/dist/user-entitlements/is-entitled-to-feature/evaluators/index.js +11 -0
  24. package/dist/user-entitlements/is-entitled-to-feature/evaluators/index.js.map +1 -0
  25. package/dist/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.d.ts +2 -0
  26. package/dist/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.js +29 -0
  27. package/dist/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.js.map +1 -0
  28. package/dist/user-entitlements/is-entitled-to-feature/evaluators/types.d.ts +2 -0
  29. package/dist/user-entitlements/is-entitled-to-feature/evaluators/types.js +3 -0
  30. package/dist/user-entitlements/is-entitled-to-feature/evaluators/types.js.map +1 -0
  31. package/dist/user-entitlements/is-entitled-to-feature/index.d.ts +1 -0
  32. package/dist/user-entitlements/is-entitled-to-feature/index.js +18 -0
  33. package/dist/user-entitlements/is-entitled-to-feature/index.js.map +1 -0
  34. package/dist/user-entitlements/{is-entitled.evaluator.d.ts → is-entitled-to-feature/is-entitled-to-feature.evaluator.d.ts} +1 -2
  35. package/dist/user-entitlements/is-entitled-to-feature/is-entitled-to-feature.evaluator.js +17 -0
  36. package/dist/user-entitlements/is-entitled-to-feature/is-entitled-to-feature.evaluator.js.map +1 -0
  37. package/dist/user-entitlements/is-entitled-to-permission/index.d.ts +1 -0
  38. package/dist/user-entitlements/is-entitled-to-permission/index.js +18 -0
  39. package/dist/user-entitlements/is-entitled-to-permission/index.js.map +1 -0
  40. package/dist/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.d.ts +2 -0
  41. package/dist/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.js +30 -0
  42. package/dist/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.js.map +1 -0
  43. package/dist/user-entitlements/types.d.ts +7 -2
  44. package/dist/user-entitlements/types.js.map +1 -1
  45. package/dist/user-entitlements/{attributes.utils.d.ts → utils/attributes.utils.d.ts} +1 -1
  46. package/dist/user-entitlements/utils/attributes.utils.js.map +1 -0
  47. package/dist/user-entitlements/utils/entitlement-results.utils.d.ts +3 -0
  48. package/dist/user-entitlements/utils/entitlement-results.utils.js +25 -0
  49. package/dist/user-entitlements/utils/entitlement-results.utils.js.map +1 -0
  50. package/dist/user-entitlements/utils/flatten.utils.js.map +1 -0
  51. package/dist/user-entitlements/utils/index.d.ts +3 -0
  52. package/dist/user-entitlements/utils/index.js +20 -0
  53. package/dist/user-entitlements/utils/index.js.map +1 -0
  54. package/dist/user-entitlements/{permissions.utils.d.ts → utils/permissions.utils.d.ts} +1 -1
  55. package/dist/user-entitlements/utils/permissions.utils.js.map +1 -0
  56. package/docs/CHANGELOG.md +14 -0
  57. package/package.json +1 -1
  58. package/src/index.ts +1 -0
  59. package/src/user-entitlements/index.ts +3 -3
  60. package/src/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.ts +28 -0
  61. package/src/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.ts +24 -0
  62. package/src/user-entitlements/is-entitled-to-feature/evaluators/index.ts +8 -0
  63. package/src/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.ts +30 -0
  64. package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/direct-entitlements.evaluator.spec.ts +81 -0
  65. package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/feature-flag.evaluator.spec.ts +57 -0
  66. package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/plan-targeting-rules.evaluator.spec.ts +70 -0
  67. package/src/user-entitlements/is-entitled-to-feature/evaluators/types.ts +7 -0
  68. package/src/user-entitlements/is-entitled-to-feature/index.ts +1 -0
  69. package/src/user-entitlements/is-entitled-to-feature/is-entitled-to-feature.evaluator.ts +18 -0
  70. package/src/user-entitlements/is-entitled-to-feature/tests/is-entitled-to-feature.evaluator.spec.ts +68 -0
  71. package/src/user-entitlements/is-entitled-to-permission/index.ts +1 -0
  72. package/src/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.ts +38 -0
  73. package/src/user-entitlements/is-entitled-to-permission/tests/is-entitled-to-permission.evaluator.spec.ts +79 -0
  74. package/src/user-entitlements/types.ts +11 -4
  75. package/src/user-entitlements/{attributes.utils.ts → utils/attributes.utils.ts} +1 -1
  76. package/src/user-entitlements/utils/entitlement-results.utils.ts +22 -0
  77. package/src/user-entitlements/utils/index.ts +3 -0
  78. package/src/user-entitlements/{permissions.utils.ts → utils/permissions.utils.ts} +1 -1
  79. package/src/user-entitlements/{tests → utils/tests}/attributes.utils.spec.ts +1 -1
  80. package/src/user-entitlements/utils/tests/entitlement-results.utils.spec.ts +46 -0
  81. package/src/user-entitlements/{tests → utils/tests}/permissions.utils.spec.ts +1 -1
  82. package/dist/user-entitlements/attributes.utils.js.map +0 -1
  83. package/dist/user-entitlements/flatten.utils.js.map +0 -1
  84. package/dist/user-entitlements/is-entitled.evaluator.js +0 -59
  85. package/dist/user-entitlements/is-entitled.evaluator.js.map +0 -1
  86. package/dist/user-entitlements/permissions.utils.js.map +0 -1
  87. package/src/user-entitlements/is-entitled.evaluator.ts +0 -82
  88. package/src/user-entitlements/tests/is-entitled.evaluator.spec.ts +0 -298
  89. /package/dist/user-entitlements/{attributes.utils.js → utils/attributes.utils.js} +0 -0
  90. /package/dist/user-entitlements/{flatten.utils.d.ts → utils/flatten.utils.d.ts} +0 -0
  91. /package/dist/user-entitlements/{flatten.utils.js → utils/flatten.utils.js} +0 -0
  92. /package/dist/user-entitlements/{permissions.utils.js → utils/permissions.utils.js} +0 -0
  93. /package/src/user-entitlements/{flatten.utils.ts → utils/flatten.utils.ts} +0 -0
@@ -0,0 +1,24 @@
1
+ import { Attributes, EntitlementResult, NotEntitledJustification, UserEntitlementsContext } from '../../types';
2
+ import { evaluateFeatureFlag } from '../../../feature-flags';
3
+ import { prepareAttributes } from '../../utils/attributes.utils';
4
+ import { TreatmentEnum } from '../../../rules';
5
+
6
+ export function featureFlagEvaluator(
7
+ featureKey: string,
8
+ userEntitlementsContext: UserEntitlementsContext,
9
+ attributes: Attributes = {},
10
+ ): EntitlementResult {
11
+ const feature = userEntitlementsContext.features[featureKey];
12
+ if (feature && feature.featureFlag) {
13
+ const preparedAttributes = prepareAttributes(attributes);
14
+ const { treatment } = evaluateFeatureFlag(feature.featureFlag, preparedAttributes);
15
+ if (treatment === TreatmentEnum.True) {
16
+ return { isEntitled: true };
17
+ }
18
+ }
19
+
20
+ return {
21
+ isEntitled: false,
22
+ justification: NotEntitledJustification.MISSING_FEATURE,
23
+ };
24
+ }
@@ -0,0 +1,8 @@
1
+ import { directEntitlementEvalutor } from './direct-entitlement.evaluator';
2
+ import { featureFlagEvaluator } from './feature-flag.evaluator';
3
+ import { planTargetingRulesEvalutor } from './plan-targeting-rules.evaluator';
4
+ import { IsEntitledEvaluator } from './types';
5
+
6
+ export function getIsEntitledEvaluators(): IsEntitledEvaluator[] {
7
+ return [directEntitlementEvalutor, featureFlagEvaluator, planTargetingRulesEvalutor];
8
+ }
@@ -0,0 +1,30 @@
1
+ import { Attributes, EntitlementResult, NotEntitledJustification, UserEntitlementsContext } from '../../types';
2
+ import { prepareAttributes } from '../../utils/attributes.utils';
3
+ import { TreatmentEnum } from '../../../rules';
4
+ import { evaluatePlan } from '../../../plans';
5
+
6
+ export function planTargetingRulesEvalutor(
7
+ featureKey: string,
8
+ userEntitlementsContext: UserEntitlementsContext,
9
+ attributes: Attributes = {},
10
+ ): EntitlementResult {
11
+ const feature = userEntitlementsContext.features[featureKey];
12
+ if (feature && feature.planIds && feature.planIds.length > 0) {
13
+ const preparedAttributes = prepareAttributes(attributes);
14
+ const plans = userEntitlementsContext.plans;
15
+ for (const planId of feature.planIds) {
16
+ const plan = plans[planId];
17
+ if (plan) {
18
+ const { treatment } = evaluatePlan(plan, preparedAttributes);
19
+ if (treatment === TreatmentEnum.True) {
20
+ return { isEntitled: true };
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ return {
27
+ isEntitled: false,
28
+ justification: NotEntitledJustification.MISSING_FEATURE,
29
+ };
30
+ }
@@ -0,0 +1,81 @@
1
+ import { NO_EXPIRATION_TIME, NotEntitledJustification } from '../../../types';
2
+ import { directEntitlementEvalutor } from '../direct-entitlement.evaluator';
3
+
4
+ describe('directEntitlementEvalutor', () => {
5
+ describe('entitled', () => {
6
+ test('feature is entitled with no expiration date', async () => {
7
+ const result = directEntitlementEvalutor('feature-key', {
8
+ features: {
9
+ 'feature-key': {
10
+ expireTime: NO_EXPIRATION_TIME,
11
+ linkedPermissions: [],
12
+ planIds: [],
13
+ },
14
+ },
15
+ permissions: {},
16
+ plans: {},
17
+ });
18
+
19
+ expect(result).toEqual({ isEntitled: true });
20
+ });
21
+
22
+ test('feature is entitled with a valid expiration date', async () => {
23
+ const result = directEntitlementEvalutor('feature-key', {
24
+ features: {
25
+ 'feature-key': {
26
+ expireTime: Date.now() + 3600,
27
+ linkedPermissions: [],
28
+ planIds: [],
29
+ },
30
+ },
31
+ permissions: {},
32
+ plans: {},
33
+ });
34
+
35
+ expect(result).toEqual({ isEntitled: true });
36
+ });
37
+ });
38
+ describe('not-entitled', () => {
39
+ test('feature is not entitled', async () => {
40
+ const result = directEntitlementEvalutor('feature-key', {
41
+ features: {
42
+ 'feature-key': {
43
+ expireTime: null,
44
+ linkedPermissions: [],
45
+ planIds: [],
46
+ },
47
+ },
48
+ permissions: {},
49
+ plans: {},
50
+ });
51
+
52
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
53
+ });
54
+
55
+ test('feature is entitled with an expired expiration date', async () => {
56
+ const result = directEntitlementEvalutor('feature-key', {
57
+ features: {
58
+ 'feature-key': {
59
+ expireTime: Date.now() - 3600,
60
+ linkedPermissions: [],
61
+ planIds: [],
62
+ },
63
+ },
64
+ permissions: {},
65
+ plans: {},
66
+ });
67
+
68
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
69
+ });
70
+
71
+ test('feature does not exist', async () => {
72
+ const result = directEntitlementEvalutor('feature-key', {
73
+ features: {},
74
+ permissions: {},
75
+ plans: {},
76
+ });
77
+
78
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,57 @@
1
+ import * as AttributesUtils from '../../../utils/attributes.utils';
2
+ import * as FeatureFlagEvaluator from '../../../../feature-flags/feature-flag.evaluator';
3
+ import { TreatmentEnum } from '../../../../rules';
4
+ import { featureFlagEvaluator } from '../feature-flag.evaluator';
5
+ import { NotEntitledJustification } from '../../../types';
6
+ describe('featureFlagEvaluator', () => {
7
+ beforeAll(async () => {
8
+ jest.spyOn(AttributesUtils, 'prepareAttributes').mockReturnValue({ testAttribute: 'test-value' });
9
+ });
10
+ describe('entitled', () => {
11
+ test('feature flag evaluation return truthy', async () => {
12
+ jest.spyOn(FeatureFlagEvaluator, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.True });
13
+ const result = featureFlagEvaluator('feature-key', {
14
+ features: {
15
+ 'feature-key': {
16
+ expireTime: null,
17
+ linkedPermissions: [],
18
+ planIds: [],
19
+ featureFlag: { defaultTreatment: TreatmentEnum.True, on: true, offTreatment: TreatmentEnum.False },
20
+ },
21
+ },
22
+ permissions: {},
23
+ plans: {},
24
+ });
25
+
26
+ expect(result).toEqual({ isEntitled: true });
27
+ });
28
+ });
29
+ describe('not-entitled', () => {
30
+ test('feature flag evaluation return falsy', async () => {
31
+ jest.spyOn(FeatureFlagEvaluator, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.False });
32
+ const result = featureFlagEvaluator('feature-key', {
33
+ features: {
34
+ 'feature-key': {
35
+ expireTime: null,
36
+ linkedPermissions: [],
37
+ planIds: [],
38
+ featureFlag: { defaultTreatment: TreatmentEnum.False, on: true, offTreatment: TreatmentEnum.False },
39
+ },
40
+ },
41
+ permissions: {},
42
+ plans: {},
43
+ });
44
+
45
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
46
+ });
47
+ test('feataure does not exist', async () => {
48
+ const result = featureFlagEvaluator('feature-key', {
49
+ features: {},
50
+ permissions: {},
51
+ plans: {},
52
+ });
53
+
54
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,70 @@
1
+ import * as PlanEvaluator from '../../../../plans/plan.evaluator';
2
+ import { TreatmentEnum } from '../../../../rules';
3
+ import { NotEntitledJustification } from '../../../types';
4
+ import { planTargetingRulesEvalutor } from '../plan-targeting-rules.evaluator';
5
+ describe('planTargetingRulesEvalutor', () => {
6
+ describe('entitled', () => {
7
+ test('some of the plans targeting rules return truthy', async () => {
8
+ jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.True });
9
+ jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.False });
10
+
11
+ const result = planTargetingRulesEvalutor('feature-key', {
12
+ features: {
13
+ 'feature-key': {
14
+ expireTime: null,
15
+ linkedPermissions: [],
16
+ planIds: ['plan-1', 'plan-2'],
17
+ },
18
+ },
19
+ permissions: {},
20
+ plans: {
21
+ 'plan-1': {
22
+ defaultTreatment: TreatmentEnum.True,
23
+ },
24
+ 'plan-2': {
25
+ defaultTreatment: TreatmentEnum.False,
26
+ },
27
+ },
28
+ });
29
+
30
+ expect(result).toEqual({ isEntitled: true });
31
+ });
32
+ });
33
+ describe('not-entitled', () => {
34
+ test('all of the plans targeting rules return falsy', async () => {
35
+ jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.False });
36
+ jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.False });
37
+
38
+ const result = planTargetingRulesEvalutor('feature-key', {
39
+ features: {
40
+ 'feature-key': {
41
+ expireTime: null,
42
+ linkedPermissions: [],
43
+ planIds: ['plan-1', 'plan-2'],
44
+ },
45
+ },
46
+ permissions: {},
47
+ plans: {
48
+ 'plan-1': {
49
+ defaultTreatment: TreatmentEnum.False,
50
+ },
51
+ 'plan-2': {
52
+ defaultTreatment: TreatmentEnum.False,
53
+ },
54
+ },
55
+ });
56
+
57
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
58
+ });
59
+
60
+ test('feature does not exist', async () => {
61
+ const result = planTargetingRulesEvalutor('feature-key', {
62
+ features: {},
63
+ permissions: {},
64
+ plans: {},
65
+ });
66
+
67
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,7 @@
1
+ import { Attributes, EntitlementResult, UserEntitlementsContext } from '../../types';
2
+
3
+ export type IsEntitledEvaluator = (
4
+ featureKey: string,
5
+ userEntitlementsContext: UserEntitlementsContext,
6
+ attributes: Attributes,
7
+ ) => EntitlementResult;
@@ -0,0 +1 @@
1
+ export * from './is-entitled-to-feature.evaluator';
@@ -0,0 +1,18 @@
1
+ import { EntitlementResult, NotEntitledJustification, UserEntitlementsContext, Attributes } from '../types';
2
+ import { getResult, shouldContinue } from '../utils';
3
+ import { getIsEntitledEvaluators } from './evaluators';
4
+ export function evaluateIsEntitledToFeature(
5
+ featureKey: string,
6
+ userEntitlementsContext: UserEntitlementsContext,
7
+ attributes: Attributes = {},
8
+ ): EntitlementResult {
9
+ const entitlementResults: EntitlementResult[] = [];
10
+ for (const evaluator of getIsEntitledEvaluators()) {
11
+ entitlementResults.push(evaluator(featureKey, userEntitlementsContext, attributes));
12
+ if (!shouldContinue(entitlementResults)) {
13
+ break;
14
+ }
15
+ }
16
+
17
+ return getResult(entitlementResults);
18
+ }
@@ -0,0 +1,68 @@
1
+ import * as IsEntitledEvaluators from '../evaluators';
2
+ import { evaluateIsEntitledToFeature } from '../is-entitled-to-feature.evaluator';
3
+ import { EntitlementResult, NotEntitledJustification, NO_EXPIRATION_TIME, UserEntitlementsContext } from '../../types';
4
+
5
+ describe('evaluateIsEntitledToFeature', () => {
6
+ const mockTruthyEvaluator = (): EntitlementResult => ({ isEntitled: true });
7
+ const mockFalsyMissingFeatureEvaluator = (): EntitlementResult => ({
8
+ isEntitled: false,
9
+ justification: NotEntitledJustification.MISSING_PERMISSION,
10
+ });
11
+ const mockFalsyExpiredEvaluator = (): EntitlementResult => ({
12
+ isEntitled: false,
13
+ justification: NotEntitledJustification.BUNDLE_EXPIRED,
14
+ });
15
+
16
+ describe('entitled', () => {
17
+ test('given all evaluators return truthy, feature is entitled', async () => {
18
+ jest
19
+ .spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
20
+ .mockReturnValue([mockTruthyEvaluator, mockTruthyEvaluator]);
21
+ const result = evaluateIsEntitledToFeature('feauture-key', {
22
+ features: {},
23
+ permissions: {},
24
+ plans: {},
25
+ });
26
+
27
+ expect(result).toEqual({ isEntitled: true });
28
+ });
29
+ test('given some evaluators return truthy, feature is entitled', async () => {
30
+ jest
31
+ .spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
32
+ .mockReturnValue([mockTruthyEvaluator, mockFalsyMissingFeatureEvaluator]);
33
+ const result = evaluateIsEntitledToFeature('feauture-key', {
34
+ features: {},
35
+ permissions: {},
36
+ plans: {},
37
+ });
38
+
39
+ expect(result).toEqual({ isEntitled: true });
40
+ });
41
+ });
42
+ describe('not-entitled', () => {
43
+ test('given all evaluators return falsy with missing feature, feature is not entitled, justified with missing feature', async () => {
44
+ jest
45
+ .spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
46
+ .mockReturnValue([mockFalsyMissingFeatureEvaluator, mockFalsyMissingFeatureEvaluator]);
47
+ const result = evaluateIsEntitledToFeature('feauture-key', {
48
+ features: {},
49
+ permissions: {},
50
+ plans: {},
51
+ });
52
+
53
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
54
+ });
55
+ test('given all evaluators return falsy, some with missing feature, some with bundle expired, feature is not entitled, justified with bundle expired', async () => {
56
+ jest
57
+ .spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
58
+ .mockReturnValue([mockFalsyMissingFeatureEvaluator, mockFalsyExpiredEvaluator]);
59
+ const result = evaluateIsEntitledToFeature('feauture-key', {
60
+ features: {},
61
+ permissions: {},
62
+ plans: {},
63
+ });
64
+
65
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
66
+ });
67
+ });
68
+ });
@@ -0,0 +1 @@
1
+ export * from './is-entitled-to-permission.evaluator';
@@ -0,0 +1,38 @@
1
+ import { evaluateIsEntitledToFeature } from '../is-entitled-to-feature/is-entitled-to-feature.evaluator';
2
+ import { UserEntitlementsContext, Attributes, EntitlementResult, NotEntitledJustification } from '../types';
3
+ import { getResult, shouldContinue } from '../utils';
4
+ import { checkPermission } from '../utils/permissions.utils';
5
+
6
+ export function evaluateIsEntitledToPermissions(
7
+ permissionKey: string,
8
+ userEntitlementsContext: UserEntitlementsContext,
9
+ attributes?: Attributes,
10
+ ): EntitlementResult {
11
+ const hasPermission = checkPermission(userEntitlementsContext.permissions, permissionKey);
12
+ if (!hasPermission) {
13
+ return { isEntitled: false, justification: NotEntitledJustification.MISSING_PERMISSION };
14
+ }
15
+
16
+ const linkedFeatures = getLinkedFeatures(permissionKey, userEntitlementsContext);
17
+
18
+ if (!linkedFeatures.length) {
19
+ return { isEntitled: true };
20
+ }
21
+
22
+ const entitlementResults: EntitlementResult[] = [];
23
+ for (const featureKey of linkedFeatures) {
24
+ entitlementResults.push(evaluateIsEntitledToFeature(featureKey, userEntitlementsContext, attributes));
25
+
26
+ if (!shouldContinue(entitlementResults)) {
27
+ break;
28
+ }
29
+ }
30
+
31
+ return getResult(entitlementResults);
32
+ }
33
+
34
+ function getLinkedFeatures(permissionKey: string, userEntitlementsContext: UserEntitlementsContext): string[] {
35
+ return Object.keys(userEntitlementsContext.features).filter((featureKey) =>
36
+ userEntitlementsContext.features[featureKey].linkedPermissions.includes(permissionKey),
37
+ );
38
+ }
@@ -0,0 +1,79 @@
1
+ import { evaluateIsEntitledToPermissions } from '../is-entitled-to-permission.evaluator';
2
+ import { NO_EXPIRATION_TIME, NotEntitledJustification, UserEntitlementsContext } from '../../types';
3
+ import * as IsEntitledToFeatureEvaluator from '../../is-entitled-to-feature/is-entitled-to-feature.evaluator';
4
+ describe('evaluateIsEntitledToPermission', () => {
5
+ describe('entitled', () => {
6
+ test('permission granted, no linked feature/s to it', async () => {
7
+ const userEntitlementContext: UserEntitlementsContext = {
8
+ features: {},
9
+ plans: {},
10
+
11
+ permissions: { 'test.permission': true },
12
+ };
13
+ const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
14
+
15
+ expect(result).toEqual({ isEntitled: true });
16
+ });
17
+ test('permission granted with linked feature/s, feature is entitled', async () => {
18
+ jest.spyOn(IsEntitledToFeatureEvaluator, 'evaluateIsEntitledToFeature').mockReturnValue({ isEntitled: true });
19
+
20
+ const userEntitlementContext: UserEntitlementsContext = {
21
+ features: {
22
+ 'test-feature': { planIds: [], expireTime: NO_EXPIRATION_TIME, linkedPermissions: ['test.permission'] },
23
+ },
24
+ plans: {},
25
+
26
+ permissions: { 'test.permission': true },
27
+ };
28
+
29
+ const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
30
+
31
+ expect(result).toEqual({ isEntitled: true });
32
+ });
33
+ });
34
+
35
+ describe('not entitled', () => {
36
+ test('permission not granted', async () => {
37
+ const userEntitlementContext: UserEntitlementsContext = {
38
+ features: {},
39
+ plans: {},
40
+ permissions: {},
41
+ };
42
+ const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
43
+
44
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_PERMISSION });
45
+ });
46
+ test('permission granted with linked feature/s, no feature is entiteld', async () => {
47
+ jest
48
+ .spyOn(IsEntitledToFeatureEvaluator, 'evaluateIsEntitledToFeature')
49
+ .mockReturnValue({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
50
+
51
+ const userEntitlementContext: UserEntitlementsContext = {
52
+ features: { 'test-feature': { planIds: [], expireTime: null, linkedPermissions: ['test.permission'] } },
53
+ plans: {},
54
+ permissions: { 'test.permission': true },
55
+ };
56
+
57
+ const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
58
+
59
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
60
+ });
61
+ test('permission granted with linked feature/s, no feature is entiteld with expired bundle', async () => {
62
+ jest
63
+ .spyOn(IsEntitledToFeatureEvaluator, 'evaluateIsEntitledToFeature')
64
+ .mockReturnValue({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
65
+ });
66
+
67
+ const userEntitlementContext: UserEntitlementsContext = {
68
+ features: {
69
+ 'test-feature': { planIds: [], expireTime: Date.now() - 3600, linkedPermissions: ['test.permission'] },
70
+ },
71
+ plans: {},
72
+ permissions: { 'test.permission': true },
73
+ };
74
+
75
+ const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
76
+
77
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
78
+ });
79
+ });
@@ -1,20 +1,27 @@
1
1
  import { FeatureFlag } from '../feature-flags/types';
2
+ import { Plan } from '../plans';
2
3
  export type UserEntitlementsContext = {
3
4
  features: Record<
4
5
  string,
5
6
  {
7
+ planIds: string[];
6
8
  expireTime: number | null;
7
9
  linkedPermissions: string[];
8
10
  featureFlag?: FeatureFlag;
9
11
  }
10
12
  >;
13
+ plans: Record<string, Plan>;
11
14
  permissions: Permissions;
12
15
  };
13
16
 
14
- export type EntitlementResult = {
15
- isEntitled: boolean;
16
- justification?: NotEntitledJustification;
17
- };
17
+ export type EntitlementResult =
18
+ | {
19
+ isEntitled: true;
20
+ }
21
+ | {
22
+ isEntitled: false;
23
+ justification: NotEntitledJustification;
24
+ };
18
25
 
19
26
  export enum NotEntitledJustification {
20
27
  MISSING_FEATURE = 'MISSING_FEATURE',
@@ -1,4 +1,4 @@
1
- import { Attributes, JwtAttributes, FronteggAttributes } from './types';
1
+ import { Attributes, JwtAttributes, FronteggAttributes } from '../types';
2
2
  import { flatten } from './flatten.utils';
3
3
  /**
4
4
  * Merges both `custom` and `jwt` records, map Frontegg attributes and modifies record keys with corrisponding prefixes
@@ -0,0 +1,22 @@
1
+ import { EntitlementResult, NotEntitledJustification } from '../types';
2
+ export function getResult(entitlementResults: EntitlementResult[]): EntitlementResult {
3
+ let hasExpired = false;
4
+ for (const entitlementResult of entitlementResults) {
5
+ if (entitlementResult.isEntitled) {
6
+ return entitlementResult;
7
+ }
8
+
9
+ if (entitlementResult.justification === NotEntitledJustification.BUNDLE_EXPIRED) {
10
+ hasExpired = true;
11
+ }
12
+ }
13
+
14
+ return {
15
+ isEntitled: false,
16
+ justification: hasExpired ? NotEntitledJustification.BUNDLE_EXPIRED : NotEntitledJustification.MISSING_FEATURE,
17
+ };
18
+ }
19
+
20
+ export function shouldContinue(entitlementResults: EntitlementResult[]): boolean {
21
+ return entitlementResults.every(({ isEntitled }) => !isEntitled);
22
+ }
@@ -0,0 +1,3 @@
1
+ export * from './permissions.utils';
2
+ export * from './attributes.utils';
3
+ export * from './entitlement-results.utils';
@@ -1,4 +1,4 @@
1
- import { Permissions } from './types';
1
+ import { Permissions } from '../types';
2
2
  export function checkPermission(permissions: Permissions, requiredPermission: string): boolean {
3
3
  return Object.keys(permissions).some((permissionKey) =>
4
4
  createPermissionCheckRegex(permissionKey).test(requiredPermission),
@@ -1,5 +1,5 @@
1
1
  import * as AttributesUtils from '../attributes.utils';
2
- import { Attributes, FronteggAttributes, JwtAttributes } from '../types';
2
+ import { Attributes, FronteggAttributes, JwtAttributes } from '../../types';
3
3
  describe('prepareAttributes', () => {
4
4
  test('given custom & jwt attributes, expected is merged & flatten attributes record', () => {
5
5
  const attributes: Attributes = {
@@ -0,0 +1,46 @@
1
+ import { EntitlementResult, NotEntitledJustification } from '../../types';
2
+ import { getResult, shouldContinue } from '../entitlement-results.utils';
3
+ describe('getResult', () => {
4
+ describe('entitled', () => {
5
+ test('given some of the results are truthy, exected is entitled', async () => {
6
+ const entitlementsResults: EntitlementResult[] = [
7
+ { isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
8
+ { isEntitled: true },
9
+ ];
10
+
11
+ const result = getResult(entitlementsResults);
12
+
13
+ expect(result).toEqual({ isEntitled: true });
14
+ });
15
+ });
16
+ describe('not-entitled', () => {
17
+ test('given all of results are falsy with missing feature, expected is not entitled with missing feature justification', async () => {
18
+ const entitlementsResults: EntitlementResult[] = [
19
+ { isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
20
+ { isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
21
+ ];
22
+
23
+ const result = getResult(entitlementsResults);
24
+
25
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
26
+ });
27
+ test('given all of results are falsy with some expired, expected is not entitled with expired justification', async () => {
28
+ const entitlementsResults: EntitlementResult[] = [
29
+ { isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
30
+ { isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED },
31
+ ];
32
+
33
+ const result = getResult(entitlementsResults);
34
+
35
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
36
+ });
37
+
38
+ test('given empty results, expected is not entitled with missing feature justification', async () => {
39
+ const entitlementsResults: EntitlementResult[] = [];
40
+
41
+ const result = getResult(entitlementsResults);
42
+
43
+ expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
44
+ });
45
+ });
46
+ });
@@ -1,4 +1,4 @@
1
- import { Permissions } from '../types';
1
+ import { Permissions } from '../../types';
2
2
  import { checkPermission } from '../permissions.utils';
3
3
  describe('checkPermission', () => {
4
4
  test.each([[{ 'test.permission': true }], [{ 'test.*': true }], [{ '*': true }]])(
@@ -1 +0,0 @@
1
- {"version":3,"file":"attributes.utils.js","sourceRoot":"","sources":["../../src/user-entitlements/attributes.utils.ts"],"names":[],"mappings":";;;AACA,mDAA0C;AAC1C;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAC/B,aAAyB,EAAE,EAC3B,8BAAqF;IAErF,MAAM,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,EAAE,GAAG,UAAU,CAAC;IAC7C,MAAM,iBAAiB,GAAG,IAAA,uBAAO,EAA+B,GAAG,CAAC,CAAC;IACrE,MAAM,kBAAkB,GAAG,8BAA8B;QACvD,CAAC,CAAC,8BAA8B,CAAC,GAAG,CAAC;QACrC,CAAC,CAAC,+BAA+B,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,wBAAwB,GAAG,WAAW,CAAC;IAC7C,MAAM,mBAAmB,GAAG,MAAM,CAAC;IAEnC,OAAO;QACL,GAAG,MAAM;QACT,GAAG,0BAA0B,CAAC,kBAAkB,EAAE,wBAAwB,CAAC;QAC3E,GAAG,0BAA0B,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;KACtE,CAAC;AACJ,CAAC;AAjBD,8CAiBC;AAED,SAAgB,+BAA+B,CAAC,GAAkB;IAChE,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAe;QAC1B,aAAa,EAAE,GAAG,CAAC,cAAyB;QAC5C,QAAQ,EAAE,GAAG,CAAC,QAAkB;QAChC,MAAM,EAAE,GAAG,CAAC,EAAY;KACzB,CAAC;AACJ,CAAC;AAPD,0EAOC;AAED,SAAgB,0BAA0B,CAAC,MAA+B,EAAE,MAAc;IACxF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,UAAU,EAAE,EAAE;QAC/D,cAAc,CAAC,GAAG,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,cAAc,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AALD,gEAKC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"flatten.utils.js","sourceRoot":"","sources":["../../src/user-entitlements/flatten.utils.ts"],"names":[],"mappings":";;;AAQA,SAAgB,OAAO,CAAmB,MAAe,EAAE,IAAqB;IAC9E,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IAElB,MAAM,SAAS,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,SAAS,KAAI,GAAG,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,QAAQ,CAAC;IAChC,MAAM,YAAY,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,YAAY,KAAI,WAAW,CAAC;IACvD,MAAM,MAAM,GAAG,EAAE,CAAC;IAElB,SAAS,IAAI,CAAC,MAAM,EAAE,IAAK,EAAE,YAAa;QACxC,YAAY,GAAG,YAAY,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG;YACvC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,OAAO,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,KAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,QAAQ,GAAG,IAAI,KAAK,iBAAiB,IAAI,IAAI,KAAK,gBAAgB,CAAC;YAEzE,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;YAC/E,IACE,CAAC,OAAO;gBACR,CAAC,QAAQ;gBACT,QAAQ;gBACR,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM;gBACzB,CAAC,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,QAAQ,CAAA,IAAI,CAAC,QAAQ,IAAI,YAAY,GAAG,QAAQ,CAAC,CAAC,EAC1D;gBACA,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;aAC9C;YAED,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,CAAC;IAEb,OAAO,MAAiB,CAAC;AAC3B,CAAC;AAnCD,0BAmCC;AAED,SAAS,QAAQ,CAAC,GAAG;IACnB,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,IAAI,OAAO,GAAG,CAAC,WAAW,CAAC,QAAQ,KAAK,UAAU,IAAI,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACnH,CAAC;AAED,SAAS,WAAW,CAAC,GAAG;IACtB,OAAO,GAAG,CAAC;AACb,CAAC"}