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