@holoyan/adonisjs-permissions 0.5.2 → 0.6.8

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 (33) hide show
  1. package/README.md +294 -97
  2. package/build/configure.js +9 -0
  3. package/build/index.d.ts +2 -1
  4. package/build/index.js +2 -1
  5. package/build/providers/role_permission_provider.js +5 -3
  6. package/build/src/acl.d.ts +14 -6
  7. package/build/src/acl.js +33 -12
  8. package/build/src/mixins/has_permissions.d.ts +1 -2
  9. package/build/src/mixins/has_permissions.js +0 -3
  10. package/build/src/model_manager.d.ts +3 -3
  11. package/build/src/scope.d.ts +7 -0
  12. package/build/src/scope.js +14 -0
  13. package/build/src/services/helper.js +3 -3
  14. package/build/src/services/model_has_role_permissions.d.ts +13 -3
  15. package/build/src/services/model_has_role_permissions.js +33 -3
  16. package/build/src/services/permissions/empty_permission.d.ts +7 -2
  17. package/build/src/services/permissions/empty_permission.js +20 -3
  18. package/build/src/services/permissions/permission_has_model_roles.d.ts +5 -2
  19. package/build/src/services/permissions/permission_has_model_roles.js +10 -1
  20. package/build/src/services/permissions/permissions_service.d.ts +11 -3
  21. package/build/src/services/permissions/permissions_service.js +48 -13
  22. package/build/src/services/query_helper.d.ts +4 -4
  23. package/build/src/services/query_helper.js +8 -8
  24. package/build/src/services/roles/empty_roles.d.ts +7 -2
  25. package/build/src/services/roles/empty_roles.js +20 -3
  26. package/build/src/services/roles/role_has_model_permissions.d.ts +7 -6
  27. package/build/src/services/roles/role_has_model_permissions.js +16 -6
  28. package/build/src/services/roles/roles_service.d.ts +10 -5
  29. package/build/src/services/roles/roles_service.js +22 -5
  30. package/build/src/types.d.ts +22 -1
  31. package/build/stubs/middlewares/acl_middleware.stub +26 -0
  32. package/build/stubs/migrations/create_db.stub +23 -24
  33. package/package.json +1 -1
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - [Basic Usage](#basic-usage)
20
20
  - [Creating roles and permissions](#creating-roles-and-permissions)
21
21
  - [Assigning permissions to the roles (Globally)](#assigning-permissions-to-the-roles-globally)
22
+ - [Creating permission on a fly](#creating-permission-on-a-fly)
22
23
  - [Assigning permissions and roles to the users (models)](#assigning-permissions-and-roles-to-the-users-models)
23
24
  - [Multi-model support](#multi-model-support)
24
25
  - [Getting all roles for a user](#getting-all-roles-for-a-user)
@@ -27,36 +28,39 @@
27
28
  - [Getting users (models) for a permission](#getting-users-models-for-a-permission)
28
29
  - [Getting models for a role](#getting-models-for-a-role)
29
30
  - [Checking for a permission](#checking-for-a-permission)
30
- - [Removing (revoking) roles and permissions from the model](#removing-revoking-roles-and-permissions-from-the-model)
31
+ - [Middleware](#middleware)
32
+ - [Removing (revoking) roles and permissions from the model](#removing-revokingdetach-roles-and-permissions-from-the-model)
31
33
  - [Digging deeper](#digging-deeper)
32
- - [Restricting a permission to a model (On resource)](#restricting-a-permission-to-a-model-on-resource)
34
+ - [Restricting a permission to a model (On a resource)](#restricting-a-permission-to-a-model-on-a-resource)
33
35
  - [Forbidding permissions](#forbidding-permissions)
34
36
  - [Forbidding permissions on a resource](#forbidding-permissions-on-a-resource)
35
37
  - [Checking for forbidden permissions](#checking-for-forbidden-permissions)
36
38
  - [Unforbidding the permissions](#unforbidding-the-permissions)
37
39
  - [Global vs resource permissions (Important!)](#global-vs-resource-permissions-important)
38
40
  - [containsPermission v hasPermission](#containspermission-v-haspermission)
41
+ - [Scopes or Multi-tenancy](#scopes-or-multi-tenancy)
42
+ - [The Scope middleware](#the-scope-middleware)
43
+ - [Default Scope](#default-scope-tenant)
44
+ - [Cheat sheet](#cheat-sheet)
45
+ - [Todo](#todo)
39
46
  - [Test](#test)
40
47
  - [License](#license)
41
48
  </p></details>
42
49
 
43
50
  ## Introduction
44
51
 
45
- AdonisJs acl is an elegant and powerful package to managing roles and permissions for any AdonisJs app. With an expressive and fluent syntax, it stays out of your way as much as possible: use it when you want, ignore it when you don't.
52
+ AdonisJs Acl is an elegant and powerful package for managing roles and permissions in any AdonisJs app. With an expressive and fluent syntax, it stays out of your way as much as possible: use it when you want, ignore it when you don't.
46
53
 
47
- For a quick, glanceable list of acl's features, check out [the cheat sheet](#cheat-sheet).
54
+ For a quick, glanceable list of Acl's features, check out the [cheat sheet](#cheat-sheet)
48
55
 
49
56
  Once installed, you can simply tell the Acl what you want to allow:
50
57
 
51
58
  ```typescript
52
59
  import {Acl} from '@holoyan/adonisjs-permissions'
53
60
 
54
-
55
61
  // Give a user the permission to edit
56
62
  await Acl.model(user).allow('edit');
57
-
58
- // Alternatively, do it through a permission
59
- await Acl.permission('edit').attachToModel(user);
63
+ // Behind the scenes Acl will create 'edit' permission and assign to the user if not available
60
64
 
61
65
  // You can also grant a permission only to a specific model
62
66
  const post = await Post.first()
@@ -65,8 +69,7 @@ await Acl.model(user).allow('delete', post);
65
69
  await user.allow('delete', post)
66
70
  ```
67
71
 
68
- To be able to use full power of Acl you should have clear understanding how it is structured and works, that's why documentation will be divided into two parts - [Basic usage](#basic-usage) and [Advanced usage](#digging-deeper) .
69
- For most of the applications [Basic usage](#basic-usage) will be enough
72
+ To be able to use the full power of Acl, you should have a clear understanding of how it is structured and how it works. That's why the documentation will be divided into two parts: [Basic usage](#basic-usage) and [Advanced usage](#digging-deeper). For most applications, Basic Usage will be enough.
70
73
 
71
74
  ## Installation
72
75
 
@@ -76,7 +79,7 @@ For most of the applications [Basic usage](#basic-usage) will be enough
76
79
  Next publish config files
77
80
 
78
81
  node ace configure @holoyan/adonisjs-permissions
79
- this will create permissions.ts file in `configs` and migration file in the `database/migrations` directory
82
+ this will create `permissions.ts` file in `configs` directory, migration file in the `database/migrations` directory
80
83
 
81
84
  Next run migration
82
85
 
@@ -85,7 +88,7 @@ Next run migration
85
88
 
86
89
  ## Configuration
87
90
 
88
- All models which will interact with `Acl` MUST use `@MorphMap('ALIAS_FOR_CLASS')` decorator and implement `AclModelInterface` contract
91
+ All models that will interact with `Acl` MUST use the `@MorphMap('ALIAS_FOR_CLASS')` decorator and implement the `AclModelInterface` contract.
89
92
 
90
93
  Example.
91
94
 
@@ -123,10 +126,9 @@ export default class Post extends BaseModel implements AclModelInterface {
123
126
 
124
127
  ```
125
128
 
126
-
127
129
  ## Mixins
128
130
 
129
- If you want to be able to call these methods on a `User` model then consider using `hasPermissions` mixin
131
+ If you want to be able to call `Acl` methods on a `User` model then consider using `hasPermissions` mixin
130
132
 
131
133
  ```typescript
132
134
 
@@ -149,8 +151,9 @@ export default class User extends compose(BaseModel, hasPermissions()) implement
149
151
  // then all methods are available on the user
150
152
 
151
153
  const user = await User.first()
152
- const roles = await user.roles() // and so on
153
-
154
+ const roles = await user.roles() // get user roles
155
+ await user.allow('edit') // give edit permission
156
+ // and so on...
154
157
 
155
158
  ```
156
159
 
@@ -161,66 +164,93 @@ const roles = await user.roles() // and so on
161
164
  Currently supported databases: `postgres`, `mysql`, `mssql`
162
165
 
163
166
  ### UUID support
164
- No uuid support *yet*
167
+ No uuid support *yet*, check [todo](#todo) list for more details
165
168
 
166
169
  ## Basic Usage
167
170
 
168
- On this section we will explore basic role permission methods
171
+ On this section, we will explore basic role permission methods.
169
172
 
170
173
  ### Creating roles and permissions
171
174
 
172
- Let's create `create,update,read,delete` permissions and `admin,manager` roles
175
+ Let's manually create `create,update,read,delete` permissions, as well as `admin,manager` roles
176
+
177
+ > Look also [Creating permissions on a fly](#creating-permissions-on-a-fly) section
173
178
 
174
179
  ```typescript
175
180
 
181
+ import { Permission } from '@holoyan/adonisjs-permissions'
182
+ import { Role } from '@holoyan/adonisjs-permissions'
183
+ import {Acl} from "@holoyan/adonisjs-permissions";
184
+
185
+
186
+
187
+
176
188
  // create permissions
177
- const create = await Acl.permission().create({
189
+ const create = await Permission.create({
178
190
  slug:'create',
179
- title:'Create some resource',
191
+ title:'Create some resource', // optional
180
192
  })
181
193
 
182
- const update = await Acl.permission().create({
194
+ const update = await Permission.create({
183
195
  slug:'update',
184
- title:'update some resource',
185
196
  })
186
197
 
198
+ // or create using Acl (recomended way)
187
199
  const read = await Acl.permission().create({
188
- slug:'read',
189
- title:'read some resource',
200
+ slug: 'read',
190
201
  })
191
202
 
203
+
192
204
  const delete = await Acl.permission().create({
193
- slug:'delete',
194
- title:'delete some resource',
205
+ slug: 'delete',
195
206
  })
196
207
 
197
208
  // create roles
198
- const admin = await Acl.role().create({
209
+ const admin = await Role.create({
199
210
  slug:'admin',
200
- title:'Cool title for Admin',
211
+ title:'Cool title for Admin', // optional
201
212
  })
202
213
 
214
+ // or create using Acl (recomended way)
203
215
  const manager = await Acl.role().create({
204
- slug:'manager',
205
- title:'Cool title for Manager',
216
+ slug: 'manager',
206
217
  })
207
218
 
208
219
  ```
209
220
 
210
- next step is to [assign permissions to the roles](#assigning-permissions-to-the-roles-globally)
221
+
222
+ The next step is to [assign permissions to the roles](#assigning-permissions-to-the-roles-globally)
211
223
 
212
224
  ### Assigning permissions to the roles (Globally)
213
225
 
214
- Now once we've created roles and permissions let's assign them
226
+ Now that we have created roles and permissions, let's assign them.
215
227
 
216
228
  ```typescript
229
+ import {Acl} from "@holoyan/adonisjs-permissions";
230
+
231
+
217
232
  await Acl.role(admin).assign('create')
218
- // or you can use give() method, they are identical
219
- await Acl.role(admin).give('update')
220
- // or you use giveAll(), assigneAll() for bulk assign
233
+ // alternatively you can use allow(), give() method, as they are identical
234
+
235
+ await Acl.role(admin).allow('update')
221
236
  await Acl.role(admin).giveAll(['read', 'delete'])
237
+ // alternatively you use giveAll(), assigneAll(), allowAll() for bulk assign
238
+
239
+ ```
240
+
241
+ ### Creating permissions on a fly
242
+
243
+ In case you are assigning a permission that is not already available, `Acl` will create new permission behind the scenes and assign them.
244
+
245
+ ```typescript
246
+
247
+ // uploadFile permission not available
248
+ await Acl.role(admin).allow('uploadFile')
249
+ // 'uploadFile' permission created and assigned
250
+
222
251
  ```
223
252
 
253
+
224
254
  ### Assigning permissions and roles to the users (models)
225
255
 
226
256
  Let's see in examples how to assign [roles and permissions](#creating-roles-and-permissions) to the users
@@ -232,6 +262,8 @@ import User from "#models/user";
232
262
  const user1 = await User.query().where(condition1).first()
233
263
  // give manager role to the user1
234
264
  await Acl.model(user1).assignRole(manager)
265
+ // or just use assign() method, they are alias
266
+ // await Acl.model(user1).assign(manager)
235
267
 
236
268
  const user2 = await User.query().where(condition2).first()
237
269
  await Acl.model(user2).assignRole(admin)
@@ -243,19 +275,15 @@ Or we can give permissions directly to users without having any role
243
275
 
244
276
  import {Acl} from "@holoyan/adonisjs-permissions";
245
277
 
246
- // create new permission
247
- const uploadFile = await Acl.permission().create({
248
- slug: 'upload-file-slug',
249
- title: 'permisison to upload files',
250
- })
251
-
278
+ // create and assign a new permission
252
279
  Acl.model(user1).assignDirectPermission('upload-file-slug')
253
-
280
+ // or use allow() method
281
+ Acl.model(user1).allow('permissionSlug')
254
282
  ```
255
283
 
256
284
  ### Multi-model support
257
285
 
258
- We are not limiting to use only User model, if you have multi auth system like User and Admin you are free to use both of them with Acl.
286
+ We are not limited to using only the User model. If you have a multi-auth system like User and Admin, you are free to use both of them with Acl.
259
287
 
260
288
 
261
289
  ```typescript
@@ -290,16 +318,16 @@ const roles = await Acl.role(role).permissions()
290
318
  const roles = await Acl.model(user).permissions()
291
319
  ```
292
320
 
293
- ### Getting users (models) for a permission
321
+ ### Getting users (models) from the permission
294
322
 
295
323
  ```typescript
296
324
 
297
325
  const models = await Acl.permission(permission).models()
298
326
 
299
327
  ```
300
- this will return array of `ModelPermission` which will contain `modelType,modelId` attributes, where `modelType` is *alias* which you had specified in [morphMap decorator](#configuration), `modelId` is the value of column, you've specified in [getModelId](#configuration) method.
328
+ this will return array of `ModelPermission` which will contain `modelType,modelId` attributes, where `modelType` is *alias* which you had specified in [morphMap decorator](#configuration), `modelId` is the value of column, you've specified inside [getModelId](#configuration) method.
301
329
 
302
- Most of the cases you will have only one model (User), it's better to use `modelsFor()` to get concrete models
330
+ Most of the time, you will have only one model (User). It's better to use the `modelsFor()` method to get concrete models.
303
331
 
304
332
  ```typescript
305
333
 
@@ -320,7 +348,7 @@ Or if you want to get for a specific model
320
348
 
321
349
  ```typescript
322
350
 
323
- const models = await Acl.role(permission).modelsFor(Admin)
351
+ const models = await Acl.role(permission).modelsFor(User)
324
352
 
325
353
  ```
326
354
 
@@ -334,7 +362,7 @@ await Acl.model(user).hasRole('admin') // :boolean
334
362
 
335
363
  ```
336
364
 
337
- you can pass array of roles
365
+ you can pass list of roles
338
366
 
339
367
  ```typescript
340
368
 
@@ -343,11 +371,12 @@ await Acl.model(user).hasAllRoles('admin', 'manager')
343
371
 
344
372
  ```
345
373
 
346
- to check if user has any of roles, will return true if user has at least one role
374
+ To check if a user has any of the roles
347
375
 
348
376
  ```typescript
349
377
 
350
378
  await Acl.model(user).hasAnyRole('admin', 'manager')
379
+ // it will return true if the user has at least one role.
351
380
 
352
381
  ```
353
382
 
@@ -361,6 +390,8 @@ await Acl.model(user).hasPermission('update')
361
390
  // or
362
391
  await Acl.model(user).can('update') // alias for hasPermission() method
363
392
 
393
+ // or simply call
394
+ await user.hasPermission('update')
364
395
  ```
365
396
 
366
397
  To check array of permissions
@@ -372,9 +403,11 @@ await Acl.model(user).hasAllPermissions(['update', 'delete'])
372
403
  // or
373
404
  await Acl.model(user).canAll(['update', 'delete']) // alias for hasAllPermissions() method
374
405
 
406
+ // await user.canAll(['update', 'delete'])
407
+
375
408
  ```
376
409
 
377
- to check if user has any of permission, will return true if user has at least one permission
410
+ to check if user has any of the permission
378
411
 
379
412
  ```typescript
380
413
 
@@ -383,9 +416,11 @@ await Acl.model(user).hasAnyPermission(['update', 'delete'])
383
416
  // or
384
417
  await Acl.model(user).canAny(['update', 'delete']) // alias for hasAnyPermission() method
385
418
 
419
+ // will return true if user has at least one permission
420
+
386
421
  ```
387
422
 
388
- Same applies for role
423
+ Same applies for the roles
389
424
 
390
425
  ```typescript
391
426
 
@@ -395,9 +430,42 @@ await Acl.role(role).hasAnyPermission(['update', 'read'])
395
430
 
396
431
  ```
397
432
 
398
- ### Removing (revoking) roles and permissions from the model
433
+ ### Middleware
434
+
435
+ You are free to do your check anywhere, for example we can create [named](https://docs.adonisjs.com/guides/middleware#named-middleware-collection) middleware and do checking
436
+
437
+ > don't forget to register your middleware inside kernel.ts
399
438
 
400
- To remove role from the user we can use `revoke` method
439
+
440
+ ```typescript
441
+
442
+ // routes.ts
443
+ import { middleware } from '#start/kernel'
444
+
445
+ // routes.ts
446
+ router.get('/posts/:id', [ProductsController, 'show']).use(middleware.acl({permission: 'edit'}))
447
+
448
+
449
+ // acl_middleware.ts
450
+ export default class AclMiddleware {
451
+ async handle(ctx: HttpContext, next: NextFn, options: { permission: string }) {
452
+
453
+ const hasPermission = await ctx.auth.user.hasPermission(options.permission)
454
+
455
+ if(!hasPermission) {
456
+ ctx.response.abort({ message: 'Cannot edit post' }, 403)
457
+ }
458
+
459
+ const output = await next()
460
+ return output
461
+ }
462
+ }
463
+
464
+ ```
465
+
466
+ ### Removing (revoking/detach) roles and permissions from the model
467
+
468
+ To remove(detach) role from the user we can use `revoke` method
401
469
 
402
470
  ```typescript
403
471
 
@@ -413,11 +481,14 @@ Removing permissions from the user
413
481
 
414
482
  ```typescript
415
483
  await Acl.model(user).revokePermission('update')
484
+ await Acl.model(user).revoke('delete') // alias for revokePermission()
485
+
416
486
  // await Acl.model(user).hasPermission('update') will return false
417
487
 
418
488
  await Acl.model(user).revokeAllPermissions(['update', 'delete'])
489
+ await Acl.model(user).revokeAll(['update', 'delete']) // alias for revokeAllPermissions()
419
490
 
420
- // will remove all assigned permissions
491
+ // revoke all assigned permissions
421
492
  await Acl.model(user).flushPermissions()
422
493
 
423
494
  // revokes all roles and permissions for a user
@@ -430,14 +501,14 @@ Removing permissions from the role
430
501
  ```typescript
431
502
  await Acl.role(role).revokePermission('update')
432
503
  // or
433
- await Acl.role(role).revoke('update') // alias for revokePermission - WORKS ONLY on roles
504
+ await Acl.role(role).revoke('update') // alias for revokePermission
434
505
 
435
506
  await Acl.role(role).revokeAllPermissions(['update', 'delete'])
436
- // alias revokeAll(['update', 'delete']) method availalbe ONLY on roles
507
+ // alias revokeAll(['update', 'delete'])
437
508
 
438
509
  // remove all assigned permissions
439
510
  await Acl.role(role).flushPermissions()
440
- // alias flush() method availalbe ONLY on roles
511
+ // alias flush()
441
512
 
442
513
  ```
443
514
 
@@ -458,40 +529,45 @@ To see in dept usage of this methods check [next section](#digging-deeper)
458
529
 
459
530
  ## Digging deeper
460
531
 
461
- In [previous](#basic-usage) section we looked basic examples and usage, most of the time basic usage probably will be enough for your project but there are much more we can do with `Acl`
532
+ In the [previous](#basic-usage) section, we looked at basic examples and usage. Most of the time, basic usage will probably be enough for your project. However, there is much more we can do with `Acl`.
462
533
 
463
- ### Restricting a permission to a model (On resource)
534
+ ### Restricting a permission to a model (On a resource)
464
535
 
465
- Sometimes you might want to restrict a permission to a specific model type. Simply pass the model name as a second argument:
536
+ Sometimes you might want to restrict a permission to a specific model type. Simply pass the model as a second argument:
466
537
 
467
538
  ```typescript
468
539
  import Product from "#models/product";
469
540
 
470
- await Acl.model(user).assignDirectPermission('edit', Product)
541
+ await Acl.model(user).allow('edit', Product)
471
542
 
472
543
  ```
473
- >Just don't forget to add model `MorphMap` decorator on Product class
544
+ >Important! - Don't forget to add `MorphMap` decorator and `AclModelInterface` on Product class
474
545
 
475
546
  ```typescript
476
547
 
477
548
  @MorphMap('products')
478
- export default class Product extends BaseModel {
549
+ export default class Product extends BaseModel implements AclModelInterface {
550
+ getModelId(): number {
551
+ return this.id
552
+ }
479
553
  // other code
480
554
  }
481
555
 
482
556
  ```
483
557
 
484
- >Warning: All models which interact with Acl **MUST** use [MorphMap]() decorator
558
+ >Warning: All models which interact with Acl **MUST** use [MorphMap]() decorator and implement `AclModelInterface`
485
559
 
486
- In this case we check permission again
560
+ Then we can make checking again
487
561
 
488
562
  ```typescript
489
563
  import Product from "#models/product";
490
564
  import Post from "#models/post";
491
565
 
492
- const productModel1 = Product.first()
493
- const productModel50 = Product.first()
494
- const postModel = Post.first()
566
+ // await Acl.model(user).allow('edit', Product)
567
+
568
+ const productModel1 = Product.find(id1)
569
+ const productModel50 = Product.find(id50)
570
+ const postModel = Post.find(postId)
495
571
 
496
572
  await Acl.model(user).hasPermission('edit', productModel1) // true
497
573
  await Acl.model(user).hasPermission('edit', productModel50) // true
@@ -532,16 +608,17 @@ await Acl.model(user).containsPermission('edit') // true
532
608
 
533
609
  ```
534
610
 
535
- This will behave same way if assign permission through the role instead of direct
611
+ This will behave the same way if you assign the permission through the role instead of directly
536
612
 
537
613
  ```typescript
538
614
 
539
615
  const product1 = Product.find(1)
540
616
 
541
- await Acl.role(admin).assign('edit', product1)
617
+ await Acl.role(admin).allow('edit', product1)
542
618
 
543
619
  const user = await User.first()
544
620
 
621
+ // assign role
545
622
  await Acl.model(user).assignRole(role)
546
623
 
547
624
  // then if we start checking, result will be same
@@ -560,7 +637,9 @@ await Acl.model(user).containsPermission('edit') // true
560
637
 
561
638
  ### Forbidding permissions
562
639
 
563
- Let's imagine a situation when your `manager` role has `create,update,read,delete` permissions. All your users have `manager` role but there are small amount of users you want to give `manager` role but same time do not allow `delete` action.
640
+ Let's imagine a situation where `manager` role has `create,update,read,delete` permissions.
641
+
642
+ All your users have `manager` role but there are small amount of users you want to forbid `delete` action.
564
643
  Good news!, we can do that
565
644
 
566
645
  ```typescript
@@ -568,18 +647,18 @@ Good news!, we can do that
568
647
  await Acl.role(manager).giveAll(['create','update','read','delete'])
569
648
 
570
649
  // assigning to the users
571
- await Acl.model(user1).assignRole(manager)
650
+ await Acl.model(user1).assign(manager)
572
651
 
573
- await Acl.model(user3).assignRole(manager)
652
+ await Acl.model(user3).assign(manager)
574
653
  await Acl.model(user3).forbid('delete')
575
654
 
576
655
  await Acl.model(user1).hasRole(manager) // true
577
- await Acl.model(user1).hasPermission('delete') // true
656
+ await Acl.model(user1).can('delete') // true
578
657
 
579
658
  await Acl.model(user3).hasRole(manager) // true
580
- await Acl.model(user3).hasPermission('delete') // false
659
+ await Acl.model(user3).can('delete') // false
581
660
 
582
- await Acl.model(user3).containsPermission('delete') // true
661
+ await Acl.model(user3).contains('delete') // true
583
662
 
584
663
  ```
585
664
 
@@ -589,7 +668,7 @@ You can also forbid single action on a resource
589
668
 
590
669
  ```typescript
591
670
 
592
- const post = Post.find(id1ToFind)
671
+ const post = Post.find(id1)
593
672
 
594
673
  await Acl.model(user3).forbid('delete', post)
595
674
 
@@ -606,17 +685,15 @@ await Acl.model(user3).forbid('delete')
606
685
 
607
686
  await Acl.model(user3).forbidden('delete') // true
608
687
 
609
-
610
- const post1 = Post.find(id1ToFind)
611
-
688
+ const post1 = Post.find(id1)
612
689
 
613
690
  await Acl.model(user).allow('edit', Post) // allow for all posts
614
691
  await Acl.model(user).forbid('edit', post1) // except post1
615
692
 
616
693
  await Acl.model(user).forbidden('edit', post1) // true
617
694
 
618
- const post7 = Post.find(id7ToFind)
619
- await Acl.model(user).forbidden('edit', post7) // false becouse 'edit' action forbidden only for post1 instance
695
+ const post7 = Post.find(id7)
696
+ await Acl.model(user).forbidden('edit', post7) // false becouse 'edit' action forbidden only for the post1 instance
620
697
  ```
621
698
 
622
699
  ### Unforbidding the permissions
@@ -627,12 +704,12 @@ await Acl.model(user3).assignRole(manager)
627
704
  await Acl.model(user3).forbid('delete')
628
705
 
629
706
  await Acl.model(user3).forbidden('delete') // true
630
- await Acl.model(user3).hasPermission('delete') // false
631
- await Acl.model(user3).containsPermission('delete') // true
707
+ await Acl.model(user3).can('delete') // false
708
+ await Acl.model(user3).can('delete') // true
632
709
 
633
710
  await Acl.model(user3).unforbid('delete')
634
711
  await Acl.model(user3).forbidden('delete') // false
635
- await Acl.model(user3).hasPermission('delete') // true
712
+ await Acl.model(user3).can('delete') // true
636
713
 
637
714
  ```
638
715
  Same behaviour applies with roles
@@ -643,7 +720,7 @@ await Acl.role(role).forbid('delete')
643
720
 
644
721
  await Acl.role(role).forbidden('delete') // true
645
722
  await Acl.role(role).hasPermission('delete') // false
646
- await Acl.role(role).containsPermission('delete') // true
723
+ await Acl.role(role).contains('delete') // true
647
724
 
648
725
  ```
649
726
 
@@ -652,7 +729,7 @@ await Acl.role(role).containsPermission('delete') // true
652
729
  > Important! Action performed globally will affect on a resource models
653
730
 
654
731
  It is very important to understood difference between global and resource permissions and their scope.
655
- Look at this way, if there is no `entity` model then action will be performed **globally**, otherwise **on resource**
732
+ Look at this way, if there is no `entity` model then actions will be performed **globally**, otherwise **on resource**
656
733
 
657
734
  ```
658
735
 
@@ -673,13 +750,11 @@ Look at this way, if there is no `entity` model then action will be performed **
673
750
 
674
751
  ```
675
752
 
676
-
677
-
678
753
  ```typescript
679
754
  import {Acl} from "@holoyan/adonisjs-permissions";
680
755
  import Post from "#models/post";
681
756
 
682
-
757
+ // first assigning permissions
683
758
  // Global level
684
759
  await Acl.model(admin).allow('create');
685
760
  await Acl.model(admin).allow('edit');
@@ -692,19 +767,19 @@ await Acl.model(manager).allow('create', Post)
692
767
  const myPost = await Post.find(id)
693
768
  await Acl.model(client).allow('view', myPost)
694
769
 
695
- // checking
770
+ // start checking
696
771
  // admin
697
772
  await Acl.model(admin).hasPermission('create') // true
698
773
  await Acl.model(admin).hasPermission('create', Post) // true
699
774
  await Acl.model(admin).hasPermission('create', myPost) // true
700
775
 
701
- // manager
776
+ // manager - assigned class level
702
777
  await Acl.model(manager).hasPermission('create') // false
703
778
  await Acl.model(manager).hasPermission('create', Post) // true
704
779
  await Acl.model(manager).hasPermission('create', myPost) // true
705
780
  await Acl.model(manager).hasPermission('create', myOtherPost) // true
706
781
 
707
- // client
782
+ // assigned model level
708
783
  await Acl.model(client).hasPermission('create') // false
709
784
  await Acl.model(client).hasPermission('create', Post) // false
710
785
  await Acl.model(client).hasPermission('create', myPost) // true
@@ -720,8 +795,7 @@ Same is true when using `forbidden` action
720
795
  // class level
721
796
  await Acl.model(manager).allow('edit', Post) // allow to edit all posts
722
797
 
723
- await Acl.model(manager).forbid('edit', myPost) // forbid editing on a specific post
724
-
798
+ await Acl.model(manager).forbid('edit', myPost) // forbid editing ONLY on a myPost
725
799
 
726
800
  await Acl.model(client).hasPermission('edit', Post) // true
727
801
  await Acl.model(client).hasPermission('edit', myPost) // false
@@ -731,7 +805,9 @@ await Acl.model(client).hasPermission('edit', myOtherPost) // true
731
805
 
732
806
  ### containsPermission v hasPermission
733
807
 
734
- As you've already seen there are difference between `containsPermission` and `hasPermission`. `containsPermission()` method will return `true` if user has that permission, it doesn't matter if it's *global*, *on resource* or *forbidden*
808
+ As you've already seen there are difference between `containsPermission` and `hasPermission` methods. `containsPermission()` method will return `true` if user has that permission, it doesn't matter if it's *global*, *on resource* or *forbidden*.
809
+
810
+ > `contains()` method is alias for `containsPermission()`
735
811
 
736
812
  Lets in example see this difference
737
813
 
@@ -751,6 +827,127 @@ await Acl.model(user).containsPermission('read') // true
751
827
 
752
828
  ```
753
829
 
830
+ ## Scopes or Multi-tenancy
831
+
832
+ Acl fully supports multi-tenant apps, allowing you to seamlessly integrate roles and permissions for all tenants within the same app.
833
+
834
+ ```typescript
835
+ console.log(user.project_id > 0) // true - lets say user.project_id is not equal to zero
836
+
837
+ await Acl.model(user).on(user.project_id).allow('edit')
838
+ await Acl.model(user).on(user.project_id).allow('delete')
839
+
840
+ // checking
841
+ await Acl.model(user).on(user.project_id).hasPermission('edit') // true
842
+ await Acl.model(user).on(user.project_id).hasPermission('delete') // true
843
+
844
+ // checking without scope
845
+ await Acl.model(user).hasPermission('edit') // false - by default scope is equal to 0
846
+
847
+
848
+ ```
849
+
850
+ ### The Scope middleware
851
+
852
+ `Acl` has built-in middleware to make scope checking easier.
853
+
854
+
855
+ This middleware is where you tell `Acl` which tenant to use for the current request. For example, assuming your users all have an account_id attribute, this is what your middleware would look like:
856
+
857
+ ```typescript
858
+
859
+ // acl_middleware
860
+ export default class AclScopeMiddleware {
861
+ async handle(ctx: HttpContext, next: NextFn) {
862
+ const scope = new Scope()
863
+ scope.set(auth.user.account_id)
864
+ ctx.acl = new AclManager().scope(scope)
865
+ /**
866
+ * Call next method in the pipeline and return its output
867
+ */
868
+ const output = await next()
869
+ return output
870
+ }
871
+ }
872
+
873
+ // then on controller you can do
874
+
875
+ // post_controller.ts
876
+
877
+ export default class PostController {
878
+ async show({acl}: HttpContext){
879
+ // will check inside auth.user.account_id scope
880
+ await acl.model().hasPermission('view')
881
+ // this both will be equal
882
+ // await acl.model().on(auth.user.account_id).hasPermission('view')
883
+ }
884
+ }
885
+
886
+ ```
887
+ > Important! If you are using `AclScopeMiddleware` and want to have scope functional per-request then use `acl` from the `ctx` instead of using global `Acl` object, otherwise changes inside `AclScopeMiddleware` will not make effect
888
+
889
+ Let's see in example
890
+
891
+
892
+ ```typescript
893
+
894
+ // acl_middleware
895
+ export default class AclScopeMiddleware {
896
+ async handle(ctx: HttpContext, next: NextFn) {
897
+ const scope = new Scope()
898
+ scope.set(5) // set scope value to 5 for current request
899
+ ctx.acl = new AclManager().scope(scope)
900
+ /**
901
+ * Call next method in the pipeline and return its output
902
+ */
903
+ const output = await next()
904
+ return output
905
+ }
906
+ }
907
+
908
+ // post_controller.ts
909
+ // global object
910
+ import {Acl} from "@holoyan/adonisjs-permissions";
911
+
912
+ export default class PostController {
913
+ async show({acl}: HttpContext){
914
+ const scope = acl.getScope()
915
+ console.log(scope.get()) // 5
916
+ // global object
917
+ console.log(Acl.getScope()) // 0
918
+
919
+ acl.scope(7)// update and set new scope
920
+ // for current request it will be 7
921
+ console.log(acl.getScope()) // 7
922
+
923
+ Acl.scope(7) // Throws error
924
+ // you can't update scope on global object, only runtime
925
+ await Acl.model(user).on(8).permissions() // get permissions for user on scope 8
926
+ }
927
+ }
928
+
929
+ ```
930
+
931
+
932
+ ### Default scope (Tenant)
933
+
934
+ > Default Scope value is equal to 0 (zero)
935
+
936
+
937
+ ## Cheat sheet
938
+
939
+ Coming soon
940
+
941
+
942
+ ## TODO
943
+
944
+ - [X] Scopes (Multitenancy)
945
+ - [ ] UUID support
946
+ - [ ] Events
947
+ - [ ] More test coverage
948
+ - [ ] Caching
949
+ - [ ] Integration with AdonisJs Bouncer
950
+
754
951
  ## Test
755
952
 
756
953
  npm run test