@via-profit/ability 3.4.0 → 3.4.1

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/SECURITY.md CHANGED
@@ -1,33 +1,33 @@
1
- # Security Policy
2
-
3
- ## Reporting a Vulnerability
4
-
5
- I take the security of `@via-profit/ability` seriously. If you discover a security vulnerability, please report it responsibly.
6
-
7
- ### How to Report a Vulnerability
8
-
9
- **Please DO NOT create a public GitHub issue for security vulnerabilities.**
10
-
11
- Instead, send the details directly to me:
12
-
13
- - **Email**: [delhsmail@gmail.com](mailto:delhsmail@gmail.com)
14
- - **Author**: Vasily Novosad
15
- - **Timezone**: UTC+5 (for coordinating response time)
16
-
17
- ### What to Include
18
-
19
- To help me address the issue quickly, please include:
20
-
21
- - Description of the vulnerability
22
- - Steps to reproduce (if applicable)
23
- - Potential impact
24
- - Suggestions for fixing (if any)
25
-
26
- ### Process
27
-
28
- 1. I will acknowledge your report within 48 hours
29
- 2. I will assess the vulnerability
30
- 3. Work on a fix will begin depending on severity
31
- 4. After the fix is released, I will notify you and acknowledge your contribution (if you agree)
32
-
33
- Thank you for helping keep this project secure!
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ I take the security of `@via-profit/ability` seriously. If you discover a security vulnerability, please report it responsibly.
6
+
7
+ ### How to Report a Vulnerability
8
+
9
+ **Please DO NOT create a public GitHub issue for security vulnerabilities.**
10
+
11
+ Instead, send the details directly to me:
12
+
13
+ - **Email**: [delhsmail@gmail.com](mailto:delhsmail@gmail.com)
14
+ - **Author**: Vasily Novosad
15
+ - **Timezone**: UTC+5 (for coordinating response time)
16
+
17
+ ### What to Include
18
+
19
+ To help me address the issue quickly, please include:
20
+
21
+ - Description of the vulnerability
22
+ - Steps to reproduce (if applicable)
23
+ - Potential impact
24
+ - Suggestions for fixing (if any)
25
+
26
+ ### Process
27
+
28
+ 1. I will acknowledge your report within 48 hours
29
+ 2. I will assess the vulnerability
30
+ 3. Work on a fix will begin depending on severity
31
+ 4. After the fix is released, I will notify you and acknowledge your contribution (if you agree)
32
+
33
+ Thank you for helping keep this project secure!
@@ -1,6 +1,6 @@
1
1
  import AbilityCode from './AbilityCode';
2
2
  export type AbilityConditionCodeType = '=' | '<>' | '>' | '<' | '>=' | '<=' | 'in' | 'not in' | 'contains' | 'not contains' | 'length greater than' | 'length less than' | 'length equals' | 'always' | 'never';
