@via-profit/ability 3.7.1 → 3.7.4

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/README.md CHANGED
@@ -1,7 +1,6 @@
1
- # @via-profit/Ability
1
+ # @via-profit/ability
2
2
 
3
- > A set of services that partially implement the [Attribute Based Access Control](https://en.wikipedia.org/wiki/Attribute-based_access_control) principle.
4
- > Allows you to describe rules, combine them into groups, form policies, and apply them to data to determine permissions.
3
+ > Легкий ABAC-движок с DSL, TypeScript-типами и нулевыми зависимостями
5
4
 
6
5
  ![npm version](https://img.shields.io/npm/v/%40via-profit/ability)
7
6
  ![npm downloads](https://img.shields.io/npm/dm/%40via-profit/ability)
@@ -11,388 +10,65 @@
11
10
  ![issues](https://img.shields.io/github/issues/via-profit/ability)
12
11
  ![stars](https://img.shields.io/github/stars/via-profit/ability?style=social)
13
12
 
14
- ## Language / Язык
13
+ > ### 🌐 Language Selection / Выбор языка
14
+ >
15
+ > * 🇷🇺 **[Документация на русском](./docs/ru/README.md)**
16
+ > * 🇬🇧 **English docs**
17
+ >
18
+ > **English documentation? Error 404: Not Found.**
19
+ > Since I’ve spent years learning English just to read your code, here is your perfect chance to practice your
20
+ Russian! Alternatively, Google Translate is your best friend here.
15
21
 
16
- [//]: # (- [🇬🇧 English](/docs/en/README.md))
17
- - [🇷🇺 Русский](/docs/ru/README.md)
22
+ ## Для чего
18
23
 
19
- ## Purpose
24
+ Проект задумывался для того, чтобы закрыть типовые сценарии контроля доступа без лишних сложностей. Нам потребовался
25
+ лёгкий ABAC-движок, работающий и на сервере и в браузере, с простым DSL, автоматической генерацией TypeScript-типов — и
26
+ без внешних зависимостей.
20
27
 
21
- The project was designed to cover typical access control scenarios without unnecessary complexity. We needed a lightweight ABAC engine with a simple DSL, automatic TypeScript type generation — and no external dependencies.
28
+ ## Основные возможности
22
29
 
30
+ - Простой DSL
31
+ - 9 встроенных стратегий разрешения
32
+ - TypeScript-first с генерацией типов
33
+ - Ноль зависимостей
34
+ - Explain для отладки
35
+ - Работает и на сервере и в браузере
23
36
 
24
- - [Key Features](#key-features)
25
- - [Installation](#installation)
26
- - [Quick Start](#quick-start)
27
- - [Core Concepts](#core-concepts)
28
- - [DSL](./dsl.md)
29
-
30
-
31
- ## Key Features
32
-
33
- 1. Simple and expressive DSL — rules read like natural language.
34
- 2. Support for grouping rules with `all of:` / `any of:`.
35
- 3. `except` operator for describing exceptions within a policy.
36
- 4. 9 built-in strategies — DenyOverrides, PermitOverrides, FirstMatch, Priority, and others. Custom strategies can be added.
37
- 5. Cross-platform — works in Node.js and browsers.
38
- 6. TypeScript-first — automatic type generation for resources from policies.
39
- 7. Zero dependencies — lightweight and no external libraries.
40
- 8. Built-in explain — decision tree with results of each rule for debugging.
41
- 9. Serialization — export and import policies to/from JSON.
42
-
43
-
44
- ## Installation
37
+ ## Установка
45
38
 
46
39
  ```bash
47
40
  npm install @via-profit/ability
48
41
  ```
49
42
 
50
- ## Quick Start
43
+ ## Быстрый старт
51
44
 
52
- ```typescript
53
- import {
54
- AbilityResolver,
55
- DenyOverridesStrategy,
56
- ability
57
- } from '@via-profit/ability';
45
+ ```ts
46
+ import { ability, AbilityResolver, DenyOverridesStrategy } from '@via-profit/ability';
58
47
 
48
+ // Создание политик, в частности, одной политики document.read
59
49
  const policies = ability`
60
- @name "Read access is allowed only for user with ID 123, if the document is published and today is Saturday"
50
+ @name "Разрешить чтение документа только авторам или если он опубликован"
61
51
  permit permission.document.read if all:
62
52
 
63
- @name "User ID is 123"
64
- document.ownerId equals 123
53
+ @name "Пользователь является автором"
54
+ document.author is equals user.id
65
55
 
66
- @name "Document is published"
56
+ @name "Документ опубликован"
67
57
  document.status in ["published", "archived"]
68
-
69
- @name "Today must be Saturday"
70
- env.today.dayName is 'Saturday'
71
58
  `;
72
59
 
60
+ // Создание резолвера
73
61
  const resolver = new AbilityResolver(policies, DenyOverridesStrategy);
74
62
 
75
- const environment = {
76
- today: {
77
- dayName: new Date().toLocaleDateString('en-US', { weekday: 'long' }),
78
- },
79
- }
80
-
81
- // Check the permission
82
- const result = resolver.resolve('document.read', {
83
- document: {
84
- ownerId: 123,
85
- status: 'published',
86
- },
87
- }, environment);
88
-
89
- console.log(result.isAllowed()); // true
90
-
91
- // Detailed results
92
- console.log(result.explain());
93
- ```
94
-
95
- ## Core Concepts
96
-
97
- | Component | Description |
98
- |----------------|-----------------------------------------------------------------------------|
99
- | `AbilityPolicy` | A policy – has an `effect` (permit/deny), a `permission`, and a set of rules. |
100
- | `AbilityRuleSet`| A group of rules combined with `all` (AND) or `any` (OR) operator. |
101
- | `AbilityRule` | An elementary rule: `subject` `operator` `resource`. |
102
- | `AbilityCondition` | Comparison operator: `=`, `<>`, `>`, `contains`, `in`, `length >`, etc. |
103
- | `AbilityMatch` | Check state: `match`, `mismatch`, `pending`, `except-mismatch`. |
104
- | `AbilityStrategy` | Algorithm for selecting the final effect from multiple matched policies. |
105
-
106
- ## DSL
107
-
108
- Ability DSL is a declarative language for describing access policies.
109
- It allows you to describe rules in a human-readable form and then use them at runtime to make decisions.
110
-
111
- Ability supports two ways to create policies from DSL:
112
-
113
- 1. **Using DSL literal**
114
- 2. **Using a regular string + AbilityDSLParser**
115
-
116
- ### Creating Policies via DSL Literal
117
-
118
- ```ts
119
- import { ability } from '@via-profit/ability';
120
-
121
- const policies = ability`
122
- permit permission.document.read if all:
123
- document.ownerId equals user.id
124
- document.status in ["published", "archived"]
125
- `;
126
- ```
127
-
128
- - the string inside `ability``…`` is parsed by AbilityDSLParser
129
- - returns an array of policies (`AbilityPolicy[]`)
130
-
131
- ### Creating Policies via Regular String
132
-
133
- If the literal is not available (e.g., in a dynamic environment):
134
-
135
- ```ts
136
- import { AbilityDSLParser } from '@via-profit/ability';
137
-
138
- const dsl = `
139
- permit permission.document.read if all:
140
- document.ownerId equals user.id
141
- document.status in ["published", "archived"]
142
- `;
143
-
144
- const policies = new AbilityDSLParser(dsl).parse();
145
- ```
146
-
147
- Both methods produce the same result.
148
-
149
- ### Typing DSL with Generics
150
-
151
- The DSL literal can accept types:
152
-
153
- ```ts
154
- const policies = ability<Resources, Environment, PolicyTags>`
155
- permit permission.document.read if all:
156
- document.ownerId equals user.id
157
- document.status in ["published", "archived"]
158
- `;
159
- ```
160
-
161
- ### What These Types Provide:
162
-
163
- | Type | Description |
164
- |---------------|--------------------------------------------------------------------|
165
- | `Resources` | Type of resources available in DSL (`document.*`, `order.*`, etc.) |
166
- | `Environment` | Type of environment data (`env.time.*`, `env.user.*`) |
167
- | `PolicyTags` | Types of policy tags (if used) |
168
-
169
- ### Generating Types from Policies
170
-
171
- Ability can automatically generate types based on policies:
172
-
173
- ```ts
174
- import { AbilityTypeGenerator } from '@via-profit/ability';
175
-
176
- const typeDefs = new AbilityTypeGenerator(policies).generateTypeDefs();
177
-
178
- fs.writeFileSync('types.gen.ts', typeDefs, { encoding: 'utf-8' });
179
- ```
180
-
181
- This creates a file:
182
-
183
- ```
184
- types.gen.ts
185
- ```
186
-
187
- It will contain:
188
-
189
- ```ts
190
- export type Resources = { ... };
191
- export type Environment = { ... };
192
- export type PolicyTags = "myTag1" | "myTag2" |
193
- ...
194
- ;
195
- ```
196
-
197
- ### Using Generated Types
198
-
199
- After generating types:
200
-
201
- ```ts
202
- import { ability, AbilityResolver, DenyOverridesStrategy } from '@via-profit/ability';
203
- import type { Resources, Environment, PolicyTags } from './types.gen';
204
-
205
- const policies = ability<Resources, Environment, PolicyTags>`
206
- permit permission.document.read if all:
207
- document.ownerId equals '1'
208
- document.status in ["published", "archived"]
209
- `;
210
- const resolver = new AbilityResolver(policies, DenyOverridesStrategy);
211
-
212
- resolver.enforce('document.read', {
213
- document: {
214
- ownerId: 1, // ❌ Type number is not assignable to type string
215
- status: 'published'
216
- },
217
- });
218
-
219
- ```
220
-
221
- Now:
222
-
223
- - `document.ownerId` and `document.status` are checked for existence and types
224
- - `document.read` is validated for correctness
225
- - operators (`equals`, `in`, etc.) are checked for type compatibility
226
-
227
- ### Basic Structure
228
-
229
- ```
230
- # <comment-line>
231
- @<annotation> <annotation-value>
232
- <effect> <permission> if <all|any>:
233
- <all|any> of: <subject> <operator> <value|resource|env>
234
- <all|any> of: <subject> <operator> <value|resource|env>
235
- ...
236
- except <all|any> of:
237
- <subject> <operator> <value|resource|env>
238
- <subject> <operator> <value|resource|env>
239
- ...
240
- ```
241
-
242
- - `comment-line` - comment
243
- - `annotation` - annotation (`id`, `name`, `disabled`, `tags`, `priority`)
244
- - `effect` – `permit` or `deny`
245
- - `permission` – permission key with `permission.` prefix (e.g., `permission.order.update`)
246
- - `all` / `any` – logical operator for the rule group
247
- - `except` - start of the exception block
248
-
249
- ### Rule
250
-
251
- A rule is the simplest structure that describes what is compared with what and how.
252
-
253
- Each rule must start with a resource path (dot-notation), followed by a comparison operator and the value to compare against.
254
-
255
- *Rule structure*:
256
-
257
- ```
258
- <subject> <operator> <value|resource|env>
259
- ```
260
-
261
- ```
262
- # Simple rule
263
- user.role equals "admin"
264
-
265
- # Comparison with a number
266
- user.age >= 18
267
-
268
- # Array membership check
269
- user.status in ["active", "verified"]
270
-
271
- # Working with array/string length
272
- user.roles length greater than 2
273
-
274
- # Null check
275
- user.deletedAt is null
276
-
277
- # Undefined check
278
- user.middleName is defined
279
-
280
- # Negation
281
- user.banned not equals true
282
- ```
283
-
284
- ### Groups and Exceptions
285
-
286
- A rule group is a block containing one or more rules.
287
-
288
- ```
289
- permit permission.article.edit if all of:
290
- article.authorId equals user.id
291
- any of:
292
- article.status equals "draft"
293
- user.role equals "editor"
63
+ // Загрузка данных (ваш вариант)
64
+ const document = await db.loadDocument();
294
65
 
295
- except any of:
296
- user.banned is true
66
+ // Проверка разрешения
67
+ resolver.enforce('document.read', { document });
297
68
  ```
298
69
 
299
- ### Annotations
300
-
301
- ```
302
- @name "High priority"
303
- @description "Description"
304
- @priority 100
305
- @disabled true
306
- deny permission.admin.all if all:
307
- always
308
- ```
309
-
310
- ## Resolution Strategies
311
-
312
- | Strategy | Behavior |
313
- |-----------------------------|-----------------------------------------------------------------|
314
- | `DenyOverridesStrategy` | If there is at least one `deny` → `deny`, otherwise `permit` (default) |
315
- | `PermitOverridesStrategy` | If there is at least one `permit` → `permit`, otherwise `deny` |
316
- | `FirstMatchStrategy` | Result of the first matching policy |
317
- | `SequentialLastMatchStrategy` | Result of the last matching policy |
318
- | `PriorityStrategy` | Selects the policy with the highest `priority` |
319
- | `AllMustPermitStrategy` | `permit` only if **all** matching policies are `permit` |
320
- | `OnlyOneApplicableStrategy` | `deny` if more than one policy matches |
321
- | `AnyPermitStrategy` | `permit` if there is at least one `permit` |
322
-
323
- Example of using a strategy:
324
-
325
- ```typescript
326
- import { PriorityStrategy } from '@via-profit/ability';
327
-
328
- const resolver = new AbilityResolver(policies, PriorityStrategy);
329
- ```
330
-
331
- ## TypeScript Type Generator
332
-
333
- Automatically creates a `Resources` type based on all rules in the policies.
334
-
335
- ```typescript
336
- import { AbilityTypeGenerator } from '@via-profit/ability';
337
-
338
- const generator = new AbilityTypeGenerator(policies);
339
- const typeDefs = generator.generateTypeDefs();
340
-
341
- // Output: export type Resources = {
342
- // ['document.read']: { readonly ownerId: number; readonly status: string; };
343
- // ...
344
- // }
345
- ```
346
-
347
- The generated types can be used for strict typing of resources when calling `resolver.resolve()`.
348
-
349
- ## API Reference
350
-
351
- ### `AbilityPolicy`
352
-
353
- ```typescript
354
- new AbilityPolicy({ id, name, permission, effect, compareMethod, priority })
355
- .addRuleSet(ruleSet)
356
- .check(resource, environment) // -> AbilityMatch
357
- .explain() // -> AbilityExplain
358
- ```
359
-
360
- ### `AbilityRule`
361
-
362
- ```typescript
363
- new AbilityRule({ subject, resource, condition })
364
- // or static factories:
365
- AbilityRule.equals('user.id', 123)
366
- AbilityRule.contains('tags', 'admin')
367
- ```
368
-
369
- ### `AbilityResolver`
370
-
371
- ```typescript
372
- const resolver = new AbilityResolver(policies, strategy?);
373
- resolver.resolve(permission, resource, environment) -> AbilityResult
374
- resolver.enforce(permission, resource, environment) // throws an error on deny
375
- ```
376
-
377
- ### `AbilityResult`
378
-
379
- ```typescript
380
- result.isAllowed() // boolean
381
- result.isDenied() // boolean
382
- result.explain() // AbilityExplain[]
383
- ```
384
-
385
- ## Principles
386
-
387
- The package does not perform asynchronous operations. Preparing data for verification is the responsibility of the calling code. This makes the engine's behavior deterministic and easy to test.
388
-
389
- ## License
390
-
391
- MIT
392
-
393
- ---
70
+ ## Лицензия
394
71
 
395
- ## Links
72
+ Распространяется под лицензией MIT © [Via-Profit](https://via-profit.ru)
396
73
 
397
- - [GitHub repository](https://github.com/via-profit/ability)
398
- - [DSL](./dsl.md)
74
+ С условиями лицензии можно ознакомиться в файле [LICENSE](./LICENSE).