@frontegg/entitlements-javascript-commons 1.0.0-alpha.5 → 1.0.0-alpha.7
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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/user-entitlements/index.d.ts +2 -0
- package/dist/user-entitlements/index.js +19 -0
- package/dist/user-entitlements/index.js.map +1 -0
- package/dist/user-entitlements/is-entitled.evaluator.d.ts +3 -0
- package/dist/user-entitlements/is-entitled.evaluator.js +53 -0
- package/dist/user-entitlements/is-entitled.evaluator.js.map +1 -0
- package/dist/user-entitlements/types.d.ts +20 -0
- package/dist/user-entitlements/types.js +11 -0
- package/dist/user-entitlements/types.js.map +1 -0
- package/docs/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/index.ts +8 -0
- package/src/user-entitlements/index.ts +2 -0
- package/src/user-entitlements/is-entitled.evaluator.ts +71 -0
- package/src/user-entitlements/tests/is-entitled.evaluator.spec.ts +291 -0
- package/src/user-entitlements/types.ts +27 -0
package/dist/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { FeatureFlagEvaluationResult, FeatureFlag, evaluateFeatureFlag } from '.
|
|
|
2
2
|
export { TreatmentEnum, Rule } from './rules';
|
|
3
3
|
export { Condition } from './conditions';
|
|
4
4
|
export { OperationEnum, ConditionValue } from './operations/types';
|
|
5
|
+
export { evaluateIsEntitledToFeature, evaluateIsEntitledToPermissions, CustomAttributes, NotEntitledJustification, UserEntitlementsContext, EntitlementResult, } from './user-entitlements';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OperationEnum = exports.TreatmentEnum = exports.evaluateFeatureFlag = void 0;
|
|
3
|
+
exports.NotEntitledJustification = exports.evaluateIsEntitledToPermissions = exports.evaluateIsEntitledToFeature = exports.OperationEnum = exports.TreatmentEnum = exports.evaluateFeatureFlag = void 0;
|
|
4
4
|
var feature_flags_1 = require("./feature-flags");
|
|
5
5
|
Object.defineProperty(exports, "evaluateFeatureFlag", { enumerable: true, get: function () { return feature_flags_1.evaluateFeatureFlag; } });
|
|
6
6
|
var rules_1 = require("./rules");
|
|
7
7
|
Object.defineProperty(exports, "TreatmentEnum", { enumerable: true, get: function () { return rules_1.TreatmentEnum; } });
|
|
8
8
|
var types_1 = require("./operations/types");
|
|
9
9
|
Object.defineProperty(exports, "OperationEnum", { enumerable: true, get: function () { return types_1.OperationEnum; } });
|
|
10
|
+
var user_entitlements_1 = require("./user-entitlements");
|
|
11
|
+
Object.defineProperty(exports, "evaluateIsEntitledToFeature", { enumerable: true, get: function () { return user_entitlements_1.evaluateIsEntitledToFeature; } });
|
|
12
|
+
Object.defineProperty(exports, "evaluateIsEntitledToPermissions", { enumerable: true, get: function () { return user_entitlements_1.evaluateIsEntitledToPermissions; } });
|
|
13
|
+
Object.defineProperty(exports, "NotEntitledJustification", { enumerable: true, get: function () { return user_entitlements_1.NotEntitledJustification; } });
|
|
10
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAgG;AAA7C,oHAAA,mBAAmB,OAAA;AACtE,iCAA8C;AAArC,sGAAA,aAAa,OAAA;AAEtB,4CAAmE;AAA1D,sGAAA,aAAa,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAgG;AAA7C,oHAAA,mBAAmB,OAAA;AACtE,iCAA8C;AAArC,sGAAA,aAAa,OAAA;AAEtB,4CAAmE;AAA1D,sGAAA,aAAa,OAAA;AACtB,yDAO6B;AAN3B,gIAAA,2BAA2B,OAAA;AAC3B,oIAAA,+BAA+B,OAAA;AAE/B,6HAAA,wBAAwB,OAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./is-entitled.evaluator"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/user-entitlements/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,0CAAwB"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CustomAttributes, EntitlementResult, UserEntitlementsContext } from './types';
|
|
2
|
+
export declare function evaluateIsEntitledToFeature(featureKey: string, userEntitlementsContext: UserEntitlementsContext, attributes?: CustomAttributes): EntitlementResult;
|
|
3
|
+
export declare function evaluateIsEntitledToPermissions(permissionKey: string, userEntitlementsContext: UserEntitlementsContext, attributes?: CustomAttributes): EntitlementResult;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateIsEntitledToPermissions = exports.evaluateIsEntitledToFeature = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
const feature_flags_1 = require("../feature-flags");
|
|
6
|
+
const rules_1 = require("../rules");
|
|
7
|
+
function evaluateIsEntitledToFeature(featureKey, userEntitlementsContext, attributes = {}) {
|
|
8
|
+
const feature = userEntitlementsContext.features[featureKey];
|
|
9
|
+
let hasExpired = false;
|
|
10
|
+
if (feature && feature.expireTime !== null) {
|
|
11
|
+
hasExpired = feature.expireTime !== types_1.NO_EXPIRATION_TIME && feature.expireTime < Date.now();
|
|
12
|
+
if (!hasExpired) {
|
|
13
|
+
return { isEntitled: true };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (feature && feature.featureFlag) {
|
|
17
|
+
const { treatment } = (0, feature_flags_1.evaluateFeatureFlag)(feature.featureFlag, attributes);
|
|
18
|
+
if (treatment === rules_1.TreatmentEnum.True) {
|
|
19
|
+
return { isEntitled: true };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { isEntitled: false, justification: hasExpired ? types_1.NotEntitledJustification.BUNDLE_EXPIRED : types_1.NotEntitledJustification.MISSING_FEATURE };
|
|
23
|
+
}
|
|
24
|
+
exports.evaluateIsEntitledToFeature = evaluateIsEntitledToFeature;
|
|
25
|
+
function evaluateIsEntitledToPermissions(permissionKey, userEntitlementsContext, attributes = {}) {
|
|
26
|
+
const permission = userEntitlementsContext.permissions[permissionKey];
|
|
27
|
+
if (!permission) {
|
|
28
|
+
return { isEntitled: false, justification: types_1.NotEntitledJustification.MISSING_PERMISSION };
|
|
29
|
+
}
|
|
30
|
+
const linkedFeatures = getLinkedFeatures(permissionKey, userEntitlementsContext);
|
|
31
|
+
if (!linkedFeatures.length) {
|
|
32
|
+
return { isEntitled: true };
|
|
33
|
+
}
|
|
34
|
+
let hasExpired = false;
|
|
35
|
+
for (const featureKey of linkedFeatures) {
|
|
36
|
+
const { isEntitled, justification } = evaluateIsEntitledToFeature(featureKey, userEntitlementsContext, attributes);
|
|
37
|
+
if (isEntitled) {
|
|
38
|
+
return { isEntitled: true };
|
|
39
|
+
}
|
|
40
|
+
if (justification === types_1.NotEntitledJustification.BUNDLE_EXPIRED) {
|
|
41
|
+
hasExpired = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
isEntitled: false,
|
|
46
|
+
justification: hasExpired ? types_1.NotEntitledJustification.BUNDLE_EXPIRED : types_1.NotEntitledJustification.MISSING_FEATURE,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
exports.evaluateIsEntitledToPermissions = evaluateIsEntitledToPermissions;
|
|
50
|
+
function getLinkedFeatures(permissionKey, userEntitlementsContext) {
|
|
51
|
+
return Object.keys(userEntitlementsContext.features).filter((featureKey) => userEntitlementsContext.features[featureKey].linkedPermissions.includes(permissionKey));
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=is-entitled.evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-entitled.evaluator.js","sourceRoot":"","sources":["../../src/user-entitlements/is-entitled.evaluator.ts"],"names":[],"mappings":";;;AAAA,mCAAqI;AACrI,oDAAuD;AACvD,oCAAyC;AACzC,SAAgB,2BAA2B,CACzC,UAAkB,EAClB,uBAAgD,EAChD,aAA+B,EAAE;IAEjC,MAAM,OAAO,GAAG,uBAAuB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE7D,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,OAAO,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,EAAE;QAC1C,UAAU,GAAG,OAAO,CAAC,UAAU,KAAK,0BAAkB,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE1F,IAAI,CAAC,UAAU,EAAE;YACf,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;SAC7B;KACF;IAED,IAAI,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE;QAClC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAA,mCAAmB,EAAC,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAC3E,IAAI,SAAS,KAAK,qBAAa,CAAC,IAAI,EAAE;YACpC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;SAC7B;KACF;IAED,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC,gCAAwB,CAAC,cAAc,CAAC,CAAC,CAAC,gCAAwB,CAAC,eAAe,EAAE,CAAC;AAC/I,CAAC;AAxBD,kEAwBC;AAED,SAAgB,+BAA+B,CAC7C,aAAqB,EACrB,uBAAgD,EAChD,aAA+B,EAAE;IAEjC,MAAM,UAAU,GAAG,uBAAuB,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;IAEtE,IAAI,CAAC,UAAU,EAAE;QACf,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,gCAAwB,CAAC,kBAAkB,EAAE,CAAC;KAC1F;IAED,MAAM,cAAc,GAAG,iBAAiB,CAAC,aAAa,EAAE,uBAAuB,CAAC,CAAC;IAEjF,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE;QAC1B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;KAC7B;IAED,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,KAAK,MAAM,UAAU,IAAI,cAAc,EAAE;QACvC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,2BAA2B,CAAC,UAAU,EAAE,uBAAuB,EAAE,UAAU,CAAC,CAAC;QAEnH,IAAI,UAAU,EAAE;YACd,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;SAC7B;QAED,IAAI,aAAa,KAAK,gCAAwB,CAAC,cAAc,EAAE;YAC7D,UAAU,GAAG,IAAI,CAAC;SACnB;KACF;IAED,OAAO;QACL,UAAU,EAAE,KAAK;QACjB,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC,gCAAwB,CAAC,cAAc,CAAC,CAAC,CAAC,gCAAwB,CAAC,eAAe;KAC/G,CAAC;AACJ,CAAC;AAnCD,0EAmCC;AAED,SAAS,iBAAiB,CAAC,aAAqB,EAAE,uBAAgD;IAChG,OAAO,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CACzE,uBAAuB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,aAAa,CAAC,CACvF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FeatureFlag } from '../feature-flags/types';
|
|
2
|
+
export type UserEntitlementsContext = {
|
|
3
|
+
features: Record<string, {
|
|
4
|
+
expireTime: number | null;
|
|
5
|
+
linkedPermissions: string[];
|
|
6
|
+
featureFlag?: FeatureFlag;
|
|
7
|
+
}>;
|
|
8
|
+
permissions: Record<string, true>;
|
|
9
|
+
};
|
|
10
|
+
export type EntitlementResult = {
|
|
11
|
+
isEntitled: boolean;
|
|
12
|
+
justification?: NotEntitledJustification;
|
|
13
|
+
};
|
|
14
|
+
export declare enum NotEntitledJustification {
|
|
15
|
+
MISSING_FEATURE = "MISSING_FEATURE",
|
|
16
|
+
MISSING_PERMISSION = "MISSING_PERMISSION",
|
|
17
|
+
BUNDLE_EXPIRED = "BUNDLE_EXPIRED"
|
|
18
|
+
}
|
|
19
|
+
export type CustomAttributes = Record<string, string | number | boolean | Date>;
|
|
20
|
+
export declare const NO_EXPIRATION_TIME = -1;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NO_EXPIRATION_TIME = exports.NotEntitledJustification = void 0;
|
|
4
|
+
var NotEntitledJustification;
|
|
5
|
+
(function (NotEntitledJustification) {
|
|
6
|
+
NotEntitledJustification["MISSING_FEATURE"] = "MISSING_FEATURE";
|
|
7
|
+
NotEntitledJustification["MISSING_PERMISSION"] = "MISSING_PERMISSION";
|
|
8
|
+
NotEntitledJustification["BUNDLE_EXPIRED"] = "BUNDLE_EXPIRED";
|
|
9
|
+
})(NotEntitledJustification = exports.NotEntitledJustification || (exports.NotEntitledJustification = {}));
|
|
10
|
+
exports.NO_EXPIRATION_TIME = -1;
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/user-entitlements/types.ts"],"names":[],"mappings":";;;AAkBA,IAAY,wBAIX;AAJD,WAAY,wBAAwB;IAClC,+DAAmC,CAAA;IACnC,qEAAyC,CAAA;IACzC,6DAAiC,CAAA;AACnC,CAAC,EAJW,wBAAwB,GAAxB,gCAAwB,KAAxB,gCAAwB,QAInC;AAIY,QAAA,kBAAkB,GAAG,CAAC,CAAC,CAAC"}
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.0.0-alpha.7](https://github.com/frontegg/entitlements-javascript-commons/compare/v-1.0.0-alpha.6...v-1.0.0-alpha.7) (2023-10-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **user-entitlements:** export function signature parameters types ([2207986](https://github.com/frontegg/entitlements-javascript-commons/commit/220798611a91875e594c83f76baed6c18f5ac19a))
|
|
7
|
+
|
|
8
|
+
# [1.0.0-alpha.6](https://github.com/frontegg/entitlements-javascript-commons/compare/v-1.0.0-alpha.5...v-1.0.0-alpha.6) (2023-10-10)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **user-entitlements:** add user entitlements evaluation logic ([d924a05](https://github.com/frontegg/entitlements-javascript-commons/commit/d924a056498b4b040dd765b262cde42201644653))
|
|
14
|
+
|
|
1
15
|
# [1.0.0-alpha.5](https://github.com/frontegg/entitlements-javascript-commons/compare/v-1.0.0-alpha.4...v-1.0.0-alpha.5) (2023-10-10)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,3 +2,11 @@ export { FeatureFlagEvaluationResult, FeatureFlag, evaluateFeatureFlag } from '.
|
|
|
2
2
|
export { TreatmentEnum, Rule } from './rules';
|
|
3
3
|
export { Condition } from './conditions';
|
|
4
4
|
export { OperationEnum, ConditionValue } from './operations/types';
|
|
5
|
+
export {
|
|
6
|
+
evaluateIsEntitledToFeature,
|
|
7
|
+
evaluateIsEntitledToPermissions,
|
|
8
|
+
CustomAttributes,
|
|
9
|
+
NotEntitledJustification,
|
|
10
|
+
UserEntitlementsContext,
|
|
11
|
+
EntitlementResult,
|
|
12
|
+
} from './user-entitlements';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CustomAttributes, EntitlementResult, NotEntitledJustification, NO_EXPIRATION_TIME, UserEntitlementsContext } from './types';
|
|
2
|
+
import { evaluateFeatureFlag } from '../feature-flags';
|
|
3
|
+
import { TreatmentEnum } from '../rules';
|
|
4
|
+
export function evaluateIsEntitledToFeature(
|
|
5
|
+
featureKey: string,
|
|
6
|
+
userEntitlementsContext: UserEntitlementsContext,
|
|
7
|
+
attributes: CustomAttributes = {},
|
|
8
|
+
): EntitlementResult {
|
|
9
|
+
const feature = userEntitlementsContext.features[featureKey];
|
|
10
|
+
|
|
11
|
+
let hasExpired = false;
|
|
12
|
+
if (feature && feature.expireTime !== null) {
|
|
13
|
+
hasExpired = feature.expireTime !== NO_EXPIRATION_TIME && feature.expireTime < Date.now();
|
|
14
|
+
|
|
15
|
+
if (!hasExpired) {
|
|
16
|
+
return { isEntitled: true };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (feature && feature.featureFlag) {
|
|
21
|
+
const { treatment } = evaluateFeatureFlag(feature.featureFlag, attributes);
|
|
22
|
+
if (treatment === TreatmentEnum.True) {
|
|
23
|
+
return { isEntitled: true };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { isEntitled: false, justification: hasExpired ? NotEntitledJustification.BUNDLE_EXPIRED : NotEntitledJustification.MISSING_FEATURE };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function evaluateIsEntitledToPermissions(
|
|
31
|
+
permissionKey: string,
|
|
32
|
+
userEntitlementsContext: UserEntitlementsContext,
|
|
33
|
+
attributes: CustomAttributes = {},
|
|
34
|
+
): EntitlementResult {
|
|
35
|
+
const permission = userEntitlementsContext.permissions[permissionKey];
|
|
36
|
+
|
|
37
|
+
if (!permission) {
|
|
38
|
+
return { isEntitled: false, justification: NotEntitledJustification.MISSING_PERMISSION };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const linkedFeatures = getLinkedFeatures(permissionKey, userEntitlementsContext);
|
|
42
|
+
|
|
43
|
+
if (!linkedFeatures.length) {
|
|
44
|
+
return { isEntitled: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let hasExpired = false;
|
|
48
|
+
|
|
49
|
+
for (const featureKey of linkedFeatures) {
|
|
50
|
+
const { isEntitled, justification } = evaluateIsEntitledToFeature(featureKey, userEntitlementsContext, attributes);
|
|
51
|
+
|
|
52
|
+
if (isEntitled) {
|
|
53
|
+
return { isEntitled: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (justification === NotEntitledJustification.BUNDLE_EXPIRED) {
|
|
57
|
+
hasExpired = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
isEntitled: false,
|
|
63
|
+
justification: hasExpired ? NotEntitledJustification.BUNDLE_EXPIRED : NotEntitledJustification.MISSING_FEATURE,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getLinkedFeatures(permissionKey: string, userEntitlementsContext: UserEntitlementsContext): string[] {
|
|
68
|
+
return Object.keys(userEntitlementsContext.features).filter((featureKey) =>
|
|
69
|
+
userEntitlementsContext.features[featureKey].linkedPermissions.includes(permissionKey),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import * as FeatureFlags from '../../feature-flags/feature-flag.evaluator';
|
|
2
|
+
import * as IsEntitledEvaluators from '../is-entitled.evaluator';
|
|
3
|
+
import { TreatmentEnum } from '../../rules';
|
|
4
|
+
import { EntitlementResult, NotEntitledJustification, NO_EXPIRATION_TIME, UserEntitlementsContext } from '../types';
|
|
5
|
+
import { FeatureFlag } from '../../feature-flags/types';
|
|
6
|
+
const mockFeatureFlag: FeatureFlag = {
|
|
7
|
+
on: true,
|
|
8
|
+
defaultTreatment: TreatmentEnum.True,
|
|
9
|
+
offTreatment: TreatmentEnum.False,
|
|
10
|
+
rules: [],
|
|
11
|
+
};
|
|
12
|
+
const truthyEntitlementResult: EntitlementResult = {
|
|
13
|
+
isEntitled: true,
|
|
14
|
+
};
|
|
15
|
+
const falsyEntitlementResultMissingFeature: EntitlementResult = {
|
|
16
|
+
isEntitled: false,
|
|
17
|
+
justification: NotEntitledJustification.MISSING_FEATURE,
|
|
18
|
+
};
|
|
19
|
+
const falsyEntitlementResultBundleExpired: EntitlementResult = {
|
|
20
|
+
isEntitled: false,
|
|
21
|
+
justification: NotEntitledJustification.BUNDLE_EXPIRED,
|
|
22
|
+
};
|
|
23
|
+
const falsyEntitlementResultMissingPermission: EntitlementResult = {
|
|
24
|
+
isEntitled: false,
|
|
25
|
+
justification: NotEntitledJustification.MISSING_PERMISSION,
|
|
26
|
+
};
|
|
27
|
+
describe('evaluateIsEntitledToFeature', () => {
|
|
28
|
+
describe('entitled', () => {
|
|
29
|
+
describe('feature-flag evaluated truthy', () => {
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
jest.spyOn(FeatureFlags, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.True });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('feature granted with valid expiration date', async () => {
|
|
35
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
36
|
+
features: {
|
|
37
|
+
'test-feature': {
|
|
38
|
+
expireTime: new Date().getTime() + 3600,
|
|
39
|
+
linkedPermissions: [],
|
|
40
|
+
featureFlag: mockFeatureFlag,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
permissions: {},
|
|
44
|
+
};
|
|
45
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
48
|
+
});
|
|
49
|
+
test('feature granted with expired expiration date', async () => {
|
|
50
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
51
|
+
features: {
|
|
52
|
+
'test-feature': {
|
|
53
|
+
expireTime: new Date().getTime() - 3600,
|
|
54
|
+
linkedPermissions: [],
|
|
55
|
+
featureFlag: mockFeatureFlag,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
permissions: {},
|
|
59
|
+
};
|
|
60
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
61
|
+
|
|
62
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
63
|
+
});
|
|
64
|
+
test('feature granted with no expiration date', async () => {
|
|
65
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
66
|
+
features: {
|
|
67
|
+
'test-feature': {
|
|
68
|
+
expireTime: NO_EXPIRATION_TIME,
|
|
69
|
+
linkedPermissions: [],
|
|
70
|
+
featureFlag: mockFeatureFlag,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
permissions: {},
|
|
74
|
+
};
|
|
75
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
76
|
+
|
|
77
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
78
|
+
});
|
|
79
|
+
test('feature has not been granted', async () => {
|
|
80
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
81
|
+
features: {
|
|
82
|
+
'test-feature': {
|
|
83
|
+
expireTime: null,
|
|
84
|
+
linkedPermissions: [],
|
|
85
|
+
featureFlag: mockFeatureFlag,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
permissions: {},
|
|
89
|
+
};
|
|
90
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
91
|
+
|
|
92
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('feature-flag evaluated falsy', () => {
|
|
96
|
+
beforeAll(async () => {
|
|
97
|
+
jest.spyOn(FeatureFlags, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.False });
|
|
98
|
+
});
|
|
99
|
+
test('feature granted with no expiration date', async () => {
|
|
100
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
101
|
+
features: {
|
|
102
|
+
'test-feature': {
|
|
103
|
+
expireTime: NO_EXPIRATION_TIME,
|
|
104
|
+
linkedPermissions: [],
|
|
105
|
+
featureFlag: mockFeatureFlag,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
permissions: {},
|
|
109
|
+
};
|
|
110
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
111
|
+
|
|
112
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
113
|
+
});
|
|
114
|
+
test('feature granted with valid expiration date', async () => {
|
|
115
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
116
|
+
features: {
|
|
117
|
+
'test-feature': {
|
|
118
|
+
expireTime: Date.now() + 3600,
|
|
119
|
+
linkedPermissions: [],
|
|
120
|
+
featureFlag: mockFeatureFlag,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
permissions: {},
|
|
124
|
+
};
|
|
125
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
126
|
+
|
|
127
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('no feature flag', () => {
|
|
131
|
+
test('feature granted with no expiration date', async () => {
|
|
132
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
133
|
+
features: {
|
|
134
|
+
'test-feature': {
|
|
135
|
+
expireTime: NO_EXPIRATION_TIME,
|
|
136
|
+
linkedPermissions: [],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
permissions: {},
|
|
140
|
+
};
|
|
141
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
142
|
+
|
|
143
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
144
|
+
});
|
|
145
|
+
test('feature granted with valid expiration date', async () => {
|
|
146
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
147
|
+
features: {
|
|
148
|
+
'test-feature': {
|
|
149
|
+
expireTime: Date.now() + 3600,
|
|
150
|
+
linkedPermissions: [],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
permissions: {},
|
|
154
|
+
};
|
|
155
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('not entitled', () => {
|
|
163
|
+
describe('feature-flag evaluated falsy', () => {
|
|
164
|
+
beforeAll(async () => {
|
|
165
|
+
jest.spyOn(FeatureFlags, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.False });
|
|
166
|
+
});
|
|
167
|
+
test('feature has not been granted', async () => {
|
|
168
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
169
|
+
features: {
|
|
170
|
+
'test-feature': {
|
|
171
|
+
expireTime: null,
|
|
172
|
+
linkedPermissions: [],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
permissions: {},
|
|
176
|
+
};
|
|
177
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
178
|
+
|
|
179
|
+
expect(result).toEqual(falsyEntitlementResultMissingFeature);
|
|
180
|
+
});
|
|
181
|
+
test('feature granted with expired expiration date', async () => {
|
|
182
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
183
|
+
features: {
|
|
184
|
+
'test-feature': {
|
|
185
|
+
expireTime: Date.now() - 3600,
|
|
186
|
+
linkedPermissions: [],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
permissions: {},
|
|
190
|
+
};
|
|
191
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
192
|
+
|
|
193
|
+
expect(result).toEqual(falsyEntitlementResultBundleExpired);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('no feature', () => {
|
|
198
|
+
test('feature does not exist', async () => {
|
|
199
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
200
|
+
features: {},
|
|
201
|
+
permissions: {},
|
|
202
|
+
};
|
|
203
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToFeature('test-feature', userEntitlementContext, {});
|
|
204
|
+
|
|
205
|
+
expect(result).toEqual(falsyEntitlementResultMissingFeature);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('evaluateIsEntitledToPermission', () => {
|
|
212
|
+
describe('entitled', () => {
|
|
213
|
+
test('permission granted, no linked feature/s to it', async () => {
|
|
214
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
215
|
+
features: {},
|
|
216
|
+
permissions: { 'test.permission': true },
|
|
217
|
+
};
|
|
218
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToPermissions(
|
|
219
|
+
'test.permission',
|
|
220
|
+
userEntitlementContext,
|
|
221
|
+
{},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
225
|
+
});
|
|
226
|
+
test('permission granted with linked feature/s, feature is entitled', async () => {
|
|
227
|
+
jest.spyOn(IsEntitledEvaluators, 'evaluateIsEntitledToFeature').mockReturnValue({ isEntitled: true });
|
|
228
|
+
|
|
229
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
230
|
+
features: { 'test-feature': { expireTime: NO_EXPIRATION_TIME, linkedPermissions: ['test.permission'] } },
|
|
231
|
+
permissions: { 'test.permission': true },
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToPermissions(
|
|
235
|
+
'test.permission',
|
|
236
|
+
userEntitlementContext,
|
|
237
|
+
{},
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
expect(result).toEqual(truthyEntitlementResult);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('not entitled', () => {
|
|
245
|
+
test('permission not granted', async () => {
|
|
246
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
247
|
+
features: {},
|
|
248
|
+
permissions: {},
|
|
249
|
+
};
|
|
250
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToPermissions(
|
|
251
|
+
'test.permission',
|
|
252
|
+
userEntitlementContext,
|
|
253
|
+
{},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect(result).toEqual(falsyEntitlementResultMissingPermission);
|
|
257
|
+
});
|
|
258
|
+
test('permission granted with linked feature/s, no feature is entiteld', async () => {
|
|
259
|
+
jest
|
|
260
|
+
.spyOn(IsEntitledEvaluators, 'evaluateIsEntitledToFeature')
|
|
261
|
+
.mockReturnValue({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
262
|
+
|
|
263
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
264
|
+
features: { 'test-feature': { expireTime: null, linkedPermissions: ['test.permission'] } },
|
|
265
|
+
permissions: { 'test.permission': true },
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToPermissions(
|
|
269
|
+
'test.permission',
|
|
270
|
+
userEntitlementContext,
|
|
271
|
+
{},
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(result).toEqual(falsyEntitlementResultMissingFeature);
|
|
275
|
+
});
|
|
276
|
+
test('permission granted with linked feature/s, no feature is entiteld with expired bundle', async () => {
|
|
277
|
+
jest
|
|
278
|
+
.spyOn(IsEntitledEvaluators, 'evaluateIsEntitledToFeature')
|
|
279
|
+
.mockReturnValue({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
283
|
+
features: { 'test-feature': { expireTime: Date.now() - 3600, linkedPermissions: ['test.permission'] } },
|
|
284
|
+
permissions: { 'test.permission': true },
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const result = IsEntitledEvaluators.evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
|
|
288
|
+
|
|
289
|
+
expect(result).toEqual(falsyEntitlementResultBundleExpired);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FeatureFlag } from '../feature-flags/types';
|
|
2
|
+
export type UserEntitlementsContext = {
|
|
3
|
+
features: Record<
|
|
4
|
+
string,
|
|
5
|
+
{
|
|
6
|
+
expireTime: number | null;
|
|
7
|
+
linkedPermissions: string[];
|
|
8
|
+
featureFlag?: FeatureFlag;
|
|
9
|
+
}
|
|
10
|
+
>;
|
|
11
|
+
permissions: Record<string, true>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type EntitlementResult = {
|
|
15
|
+
isEntitled: boolean;
|
|
16
|
+
justification?: NotEntitledJustification;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export enum NotEntitledJustification {
|
|
20
|
+
MISSING_FEATURE = 'MISSING_FEATURE',
|
|
21
|
+
MISSING_PERMISSION = 'MISSING_PERMISSION',
|
|
22
|
+
BUNDLE_EXPIRED = 'BUNDLE_EXPIRED',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CustomAttributes = Record<string, string | number | boolean | Date>;
|
|
26
|
+
|
|
27
|
+
export const NO_EXPIRATION_TIME = -1;
|