3
- export type AbilityConditionLiteralType = 'equals' | 'not_equals' | 'contains' | 'no_contains' | 'in' | 'not_in' | 'greater_than' | 'less_than' | 'less_or_equal' | 'greater_or_equal' | 'length_greater_than' | 'length_less_than' | 'length_equals' | 'always' | 'never';
3
+ export type AbilityConditionLiteralType = 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'greater_than' | 'less_than' | 'less_or_equal' | 'greater_or_equal' | 'length_greater_than' | 'length_less_than' | 'length_equals' | 'always' | 'never';
4
4
  export declare class AbilityCondition extends AbilityCode<AbilityConditionCodeType> {
5
5
  static equals: AbilityCondition;
6
6
  static not_equals: AbilityCondition;
@@ -1,6 +1,6 @@
1
1
  export declare class AbilityError extends Error {
2
- constructor(message: string);
2
+ constructor(message: string, options?: ErrorOptions);
3
3
  }
4
4
  export declare class AbilityParserError extends Error {
5
- constructor(message: string);
5
+ constructor(message: string, options?: ErrorOptions);
6
6
  }
@@ -1,5 +1,5 @@
1
1
  import AbilityMatch from './AbilityMatch';
2
- import AbilityCondition, { AbilityConditionCodeType } from './AbilityCondition';
2
+ import { AbilityCondition, AbilityConditionCodeType } from './AbilityCondition';
3
3
  export type AbilityRuleConfig = {
4
4
  readonly id?: string | null;
5
5
  readonly name?: string | null;
@@ -42,6 +42,11 @@ export declare class AbilityRule<Resources extends object = object, Environment
42
42
  * @param params
43
43
  */
44
44
  constructor(params: AbilityRuleConstructorProps);
45
+ private isPrimitive;
46
+ private isNumber;
47
+ private isString;
48
+ private valueLen;
49
+ private operatorHandlers;
45
50
  /**
46
51
  * Check if the rule is matched
47
52
  * @param resource - The resource to check
package/dist/index.js CHANGED
@@ -22,75 +22,116 @@ class AbilityCompare extends AbilityCode {
22
22
  }
23
23
 
24
24
  class AbilityError extends Error {
25
- constructor(message) {
26
- super(message);
25
+ constructor(message, options) {
26
+ super(message, options);
27
+ this.name = 'AbilityError';
28
+ if (Error.captureStackTrace) {
29
+ Error.captureStackTrace(this, this.constructor);
30
+ }
27
31
  }
28
32
  }
29
33
  class AbilityParserError extends Error {
30
- constructor(message) {
31
- super(message);
34
+ constructor(message, options) {
35
+ super(message, options);
36
+ this.name = 'AbilityParserError';
37
+ if (Error.captureStackTrace) {
38
+ Error.captureStackTrace(this, this.constructor);
39
+ }
32
40
  }
33
41
  }
34
42
 
35
43
  class AbilityCondition extends AbilityCode {
36
- static equals = new AbilityCondition('=');
37
- static not_equals = new AbilityCondition('<>');
38
- static greater_than = new AbilityCondition('>');
39
- static less_than = new AbilityCondition('<');
40
- static less_or_equal = new AbilityCondition('<=');
41
- static greater_or_equal = new AbilityCondition('>=');
42
- static in = new AbilityCondition('in');
43
- static not_in = new AbilityCondition('not in');
44
- static contains = new AbilityCondition('contains');
45
- static not_contains = new AbilityCondition('not contains');
46
- static length_greater_than = new AbilityCondition('length greater than');
47
- static length_less_than = new AbilityCondition('length less than');
48
- static length_equals = new AbilityCondition('length equals');
49
- static always = new AbilityCondition('always');
50
- static never = new AbilityCondition('never');
44
+ static equals;
45
+ static not_equals;
46
+ static greater_than;
47
+ static less_than;
48
+ static less_or_equal;
49
+ static greater_or_equal;
50
+ static in;
51
+ static not_in;
52
+ static contains;
53
+ static not_contains;
54
+ static length_greater_than;
55
+ static length_less_than;
56
+ static length_equals;
57
+ static always;
58
+ static never;
59
+ static {
60
+ this.equals = new AbilityCondition('=');
61
+ this.not_equals = new AbilityCondition('<>');
62
+ this.greater_than = new AbilityCondition('>');
63
+ this.less_than = new AbilityCondition('<');
64
+ this.less_or_equal = new AbilityCondition('<=');
65
+ this.greater_or_equal = new AbilityCondition('>=');
66
+ this.in = new AbilityCondition('in');
67
+ this.not_in = new AbilityCondition('not in');
68
+ this.contains = new AbilityCondition('contains');
69
+ this.not_contains = new AbilityCondition('not contains');
70
+ this.length_greater_than = new AbilityCondition('length greater than');
71
+ this.length_less_than = new AbilityCondition('length less than');
72
+ this.length_equals = new AbilityCondition('length equals');
73
+ this.always = new AbilityCondition('always');
74
+ this.never = new AbilityCondition('never');
75
+ }
51
76
  static fromLiteral(literal) {
52
- switch (literal) {
53
- case 'equals':
54
- return this.equals;
55
- case 'not_equals':
56
- return this.not_equals;
57
- case 'greater_than':
58
- return this.greater_than;
59
- case 'less_than':
60
- return this.less_than;
61
- case 'less_or_equal':
62
- return this.less_or_equal;
63
- case 'greater_or_equal':
64
- return this.greater_or_equal;
65
- case 'contains':
66
- return this.contains;
67
- case 'no_contains':
68
- return this.not_contains;
77
+ const map = {
78
+ equals: this.equals,
79
+ not_equals: this.not_equals,
80
+ greater_than: this.greater_than,
81
+ less_than: this.less_than,
82
+ less_or_equal: this.less_or_equal,
83
+ greater_or_equal: this.greater_or_equal,
84
+ in: this.in,
85
+ not_in: this.not_in,
86
+ contains: this.contains,
87
+ not_contains: this.not_contains,
88
+ length_greater_than: this.length_greater_than,
89
+ length_equals: this.length_equals,
90
+ always: this.always,
91
+ never: this.never,
92
+ length_less_than: this.length_less_than,
93
+ };
94
+ const condition = map[literal];
95
+ if (!condition) {
96
+ throw new AbilityParserError(`Literal "${literal}" does not found in AbilityCondition class`);
97
+ }
98
+ return condition;
99
+ }
100
+ get literal() {
101
+ switch (this.code) {
102
+ case '=':
103
+ return 'equals';
104
+ case '<>':
105
+ return 'not_equals';
106
+ case '>':
107
+ return 'greater_than';
108
+ case '<':
109
+ return 'less_than';
110
+ case '>=':
111
+ return 'greater_or_equal';
112
+ case '<=':
113
+ return 'less_or_equal';
69
114
  case 'in':
70
- return this.in;
71
- case 'not_in':
72
- return this.not_in;
73
- case 'length_greater_than':
74
- return this.length_greater_than;
75
- case 'length_equals':
76
- return this.length_equals;
115
+ return 'in';
116
+ case 'not in':
117
+ return 'not_in';
118
+ case 'contains':
119
+ return 'contains';
120
+ case 'not contains':
121
+ return 'not_contains';
122
+ case 'length greater than':
123
+ return 'length_greater_than';
124
+ case 'length less than':
125
+ return 'length_less_than';
126
+ case 'length equals':
127
+ return 'length_equals';
77
128
  case 'always':
78
- return this.always;
129
+ return 'always';
79
130
  case 'never':
80
- return this.never;
131
+ return 'never';
81
132
  default:
82
- throw new AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
83
- }
84
- }
85
- get literal() {
86
- const literal = Object.keys(AbilityCondition).find(member => {
87
- const val = AbilityCondition[member];
88
- return val.code === this.code;
89
- });
90
- if (typeof literal === 'undefined') {
91
- throw new Error(`Literal value does not found in class AbilityCondition`);
133
+ throw new Error(`Unknown condition code: ${this.code}`);
92
134
  }
93
- return literal;
94
135
  }
95
136
  }
96
137
 
@@ -624,149 +665,122 @@ class AbilityRule {
624
665
  this.resource = resource;
625
666
  this.condition = condition;
626
667
  }
627
- /**
628
- * Check if the rule is matched
629
- * @param resource - The resource to check
630
- * @param environment
631
- */
632
- async check(resource, environment) {
633
- let is = false;
634
- const [subjectValue, resourceValue] = this.extractValues(resource, environment);
635
- const isValue = (v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
636
- // always
637
- if (AbilityCondition.always.isEqual(this.condition)) {
638
- is = true;
639
- }
640
- // equals
641
- if (AbilityCondition.equals.isEqual(this.condition)) {
642
- is = subjectValue === resourceValue;
643
- }
644
- // not equals
645
- if (AbilityCondition.not_equals.isEqual(this.condition)) {
646
- is = subjectValue !== resourceValue;
647
- }
648
- // less than
649
- if (AbilityCondition.less_than.isEqual(this.condition)) {
650
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
651
- is = subjectValue < resourceValue;
652
- }
653
- }
654
- // less or equal
655
- if (AbilityCondition.less_or_equal.isEqual(this.condition)) {
656
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
657
- is = subjectValue <= resourceValue;
658
- }
659
- }
660
- // more than
661
- if (AbilityCondition.greater_than.isEqual(this.condition)) {
662
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
663
- is = subjectValue > resourceValue;
664
- }
665
- }
666
- // more or equal
667
- if (AbilityCondition.greater_or_equal.isEqual(this.condition)) {
668
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
669
- is = subjectValue >= resourceValue;
670
- }
671
- }
672
- // in
673
- if (AbilityCondition.in.isEqual(this.condition)) {
674
- // value in array
675
- if (isValue(subjectValue) && Array.isArray(resourceValue)) {
676
- is = resourceValue.includes(subjectValue);
677
- }
678
- // array intersects array
679
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
680
- is = subjectValue.some(v => resourceValue.includes(v));
681
- }
682
- }
683
- // not in
684
- if (AbilityCondition.not_in.isEqual(this.condition)) {
685
- if (isValue(subjectValue) && Array.isArray(resourceValue)) {
686
- is = !resourceValue.includes(subjectValue);
687
- }
688
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
689
- is = !subjectValue.some(v => resourceValue.includes(v));
690
- }
691
- }
692
- // contains
693
- if (AbilityCondition.contains.isEqual(this.condition)) {
694
- // array contains value
695
- if (Array.isArray(subjectValue) && isValue(resourceValue)) {
696
- is = subjectValue.includes(resourceValue);
697
- }
698
- // array intersects array
699
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
700
- is = subjectValue.some(v => resourceValue.includes(v));
668
+ isPrimitive(v) {
669
+ return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
670
+ }
671
+ isNumber(v) {
672
+ return typeof v === 'number';
673
+ }
674
+ isString(v) {
675
+ return typeof v === 'string';
676
+ }
677
+ valueLen = (v) => this.isString(v) || Array.isArray(v) ? v.length : null;
678
+ operatorHandlers = {
679
+ [AbilityCondition.always.literal]: () => true,
680
+ [AbilityCondition.never.literal]: () => false,
681
+ [AbilityCondition.equals.literal]: (a, b) => a === b,
682
+ [AbilityCondition.not_equals.literal]: (a, b) => a !== b,
683
+ [AbilityCondition.contains.literal]: (a, b) => {
684
+ if (Array.isArray(a) && this.isPrimitive(b)) {
685
+ return a.includes(b);
701
686
  }
702
- }
703
- // not contains
704
- if (AbilityCondition.not_contains.isEqual(this.condition)) {
705
- if (Array.isArray(subjectValue) && isValue(resourceValue)) {
706
- is = !subjectValue.includes(resourceValue);
687
+ if (Array.isArray(a) && Array.isArray(b)) {
688
+ return a.some(v => b.includes(v));
707
689
  }
708
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
709
- is = !subjectValue.some(v => resourceValue.includes(v));
690
+ return false;
691
+ },
692
+ [AbilityCondition.not_contains.literal]: (a, b) => {
693
+ if (Array.isArray(a) && this.isPrimitive(b)) {
694
+ return !a.includes(b);
710
695
  }
711
- }
712
- // length equals
713
- if (AbilityCondition.length_equals.isEqual(this.condition)) {
714
- // foo.bar == n
715
- if (isValue(subjectValue) && typeof resourceValue === 'number') {
716
- is = String(subjectValue).length === resourceValue;
696
+ if (Array.isArray(a) && Array.isArray(b)) {
697
+ return !a.some(v => b.includes(v));
717
698
  }
718
- // ['foo', 'bar'] = n
719
- else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
720
- is = subjectValue.length === resourceValue;
699
+ return false;
700
+ },
701
+ [AbilityCondition.in.literal]: (a, b) => {
702
+ if (this.isPrimitive(a) && Array.isArray(b)) {
703
+ return b.includes(a);
721
704
  }
722
- // ['foo', 'bar'] = ['baz', 'taz']
723
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
724
- is = subjectValue.length === resourceValue.length;
705
+ if (Array.isArray(a) && Array.isArray(b)) {
706
+ return a.some(v => b.includes(v));
725
707
  }
726
- // 'foo' = 'bar'
727
- else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
728
- is = subjectValue.length === resourceValue.length;
708
+ return false;
709
+ },
710
+ [AbilityCondition.not_in.literal]: (a, b) => {
711
+ if (this.isPrimitive(a) && Array.isArray(b)) {
712
+ return !b.includes(a);
729
713
  }
730
- }
731
- // length greater than
732
- if (AbilityCondition.length_greater_than.isEqual(this.condition)) {
733
- // foo.bar > n
734
- if (isValue(subjectValue) && typeof resourceValue === 'number') {
735
- is = String(subjectValue).length > resourceValue;
714
+ if (Array.isArray(a) && Array.isArray(b)) {
715
+ return !a.some(v => b.includes(v));
736
716
  }
737
- // ['foo', 'bar'] > n
738
- else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
739
- is = subjectValue.length > resourceValue;
717
+ return false;
718
+ },
719
+ [AbilityCondition.greater_than.literal]: (a, b) => {
720
+ return this.isNumber(a) && this.isNumber(b) ? a > b : false;
721
+ },
722
+ [AbilityCondition.less_than.literal]: (a, b) => {
723
+ return this.isNumber(a) && this.isNumber(b) ? a < b : false;
724
+ },
725
+ [AbilityCondition.greater_or_equal.literal]: (a, b) => {
726
+ return this.isNumber(a) && this.isNumber(b) ? a >= b : false;
727
+ },
728
+ [AbilityCondition.less_or_equal.literal]: (a, b) => {
729
+ return this.isNumber(a) && this.isNumber(b) ? a <= b : false;
730
+ },
731
+ [AbilityCondition.length_greater_than.literal]: (a, b) => {
732
+ const alen = this.valueLen(a);
733
+ if (alen === null) {
734
+ return false;
735
+ }
736
+ if (this.isNumber(b)) {
737
+ return alen > b;
738
+ }
739
+ const bLen = this.valueLen(b);
740
+ if (bLen !== null) {
741
+ return alen > bLen;
740
742
  }
741
- // ['foo', 'bar'] > ['baz', 'taz']
742
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
743
- is = subjectValue.length > resourceValue.length;
743
+ return false;
744
+ },
745
+ [AbilityCondition.length_less_than.literal]: (a, b) => {
746
+ const alen = this.valueLen(a);
747
+ if (alen === null) {
748
+ return false;
744
749
  }
745
- // 'foo' > 'bar'
746
- else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
747
- is = subjectValue.length > resourceValue.length;
750
+ if (this.isNumber(b)) {
751
+ return alen < b;
748
752
  }
749
- }
750
- // length greater than
751
- if (AbilityCondition.length_less_than.isEqual(this.condition)) {
752
- // foo.bar < n
753
- if (isValue(subjectValue) && typeof resourceValue === 'number') {
754
- is = String(subjectValue).length < resourceValue;
753
+ const bLen = this.valueLen(b);
754
+ if (bLen !== null) {
755
+ return alen < bLen;
755
756
  }
756
- // ['foo', 'bar'] < n
757
- else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
758
- is = subjectValue.length < resourceValue;
757
+ return false;
758
+ },
759
+ [AbilityCondition.length_equals.literal]: (a, b) => {
760
+ const alen = this.valueLen(a);
761
+ if (alen === null) {
762
+ return false;
759
763
  }
760
- // ['foo', 'bar'] < ['baz', 'taz']
761
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
762
- is = subjectValue.length < resourceValue.length;
764
+ if (this.isNumber(b)) {
765
+ return alen === b;
763
766
  }
764
- // 'foo' < 'bar'
765
- else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
766
- is = subjectValue.length < resourceValue.length;
767
+ const bLen = this.valueLen(b);
768
+ if (bLen !== null) {
769
+ return alen === bLen;
767
770
  }
768
- }
769
- this.state = is ? AbilityMatch.match : AbilityMatch.mismatch;
771
+ return false;
772
+ },
773
+ };
774
+ /**
775
+ * Check if the rule is matched
776
+ * @param resource - The resource to check
777
+ * @param environment
778
+ */
779
+ async check(resource, environment) {
780
+ const [subjectValue, resourceValue] = this.extractValues(resource, environment);
781
+ const handler = this.operatorHandlers[this.condition.literal];
782
+ const result = handler(subjectValue, resourceValue);
783
+ this.state = result ? AbilityMatch.match : AbilityMatch.mismatch;
770
784
  return this.state;
771
785
  }
772
786
  /**