@via-profit/ability 3.3.0 → 3.4.0

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
- export type AbilityConditionCodeType = '=' | '<>' | '>' | '<' | '>=' | '<=' | 'in' | 'not in' | 'contains' | 'not contains' | 'length greater than' | 'length less than' | 'length equals';
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';
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';
4
4
  export declare class AbilityCondition extends AbilityCode<AbilityConditionCodeType> {
5
5
  static equals: AbilityCondition;
6
6
  static not_equals: AbilityCondition;
@@ -15,6 +15,8 @@ export declare class AbilityCondition extends AbilityCode<AbilityConditionCodeTy
15
15
  static length_greater_than: AbilityCondition;
16
16
  static length_less_than: AbilityCondition;
17
17
  static length_equals: AbilityCondition;
18
+ static always: AbilityCondition;
19
+ static never: AbilityCondition;
18
20
  static fromLiteral(literal: AbilityConditionLiteralType): AbilityCondition;
19
21
  get literal(): AbilityConditionLiteralType;
20
22
  }
package/dist/index.js CHANGED
@@ -46,6 +46,8 @@ class AbilityCondition extends AbilityCode {
46
46
  static length_greater_than = new AbilityCondition('length greater than');
47
47
  static length_less_than = new AbilityCondition('length less than');
48
48
  static length_equals = new AbilityCondition('length equals');
49
+ static always = new AbilityCondition('always');
50
+ static never = new AbilityCondition('never');
49
51
  static fromLiteral(literal) {
50
52
  switch (literal) {
51
53
  case 'equals':
@@ -72,6 +74,10 @@ class AbilityCondition extends AbilityCode {
72
74
  return this.length_greater_than;
73
75
  case 'length_equals':
74
76
  return this.length_equals;
77
+ case 'always':
78
+ return this.always;
79
+ case 'never':
80
+ return this.never;
75
81
  default:
76
82
  throw new AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
77
83
  }
@@ -627,6 +633,10 @@ class AbilityRule {
627
633
  let is = false;
628
634
  const [subjectValue, resourceValue] = this.extractValues(resource, environment);
629
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
+ }
630
640
  // equals
631
641
  if (AbilityCondition.equals.isEqual(this.condition)) {
632
642
  is = subjectValue === resourceValue;
@@ -1221,6 +1231,8 @@ class AbilityDSLToken extends AbilityCode {
1221
1231
  static LEN_LT = 'LEN_LT';
1222
1232
  static LEN_EQ = 'LEN_EQ';
1223
1233
  static NOT_EQ = 'NOT_EQ';
1234
+ static ALWAYS = 'ALWAYS';
1235
+ static NEVER = 'NEVER';
1224
1236
  static STRING = 'STRING';
1225
1237
  static NUMBER = 'NUMBER';
1226
1238
  static BOOLEAN = 'BOOLEAN';
@@ -1263,6 +1275,8 @@ class AbilityDSLLexer {
1263
1275
  'is',
1264
1276
  'or',
1265
1277
  'than',
1278
+ 'always',
1279
+ 'never',
1266
1280
  ]);
1267
1281
  constructor(input) {
1268
1282
  this.input = input;
@@ -1408,6 +1422,12 @@ class AbilityDSLLexer {
1408
1422
  }
1409
1423
  }
1410
1424
  const word = this.input.slice(start, this.pos);
1425
+ if (word === 'always') {
1426
+ return new AbilityDSLToken(AbilityDSLToken.ALWAYS, word, startLine, startColumn);
1427
+ }
1428
+ if (word === 'never') {
1429
+ return new AbilityDSLToken(AbilityDSLToken.NEVER, word, startLine, startColumn);
1430
+ }
1411
1431
  // Если есть точка — это путь (identifier или permission)
1412
1432
  if (word.includes('.')) {
1413
1433
  const last = this.tokens[this.tokens.length - 1];
@@ -1670,7 +1690,9 @@ class AbilityDSLParser {
1670
1690
  if (this.isStartOfGroup() || this.isStartOfPolicy()) {
1671
1691
  break;
1672
1692
  }
1673
- if (this.check(AbilityDSLToken.IDENTIFIER)) {
1693
+ if (this.check(AbilityDSLToken.IDENTIFIER) ||
1694
+ this.check(AbilityDSLToken.ALWAYS) ||
1695
+ this.check(AbilityDSLToken.NEVER)) {
1674
1696
  group.addRule(this.parseRule());
1675
1697
  }
1676
1698
  else {
@@ -1687,7 +1709,7 @@ class AbilityDSLParser {
1687
1709
  parseGroup() {
1688
1710
  this.consumeLeadingComments();
1689
1711
  const meta = this.takeAnnotations();
1690
- const compareToken = this.consumeOneOf([AbilityDSLToken.ALL, AbilityDSLToken.ANY], 'Expected "all" or "any"');
1712
+ const compareToken = this.consumeOneOf([AbilityDSLToken.ALL, AbilityDSLToken.ANY, AbilityDSLToken.ALWAYS, AbilityDSLToken.NEVER], 'Expected "all" or "any" or "always" or "never"');
1691
1713
  const compareMethod = compareToken.code === AbilityDSLToken.ALL ? AbilityCompare.and : AbilityCompare.or;
1692
1714
  if (this.check(AbilityDSLToken.OF)) {
1693
1715
  this.advance();
@@ -1717,11 +1739,26 @@ class AbilityDSLParser {
1717
1739
  parseRule() {
1718
1740
  this.consumeLeadingComments();
1719
1741
  const meta = this.takeAnnotations();
1720
- if (!this.check(AbilityDSLToken.IDENTIFIER)) {
1721
- this.syntaxError(`Expected identifier, got ${this.peek().code}`, this.peek());
1742
+ // if (this.check(AbilityDSLToken.ALWAYS) || this.check(AbilityDSLToken.NEVER)) {
1743
+ // // Checking that there are no extra tokens after the value
1744
+ // // (skip comments)
1745
+ // this.consumeLeadingComments();
1746
+ // const specOperator = this.consume();
1747
+ // // return new AbilityRule({
1748
+ // // subject: '',
1749
+ // // resource,
1750
+ // // condition,
1751
+ // // name: meta.name,
1752
+ // // });
1753
+ // }
1754
+ const isNeverAlways = this.check(AbilityDSLToken.ALWAYS) || this.check(AbilityDSLToken.NEVER);
1755
+ if (!isNeverAlways && !this.check(AbilityDSLToken.IDENTIFIER)) {
1756
+ this.syntaxError(`Expected identifier, but got ${this.peek().code}`, this.peek());
1722
1757
  }
1723
1758
  // Subject (e.g., "user.roles")
1724
- const subject = this.consume(AbilityDSLToken.IDENTIFIER, 'Expected field').value;
1759
+ const subject = isNeverAlways
1760
+ ? ''
1761
+ : this.consume(AbilityDSLToken.IDENTIFIER, 'Expected field').value;
1725
1762
  // Operator (e.g., "contains", "equals", "is not null")
1726
1763
  const { condition, operator } = this.parseConditionOperator();
1727
1764
  let resource;
@@ -1729,7 +1766,9 @@ class AbilityDSLParser {
1729
1766
  // Special operators that don't consume a value token.
1730
1767
  if (operator === AbilityDSLToken.EQ_NULL ||
1731
1768
  operator === AbilityDSLToken.NOT_EQ_NULL ||
1732
- operator === AbilityDSLToken.NULL) {
1769
+ operator === AbilityDSLToken.NULL ||
1770
+ operator === AbilityDSLToken.ALWAYS ||
1771
+ operator === AbilityDSLToken.NEVER) {
1733
1772
  resource = null;
1734
1773
  }
1735
1774
  else {
@@ -1761,6 +1800,16 @@ class AbilityDSLParser {
1761
1800
  */
1762
1801
  parseConditionOperator() {
1763
1802
  const savedPos = this.pos;
1803
+ // "always"
1804
+ if (this.matchWord('always')) {
1805
+ return { condition: AbilityCondition.always, operator: AbilityDSLToken.ALWAYS };
1806
+ }
1807
+ this.pos = savedPos;
1808
+ // "never"
1809
+ if (this.matchWord('never')) {
1810
+ return { condition: AbilityCondition.never, operator: AbilityDSLToken.NEVER };
1811
+ }
1812
+ this.pos = savedPos;
1764
1813
  // "length equals"
1765
1814
  if (this.matchWord('length') && this.matchWord('equals')) {
1766
1815
  return { condition: AbilityCondition.length_equals, operator: AbilityDSLToken.LEN_EQ };
@@ -1956,7 +2005,10 @@ class AbilityDSLParser {
1956
2005
  return false;
1957
2006
  }
1958
2007
  const token = this.peek();
1959
- if ((token.code === AbilityDSLToken.KEYWORD || token.code === AbilityDSLToken.IDENTIFIER) &&
2008
+ if ((token.code === AbilityDSLToken.KEYWORD ||
2009
+ token.code === AbilityDSLToken.IDENTIFIER ||
2010
+ token.code === AbilityDSLToken.ALWAYS ||
2011
+ token.code === AbilityDSLToken.NEVER) &&
1960
2012
  token.value === word) {
1961
2013
  this.advance();
1962
2014
  return true;
@@ -2101,9 +2153,6 @@ class AbilityDSLParser {
2101
2153
  }
2102
2154
  throw new AbilityDSLSyntaxError(token.line, token.column, context + '\n', finalDetails);
2103
2155
  }
2104
- getLine(lineNumber) {
2105
- return this.dsl.split(/\r?\n/)[lineNumber - 1] ?? '';
2106
- }
2107
2156
  suggest(actual, expectedTypes) {
2108
2157
  const candidates = [];
2109
2158
  for (const type of expectedTypes) {
@@ -72,7 +72,6 @@ export declare class AbilityDSLParser<Resource extends ResourceObject = Record<s
72
72
  private processCommentToken;
73
73
  private takeAnnotations;
74
74
  private syntaxError;
75
- private getLine;
76
75
  private suggest;
77
76
  private levenshteinDistance;
78
77
  private consumeOneOf;
@@ -1,5 +1,5 @@
1
1
  import AbilityCode from '../../core/AbilityCode';
2
- export type TokenType = 'EFFECT' | 'IF' | 'PERMISSION' | 'IDENTIFIER' | 'COLON' | 'COMMA' | 'DOT' | 'LBRACKET' | 'RBRACKET' | 'ALL' | 'ANY' | 'OF' | 'EOF' | 'COMMENT' | 'EQ' | 'CONTAINS' | 'IN' | 'NOT_IN' | 'NOT_CONTAINS' | 'GT' | 'GTE' | 'LT' | 'LTE' | 'NULL' | 'EQ_NULL' | 'NOT_EQ_NULL' | 'NOT_EQ' | 'LEN_GT' | 'LEN_LT' | 'LEN_EQ' | 'STRING' | 'NUMBER' | 'BOOLEAN' | 'SYMBOL' | 'KEYWORD' | 'UNKNOWN';
2
+ export type TokenType = 'EFFECT' | 'IF' | 'PERMISSION' | 'IDENTIFIER' | 'COLON' | 'COMMA' | 'DOT' | 'LBRACKET' | 'RBRACKET' | 'ALL' | 'ANY' | 'OF' | 'EOF' | 'COMMENT' | 'EQ' | 'CONTAINS' | 'IN' | 'NOT_IN' | 'NOT_CONTAINS' | 'GT' | 'GTE' | 'LT' | 'LTE' | 'NULL' | 'EQ_NULL' | 'NOT_EQ_NULL' | 'NOT_EQ' | 'LEN_GT' | 'LEN_LT' | 'LEN_EQ' | 'ALWAYS' | 'NEVER' | 'STRING' | 'NUMBER' | 'BOOLEAN' | 'SYMBOL' | 'KEYWORD' | 'UNKNOWN';
3
3
  /**
4
4
  * Represents a single token produced by the Ability DSL lexer.
5
5
  * Each token carries a type (e.g., EFFECT, IDENTIFIER, STRING) and its raw string value.
@@ -47,6 +47,8 @@ export declare class AbilityDSLToken<Code extends TokenType = TokenType> extends
47
47
  static readonly LEN_LT: TokenType;
48
48
  static readonly LEN_EQ: TokenType;
49
49
  static readonly NOT_EQ: TokenType;
50
+ static readonly ALWAYS: TokenType;
51
+ static readonly NEVER: TokenType;
50
52
  static readonly STRING: TokenType;
51
53
  static readonly NUMBER: TokenType;
52
54
  static readonly BOOLEAN: TokenType;
package/package.json CHANGED
@@ -1,73 +1,73 @@
1
- {
2
- "name": "@via-profit/ability",
3
- "support": "https://via-profit.ru",
4
- "version": "3.3.0",
5
- "description": "Via-Profit Ability service",
6
- "keywords": [
7
- "ability",
8
- "access",
9
- "via-profit"
10
- ],
11
- "main": "./dist/index.js",
12
- "engines": {
13
- "node": ">= 17.0.0",
14
- "npm": ">= 8.19.3"
15
- },
16
- "files": [
17
- "dist",
18
- "README.md",
19
- "LICENSE",
20
- "CHANGELOG.md",
21
- "CONTRIBUTING.md",
22
- "SECURITY.md"
23
- ],
24
- "scripts": {
25
- "build": "rollup -c",
26
- "build:dev": "cross-env NODE_ENV=development rollup -c -w",
27
- "bench": "npm run build && node ./bench/benchmark.js",
28
- "test": "jest",
29
- "lint": "tsc --noEmit && eslint --fix .",
30
- "pretty": "prettier --write ./src"
31
- },
32
- "repository": {
33
- "type": "git",
34
- "url": "https://github.com/via-profit/ability.git"
35
- },
36
- "author": {
37
- "name": "Via Profit",
38
- "url": "https://dev.via-profit.ru"
39
- },
40
- "contributors": [
41
- "Vasily Novosad <delhsmail@gmail.com>",
42
- "Pavel Natalin <trubonru@gmail.com>"
43
- ],
44
- "license": "MIT",
45
- "devDependencies": {
46
- "@eslint/js": "^9.13.0",
47
- "@jagi/jest-transform-graphql": "^1.0.2",
48
- "@jest/types": "^29.6.3",
49
- "@rollup/plugin-alias": "^6.0.0",
50
- "@rollup/plugin-commonjs": "^29.0.2",
51
- "@rollup/plugin-node-resolve": "^16.0.3",
52
- "@rollup/plugin-typescript": "^12.3.0",
53
- "@types/jest": "^29.5.13",
54
- "@types/node": "^22.7.7",
55
- "@types/nodemon": "^1.19.6",
56
- "concurrently": "^9.0.1",
57
- "cross-env": "^7.0.3",
58
- "eslint": "^9.13.0",
59
- "globals": "^15.11.0",
60
- "jest": "^29.7.0",
61
- "jest-transform-graphql": "^2.1.0",
62
- "nodemon": "^3.1.7",
63
- "prettier": "^3.3.3",
64
- "rollup": "^4.60.0",
65
- "rollup-plugin-copy": "^3.5.0",
66
- "tinybench": "^6.0.0",
67
- "ts-jest": "^29.2.5",
68
- "ts-loader": "^9.5.1",
69
- "ts-node": "^10.9.2",
70
- "typescript": "^5.6.3",
71
- "typescript-eslint": "^8.10.0"
72
- }
73
- }
1
+ {
2
+ "name": "@via-profit/ability",
3
+ "support": "https://via-profit.ru",
4
+ "version": "3.4.0",
5
+ "description": "Via-Profit Ability service",
6
+ "keywords": [
7
+ "ability",
8
+ "access",
9
+ "via-profit"
10
+ ],
11
+ "main": "./dist/index.js",
12
+ "engines": {
13
+ "node": ">= 17.0.0",
14
+ "npm": ">= 8.19.3"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE",
20
+ "CHANGELOG.md",
21
+ "CONTRIBUTING.md",
22
+ "SECURITY.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "rollup -c",
26
+ "build:dev": "cross-env NODE_ENV=development rollup -c -w",
27
+ "bench": "npm run build && node ./bench/benchmark.js",
28
+ "test": "jest",
29
+ "lint": "tsc --noEmit && eslint --fix .",
30
+ "pretty": "prettier --write ./src"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/via-profit/ability.git"
35
+ },
36
+ "author": {
37
+ "name": "Via Profit",
38
+ "url": "https://dev.via-profit.ru"
39
+ },
40
+ "contributors": [
41
+ "Vasily Novosad <delhsmail@gmail.com>",
42
+ "Pavel Natalin <trubonru@gmail.com>"
43
+ ],
44
+ "license": "MIT",
45
+ "devDependencies": {
46
+ "@eslint/js": "^9.13.0",
47
+ "@jagi/jest-transform-graphql": "^1.0.2",
48
+ "@jest/types": "^29.6.3",
49
+ "@rollup/plugin-alias": "^6.0.0",
50
+ "@rollup/plugin-commonjs": "^29.0.2",
51
+ "@rollup/plugin-node-resolve": "^16.0.3",
52
+ "@rollup/plugin-typescript": "^12.3.0",
53
+ "@types/jest": "^29.5.13",
54
+ "@types/node": "^22.7.7",
55
+ "@types/nodemon": "^1.19.6",
56
+ "concurrently": "^9.0.1",
57
+ "cross-env": "^7.0.3",
58
+ "eslint": "^9.13.0",
59
+ "globals": "^15.11.0",
60
+ "jest": "^29.7.0",
61
+ "jest-transform-graphql": "^2.1.0",
62
+ "nodemon": "^3.1.7",
63
+ "prettier": "^3.3.3",
64
+ "rollup": "^4.60.0",
65
+ "rollup-plugin-copy": "^3.5.0",
66
+ "tinybench": "^6.0.0",
67
+ "ts-jest": "^29.2.5",
68
+ "ts-loader": "^9.5.1",
69
+ "ts-node": "^10.9.2",
70
+ "typescript": "^5.6.3",
71
+ "typescript-eslint": "^8.10.0"
72
+ }
73
+ }