@hazeljs/casl 0.7.9 → 0.8.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/README.md +24 -28
- package/dist/ability.factory.d.ts +37 -0
- package/dist/ability.factory.d.ts.map +1 -0
- package/dist/ability.factory.js +38 -0
- package/dist/casl.module.d.ts +35 -0
- package/dist/casl.module.d.ts.map +1 -0
- package/dist/casl.module.js +42 -0
- package/dist/casl.service.d.ts +35 -0
- package/dist/casl.service.d.ts.map +1 -0
- package/dist/casl.service.js +59 -0
- package/dist/casl.test.d.ts +2 -0
- package/dist/casl.test.d.ts.map +1 -0
- package/dist/casl.test.js +320 -0
- package/dist/decorators/ability.decorator.d.ts +32 -0
- package/dist/decorators/ability.decorator.d.ts.map +1 -0
- package/dist/decorators/ability.decorator.js +54 -0
- package/dist/decorators/check-policies.decorator.d.ts +31 -0
- package/dist/decorators/check-policies.decorator.d.ts.map +1 -0
- package/dist/decorators/check-policies.decorator.js +42 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/policy.guard.d.ts +36 -0
- package/dist/policy.guard.d.ts.map +1 -0
- package/dist/policy.guard.js +87 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**Attribute-level (record-level) authorization for HazelJS, powered by [CASL](https://casl.js.org).**
|
|
4
4
|
|
|
5
5
|
Where `RoleGuard` asks "is your role high enough?", `@hazeljs/casl` asks:
|
|
6
|
+
|
|
6
7
|
> **Can _this_ user perform _this action_ on _this specific record_?**
|
|
7
8
|
|
|
8
9
|
[](https://www.npmjs.com/package/@hazeljs/casl)
|
|
@@ -43,7 +44,7 @@ import { Injectable } from '@hazeljs/core';
|
|
|
43
44
|
// All @casl/ability symbols are re-exported — no separate @casl/ability dep needed.
|
|
44
45
|
import { AbilityFactory, MongoAbility, AbilityBuilder, createMongoAbility } from '@hazeljs/casl';
|
|
45
46
|
|
|
46
|
-
type Action
|
|
47
|
+
type Action = 'create' | 'read' | 'update' | 'delete' | 'manage';
|
|
47
48
|
type Subject = Post | 'Post' | 'all';
|
|
48
49
|
export type AppAbility = MongoAbility<[Action, Subject]>;
|
|
49
50
|
|
|
@@ -53,10 +54,10 @@ export class AppAbilityFactory extends AbilityFactory<AppAbility> {
|
|
|
53
54
|
const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
|
54
55
|
|
|
55
56
|
if (user.role === 'admin') {
|
|
56
|
-
can('manage', 'all');
|
|
57
|
+
can('manage', 'all'); // admin: everything
|
|
57
58
|
} else {
|
|
58
|
-
can('read',
|
|
59
|
-
can('update', 'Post', { authorId: user.id });
|
|
59
|
+
can('read', 'Post');
|
|
60
|
+
can('update', 'Post', { authorId: user.id }); // own posts only
|
|
60
61
|
cannot('delete', 'Post');
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -75,9 +76,7 @@ import { CaslModule } from '@hazeljs/casl';
|
|
|
75
76
|
import { AppAbilityFactory } from './casl/app-ability.factory';
|
|
76
77
|
|
|
77
78
|
@HazelModule({
|
|
78
|
-
imports: [
|
|
79
|
-
CaslModule.forRoot({ abilityFactory: AppAbilityFactory }),
|
|
80
|
-
],
|
|
79
|
+
imports: [CaslModule.forRoot({ abilityFactory: AppAbilityFactory })],
|
|
81
80
|
})
|
|
82
81
|
export class AppModule {}
|
|
83
82
|
```
|
|
@@ -114,10 +113,10 @@ export class PostsController {
|
|
|
114
113
|
|
|
115
114
|
Errors thrown:
|
|
116
115
|
|
|
117
|
-
| Condition
|
|
118
|
-
|
|
119
|
-
| No `req.user` (guard order wrong) | 401
|
|
120
|
-
| Any handler returns `false`
|
|
116
|
+
| Condition | Status |
|
|
117
|
+
| --------------------------------- | ------ |
|
|
118
|
+
| No `req.user` (guard order wrong) | 401 |
|
|
119
|
+
| Any handler returns `false` | 403 |
|
|
121
120
|
|
|
122
121
|
---
|
|
123
122
|
|
|
@@ -216,7 +215,7 @@ Request
|
|
|
216
215
|
|
|
217
216
|
## `@Ability()` — inject the ability directly
|
|
218
217
|
|
|
219
|
-
`@Ability()` is a **parameter decorator** that resolves `CaslService.createForUser(req.user)` once per request and injects the result straight into your controller method.
|
|
218
|
+
`@Ability()` is a **parameter decorator** that resolves `CaslService.createForUser(req.user)` once per request and injects the result straight into your controller method. Services receive the pre-built ability instead of the raw user, keeping business logic clean.
|
|
220
219
|
|
|
221
220
|
```typescript
|
|
222
221
|
// posts.controller.ts
|
|
@@ -232,18 +231,15 @@ export class PostsController {
|
|
|
232
231
|
|
|
233
232
|
@Patch('/:id')
|
|
234
233
|
update(
|
|
235
|
-
@Ability() ability: AppAbility,
|
|
234
|
+
@Ability() ability: AppAbility, // ← resolved from req.user automatically
|
|
236
235
|
@Param('id') id: string,
|
|
237
|
-
@Body() dto: UpdatePostDto
|
|
236
|
+
@Body() dto: UpdatePostDto
|
|
238
237
|
) {
|
|
239
238
|
return this.postsService.update(ability, id, dto);
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
@Delete('/:id')
|
|
243
|
-
remove(
|
|
244
|
-
@Ability() ability: AppAbility,
|
|
245
|
-
@Param('id') id: string,
|
|
246
|
-
) {
|
|
242
|
+
remove(@Ability() ability: AppAbility, @Param('id') id: string) {
|
|
247
243
|
return this.postsService.remove(ability, id);
|
|
248
244
|
}
|
|
249
245
|
}
|
|
@@ -254,7 +250,7 @@ The service just receives an `AppAbility` — no `CaslService` injection needed:
|
|
|
254
250
|
```typescript
|
|
255
251
|
// posts.service.ts
|
|
256
252
|
import { Injectable } from '@hazeljs/core';
|
|
257
|
-
import { subject } from '@hazeljs/casl';
|
|
253
|
+
import { subject } from '@hazeljs/casl'; // re-exported — no @casl/ability dep needed
|
|
258
254
|
import type { AppAbility } from './casl/app-ability.factory';
|
|
259
255
|
|
|
260
256
|
@Injectable()
|
|
@@ -283,7 +279,7 @@ export class PostsService {
|
|
|
283
279
|
```
|
|
284
280
|
|
|
285
281
|
> **When to use `@Ability()` vs `CaslService`**
|
|
286
|
-
> Use `@Ability()` when the controller passes the ability to a single service.
|
|
282
|
+
> Use `@Ability()` when the controller passes the ability to a single service. Use `CaslService` directly when a service is called from multiple places (background jobs, other services) and the caller may not hold an ability object.
|
|
287
283
|
|
|
288
284
|
---
|
|
289
285
|
|
|
@@ -300,11 +296,11 @@ import type { AppAbility } from './casl/app-ability.factory';
|
|
|
300
296
|
export class PostsService {
|
|
301
297
|
constructor(
|
|
302
298
|
private readonly postsRepo: PostsRepository,
|
|
303
|
-
private readonly casl: CaslService<AppAbility
|
|
299
|
+
private readonly casl: CaslService<AppAbility>
|
|
304
300
|
) {}
|
|
305
301
|
|
|
306
302
|
async update(user: Record<string, unknown>, postId: string, dto: UpdatePostDto) {
|
|
307
|
-
const post
|
|
303
|
+
const post = await this.postsRepo.findById(postId);
|
|
308
304
|
const ability = this.casl.createForUser(user);
|
|
309
305
|
|
|
310
306
|
if (!ability.can('update', subject('Post', post))) {
|
|
@@ -327,9 +323,9 @@ import { CaslService } from '@hazeljs/casl';
|
|
|
327
323
|
// Inject and call createForUser to get an ability for the current user
|
|
328
324
|
const ability = this.casl.createForUser(user);
|
|
329
325
|
|
|
330
|
-
ability.can('read',
|
|
331
|
-
ability.can('update', subject('Post', post))
|
|
332
|
-
ability.cannot('delete', 'Post')
|
|
326
|
+
ability.can('read', 'Post'); // true / false
|
|
327
|
+
ability.can('update', subject('Post', post)); // checks conditions
|
|
328
|
+
ability.cannot('delete', 'Post'); // negation check
|
|
333
329
|
```
|
|
334
330
|
|
|
335
331
|
---
|
|
@@ -349,9 +345,9 @@ abstract class AbilityFactory<A extends AnyAbility> {
|
|
|
349
345
|
|
|
350
346
|
## `CaslModule.forRoot()` options
|
|
351
347
|
|
|
352
|
-
| Option
|
|
353
|
-
|
|
354
|
-
| `abilityFactory` | `new (...args: any[]) => AbilityFactory<A>` | ✓
|
|
348
|
+
| Option | Type | Required | Description |
|
|
349
|
+
| ---------------- | ------------------------------------------- | -------- | -------------------------------------------- |
|
|
350
|
+
| `abilityFactory` | `new (...args: any[]) => AbilityFactory<A>` | ✓ | Your factory class (must be `@Injectable()`) |
|
|
355
351
|
|
|
356
352
|
---
|
|
357
353
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AnyAbility } from '@casl/ability';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for user-defined ability factories.
|
|
4
|
+
*
|
|
5
|
+
* Extend this class and implement `createForUser` to define what actions a
|
|
6
|
+
* given user can perform. Register your factory with `CaslModule.forRoot()`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { MongoAbility, createMongoAbility, AbilityBuilder } from '@casl/ability';
|
|
11
|
+
* import { Injectable } from '@hazeljs/core';
|
|
12
|
+
* import { AbilityFactory } from '@hazeljs/casl';
|
|
13
|
+
*
|
|
14
|
+
* type Action = 'create' | 'read' | 'update' | 'delete' | 'manage';
|
|
15
|
+
* type Subject = Post | 'Post' | 'all';
|
|
16
|
+
* export type AppAbility = MongoAbility<[Action, Subject]>;
|
|
17
|
+
*
|
|
18
|
+
* @Injectable()
|
|
19
|
+
* export class AppAbilityFactory extends AbilityFactory<AppAbility> {
|
|
20
|
+
* createForUser(user: AuthUser): AppAbility {
|
|
21
|
+
* const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
|
22
|
+
* if (user.role === 'admin') {
|
|
23
|
+
* can('manage', 'all');
|
|
24
|
+
* } else {
|
|
25
|
+
* can('read', 'Post');
|
|
26
|
+
* can('update', 'Post', { authorId: user.id }); // own posts only
|
|
27
|
+
* cannot('delete', 'Post');
|
|
28
|
+
* }
|
|
29
|
+
* return build();
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare abstract class AbilityFactory<A extends AnyAbility> {
|
|
35
|
+
abstract createForUser(user: Record<string, unknown>): A;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=ability.factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ability.factory.d.ts","sourceRoot":"","sources":["../src/ability.factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,8BAAsB,cAAc,CAAC,CAAC,SAAS,UAAU;IACvD,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC;CACzD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AbilityFactory = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class for user-defined ability factories.
|
|
6
|
+
*
|
|
7
|
+
* Extend this class and implement `createForUser` to define what actions a
|
|
8
|
+
* given user can perform. Register your factory with `CaslModule.forRoot()`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { MongoAbility, createMongoAbility, AbilityBuilder } from '@casl/ability';
|
|
13
|
+
* import { Injectable } from '@hazeljs/core';
|
|
14
|
+
* import { AbilityFactory } from '@hazeljs/casl';
|
|
15
|
+
*
|
|
16
|
+
* type Action = 'create' | 'read' | 'update' | 'delete' | 'manage';
|
|
17
|
+
* type Subject = Post | 'Post' | 'all';
|
|
18
|
+
* export type AppAbility = MongoAbility<[Action, Subject]>;
|
|
19
|
+
*
|
|
20
|
+
* @Injectable()
|
|
21
|
+
* export class AppAbilityFactory extends AbilityFactory<AppAbility> {
|
|
22
|
+
* createForUser(user: AuthUser): AppAbility {
|
|
23
|
+
* const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
|
24
|
+
* if (user.role === 'admin') {
|
|
25
|
+
* can('manage', 'all');
|
|
26
|
+
* } else {
|
|
27
|
+
* can('read', 'Post');
|
|
28
|
+
* can('update', 'Post', { authorId: user.id }); // own posts only
|
|
29
|
+
* cannot('delete', 'Post');
|
|
30
|
+
* }
|
|
31
|
+
* return build();
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class AbilityFactory {
|
|
37
|
+
}
|
|
38
|
+
exports.AbilityFactory = AbilityFactory;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AnyAbility } from '@casl/ability';
|
|
2
|
+
import { AbilityFactory } from './ability.factory';
|
|
3
|
+
export interface CaslModuleOptions<A extends AnyAbility = AnyAbility> {
|
|
4
|
+
/**
|
|
5
|
+
* Your application's ability factory class. It must extend `AbilityFactory`
|
|
6
|
+
* and be decorated with `@Injectable()` so the DI container can resolve it.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* CaslModule.forRoot({ abilityFactory: AppAbilityFactory })
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
abilityFactory: new (...args: any[]) => AbilityFactory<A>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* HazelJS module that wires attribute-level authorization into your app.
|
|
17
|
+
*
|
|
18
|
+
* Register once in your root module:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* @HazelModule({
|
|
22
|
+
* imports: [
|
|
23
|
+
* CaslModule.forRoot({ abilityFactory: AppAbilityFactory }),
|
|
24
|
+
* ],
|
|
25
|
+
* })
|
|
26
|
+
* export class AppModule {}
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Then use `PoliciesGuard` / `@CheckPolicies` on your routes and inject
|
|
30
|
+
* `CaslService` in your services for record-level checks.
|
|
31
|
+
*/
|
|
32
|
+
export declare class CaslModule {
|
|
33
|
+
static forRoot<A extends AnyAbility>(options: CaslModuleOptions<A>): typeof CaslModule;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=casl.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"casl.module.d.ts","sourceRoot":"","sources":["../src/casl.module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAClE;;;;;;;;OAQG;IAEH,cAAc,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAIa,UAAU;IACrB,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,UAAU,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,UAAU;CAIvF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var CaslModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.CaslModule = void 0;
|
|
11
|
+
const core_1 = require("@hazeljs/core");
|
|
12
|
+
const casl_service_1 = require("./casl.service");
|
|
13
|
+
/**
|
|
14
|
+
* HazelJS module that wires attribute-level authorization into your app.
|
|
15
|
+
*
|
|
16
|
+
* Register once in your root module:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* @HazelModule({
|
|
20
|
+
* imports: [
|
|
21
|
+
* CaslModule.forRoot({ abilityFactory: AppAbilityFactory }),
|
|
22
|
+
* ],
|
|
23
|
+
* })
|
|
24
|
+
* export class AppModule {}
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Then use `PoliciesGuard` / `@CheckPolicies` on your routes and inject
|
|
28
|
+
* `CaslService` in your services for record-level checks.
|
|
29
|
+
*/
|
|
30
|
+
let CaslModule = CaslModule_1 = class CaslModule {
|
|
31
|
+
static forRoot(options) {
|
|
32
|
+
casl_service_1.CaslService.configure(options.abilityFactory);
|
|
33
|
+
return CaslModule_1;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.CaslModule = CaslModule;
|
|
37
|
+
exports.CaslModule = CaslModule = CaslModule_1 = __decorate([
|
|
38
|
+
(0, core_1.HazelModule)({
|
|
39
|
+
providers: [casl_service_1.CaslService],
|
|
40
|
+
exports: [casl_service_1.CaslService],
|
|
41
|
+
})
|
|
42
|
+
], CaslModule);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AnyAbility } from '@casl/ability';
|
|
2
|
+
import { AbilityFactory } from './ability.factory';
|
|
3
|
+
/**
|
|
4
|
+
* Core service that builds a CASL ability for a given user.
|
|
5
|
+
*
|
|
6
|
+
* Inject this service anywhere you need record-level authorization:
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* @Injectable()
|
|
11
|
+
* export class PostsService {
|
|
12
|
+
* constructor(private readonly casl: CaslService<AppAbility>) {}
|
|
13
|
+
*
|
|
14
|
+
* async update(user: AuthUser, postId: string, dto: UpdatePostDto) {
|
|
15
|
+
* const post = await this.postsRepo.findById(postId);
|
|
16
|
+
* const ability = this.casl.createForUser(user);
|
|
17
|
+
* if (!ability.can('update', post)) {
|
|
18
|
+
* throw Object.assign(new Error('Forbidden'), { status: 403 });
|
|
19
|
+
* }
|
|
20
|
+
* return this.postsRepo.update(postId, dto);
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class CaslService<A extends AnyAbility = AnyAbility> {
|
|
26
|
+
private get factory();
|
|
27
|
+
/**
|
|
28
|
+
* Build an ability instance for the given user object.
|
|
29
|
+
* The user value comes from `req.user` (set by JwtAuthGuard or your own guard).
|
|
30
|
+
*/
|
|
31
|
+
createForUser(user: Record<string, unknown>): A;
|
|
32
|
+
static factoryClass: (new (...args: any[]) => AbilityFactory<AnyAbility>) | undefined;
|
|
33
|
+
static configure(factoryClass: new (...args: any[]) => AbilityFactory<AnyAbility>): void;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=casl.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"casl.service.d.ts","sourceRoot":"","sources":["../src/casl.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,WAAW,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAExD,OAAO,KAAK,OAAO,GASlB;IAED;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC;IAS/C,MAAM,CAAC,YAAY,EAAE,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,cAAc,CAAC,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC;IAGtF,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI;CAGzF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var CaslService_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.CaslService = void 0;
|
|
11
|
+
const core_1 = require("@hazeljs/core");
|
|
12
|
+
/**
|
|
13
|
+
* Core service that builds a CASL ability for a given user.
|
|
14
|
+
*
|
|
15
|
+
* Inject this service anywhere you need record-level authorization:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* @Injectable()
|
|
20
|
+
* export class PostsService {
|
|
21
|
+
* constructor(private readonly casl: CaslService<AppAbility>) {}
|
|
22
|
+
*
|
|
23
|
+
* async update(user: AuthUser, postId: string, dto: UpdatePostDto) {
|
|
24
|
+
* const post = await this.postsRepo.findById(postId);
|
|
25
|
+
* const ability = this.casl.createForUser(user);
|
|
26
|
+
* if (!ability.can('update', post)) {
|
|
27
|
+
* throw Object.assign(new Error('Forbidden'), { status: 403 });
|
|
28
|
+
* }
|
|
29
|
+
* return this.postsRepo.update(postId, dto);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
let CaslService = CaslService_1 = class CaslService {
|
|
35
|
+
// Resolved lazily so the factory class is available after DI wires everything up.
|
|
36
|
+
get factory() {
|
|
37
|
+
const factoryClass = CaslService_1.factoryClass;
|
|
38
|
+
if (!factoryClass) {
|
|
39
|
+
throw new Error('CaslService: no AbilityFactory registered. ' +
|
|
40
|
+
'Call CaslModule.forRoot({ abilityFactory: YourFactory }) in your app module.');
|
|
41
|
+
}
|
|
42
|
+
return core_1.Container.getInstance().resolve(factoryClass);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build an ability instance for the given user object.
|
|
46
|
+
* The user value comes from `req.user` (set by JwtAuthGuard or your own guard).
|
|
47
|
+
*/
|
|
48
|
+
createForUser(user) {
|
|
49
|
+
return this.factory.createForUser(user);
|
|
50
|
+
}
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
static configure(factoryClass) {
|
|
53
|
+
CaslService_1.factoryClass = factoryClass;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.CaslService = CaslService;
|
|
57
|
+
exports.CaslService = CaslService = CaslService_1 = __decorate([
|
|
58
|
+
(0, core_1.Injectable)()
|
|
59
|
+
], CaslService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"casl.test.d.ts","sourceRoot":"","sources":["../src/casl.test.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/// <reference types="jest" />
|
|
3
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
4
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
7
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
8
|
+
};
|
|
9
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
10
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
11
|
+
};
|
|
12
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
13
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
require("reflect-metadata");
|
|
17
|
+
jest.mock('@hazeljs/core', () => ({
|
|
18
|
+
__esModule: true,
|
|
19
|
+
Service: () => () => undefined,
|
|
20
|
+
Injectable: () => () => undefined,
|
|
21
|
+
HazelModule: () => () => undefined,
|
|
22
|
+
Container: {
|
|
23
|
+
getInstance: jest.fn(),
|
|
24
|
+
},
|
|
25
|
+
logger: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
26
|
+
default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
27
|
+
}));
|
|
28
|
+
const core_1 = require("@hazeljs/core");
|
|
29
|
+
const ability_1 = require("@casl/ability");
|
|
30
|
+
const ability_factory_1 = require("./ability.factory");
|
|
31
|
+
const casl_service_1 = require("./casl.service");
|
|
32
|
+
const casl_module_1 = require("./casl.module");
|
|
33
|
+
const policy_guard_1 = require("./policy.guard");
|
|
34
|
+
const check_policies_decorator_1 = require("./decorators/check-policies.decorator");
|
|
35
|
+
const ability_decorator_1 = require("./decorators/ability.decorator");
|
|
36
|
+
function buildAbility(role, userId) {
|
|
37
|
+
const { can, cannot, build } = new ability_1.AbilityBuilder(ability_1.createMongoAbility);
|
|
38
|
+
if (role === 'admin') {
|
|
39
|
+
can('manage', 'all');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
can('read', 'Post');
|
|
43
|
+
can('update', 'Post', { authorId: userId });
|
|
44
|
+
cannot('delete', 'Post');
|
|
45
|
+
}
|
|
46
|
+
return build();
|
|
47
|
+
}
|
|
48
|
+
class TestAbilityFactory extends ability_factory_1.AbilityFactory {
|
|
49
|
+
createForUser(user) {
|
|
50
|
+
return buildAbility(user['role'], user['id']);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Helper: build a mock ExecutionContext
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
function makeCtx(user) {
|
|
57
|
+
return {
|
|
58
|
+
switchToHttp: () => ({
|
|
59
|
+
getRequest: () => ({ user }),
|
|
60
|
+
getResponse: () => ({}),
|
|
61
|
+
getContext: () => ({}),
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// CaslService
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
describe('CaslService', () => {
|
|
69
|
+
const mockContainer = core_1.Container;
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
casl_service_1.CaslService.factoryClass = undefined;
|
|
72
|
+
jest.clearAllMocks();
|
|
73
|
+
});
|
|
74
|
+
it('throws when no factory is configured', () => {
|
|
75
|
+
const svc = new casl_service_1.CaslService();
|
|
76
|
+
expect(() => svc.createForUser({ id: '1', role: 'user' })).toThrow('CaslService: no AbilityFactory registered');
|
|
77
|
+
});
|
|
78
|
+
it('resolves the factory from the DI container and calls createForUser', () => {
|
|
79
|
+
const factory = new TestAbilityFactory();
|
|
80
|
+
mockContainer.getInstance.mockReturnValue({
|
|
81
|
+
resolve: jest.fn().mockReturnValue(factory),
|
|
82
|
+
});
|
|
83
|
+
casl_service_1.CaslService.configure(TestAbilityFactory);
|
|
84
|
+
const svc = new casl_service_1.CaslService();
|
|
85
|
+
const user = { id: 'user-1', role: 'admin' };
|
|
86
|
+
const ability = svc.createForUser(user);
|
|
87
|
+
expect(ability.can('manage', 'all')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
it('returns an ability respecting conditions for regular users', () => {
|
|
90
|
+
const factory = new TestAbilityFactory();
|
|
91
|
+
mockContainer.getInstance.mockReturnValue({
|
|
92
|
+
resolve: jest.fn().mockReturnValue(factory),
|
|
93
|
+
});
|
|
94
|
+
casl_service_1.CaslService.configure(TestAbilityFactory);
|
|
95
|
+
const svc = new casl_service_1.CaslService();
|
|
96
|
+
const user = { id: 'user-1', role: 'user' };
|
|
97
|
+
const ability = svc.createForUser(user);
|
|
98
|
+
expect(ability.can('read', 'Post')).toBe(true);
|
|
99
|
+
expect(ability.can('delete', 'Post')).toBe(false);
|
|
100
|
+
// own post — use subject() helper so CASL knows the subject type
|
|
101
|
+
expect(ability.can('update', (0, ability_1.subject)('Post', { authorId: 'user-1' }))).toBe(true);
|
|
102
|
+
// someone else's post
|
|
103
|
+
expect(ability.can('update', (0, ability_1.subject)('Post', { authorId: 'other-user' }))).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// CaslModule.forRoot
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
describe('CaslModule.forRoot', () => {
|
|
110
|
+
afterEach(() => {
|
|
111
|
+
casl_service_1.CaslService.factoryClass = undefined;
|
|
112
|
+
});
|
|
113
|
+
it('configures the factory class on CaslService', () => {
|
|
114
|
+
casl_module_1.CaslModule.forRoot({ abilityFactory: TestAbilityFactory });
|
|
115
|
+
expect(casl_service_1.CaslService.factoryClass).toBe(TestAbilityFactory);
|
|
116
|
+
});
|
|
117
|
+
it('returns the CaslModule class (for HazelModule imports)', () => {
|
|
118
|
+
const result = casl_module_1.CaslModule.forRoot({ abilityFactory: TestAbilityFactory });
|
|
119
|
+
expect(result).toBe(casl_module_1.CaslModule);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// PoliciesGuard
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
describe('PoliciesGuard', () => {
|
|
126
|
+
const mockContainer = core_1.Container;
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
casl_service_1.CaslService.factoryClass = undefined;
|
|
129
|
+
jest.clearAllMocks();
|
|
130
|
+
});
|
|
131
|
+
function setupFactory() {
|
|
132
|
+
const factory = new TestAbilityFactory();
|
|
133
|
+
mockContainer.getInstance.mockReturnValue({
|
|
134
|
+
resolve: jest.fn().mockReturnValue(factory),
|
|
135
|
+
});
|
|
136
|
+
casl_service_1.CaslService.configure(TestAbilityFactory);
|
|
137
|
+
return new casl_service_1.CaslService();
|
|
138
|
+
}
|
|
139
|
+
it('returns true when no handlers are provided', async () => {
|
|
140
|
+
const casl = setupFactory();
|
|
141
|
+
const Guard = (0, policy_guard_1.PoliciesGuard)();
|
|
142
|
+
const guard = new Guard(casl);
|
|
143
|
+
const ctx = makeCtx({ id: '1', role: 'user' });
|
|
144
|
+
await expect(guard.canActivate(ctx)).resolves.toBe(true);
|
|
145
|
+
});
|
|
146
|
+
it('returns true when all function handlers pass', async () => {
|
|
147
|
+
const casl = setupFactory();
|
|
148
|
+
const Guard = (0, policy_guard_1.PoliciesGuard)((ability) => ability.can('read', 'Post'));
|
|
149
|
+
const guard = new Guard(casl);
|
|
150
|
+
const ctx = makeCtx({ id: '1', role: 'user' });
|
|
151
|
+
await expect(guard.canActivate(ctx)).resolves.toBe(true);
|
|
152
|
+
});
|
|
153
|
+
it('throws 403 when a handler returns false', async () => {
|
|
154
|
+
const casl = setupFactory();
|
|
155
|
+
const Guard = (0, policy_guard_1.PoliciesGuard)((ability) => ability.can('delete', 'Post'));
|
|
156
|
+
const guard = new Guard(casl);
|
|
157
|
+
const ctx = makeCtx({ id: '1', role: 'user' });
|
|
158
|
+
await expect(guard.canActivate(ctx)).rejects.toMatchObject({ status: 403 });
|
|
159
|
+
});
|
|
160
|
+
it('throws 401 when req.user is missing', async () => {
|
|
161
|
+
const casl = setupFactory();
|
|
162
|
+
const Guard = (0, policy_guard_1.PoliciesGuard)((ability) => ability.can('read', 'Post'));
|
|
163
|
+
const guard = new Guard(casl);
|
|
164
|
+
const ctx = makeCtx(undefined);
|
|
165
|
+
await expect(guard.canActivate(ctx)).rejects.toMatchObject({ status: 401 });
|
|
166
|
+
});
|
|
167
|
+
it('works with a class-instance (IPolicyHandler) handler', async () => {
|
|
168
|
+
const casl = setupFactory();
|
|
169
|
+
const classHandler = {
|
|
170
|
+
handle: jest.fn().mockResolvedValue(true),
|
|
171
|
+
};
|
|
172
|
+
const Guard = (0, policy_guard_1.PoliciesGuard)(classHandler);
|
|
173
|
+
const guard = new Guard(casl);
|
|
174
|
+
const ctx = makeCtx({ id: '1', role: 'user' });
|
|
175
|
+
await expect(guard.canActivate(ctx)).resolves.toBe(true);
|
|
176
|
+
expect(classHandler.handle).toHaveBeenCalledTimes(1);
|
|
177
|
+
});
|
|
178
|
+
it('admin passes all checks', async () => {
|
|
179
|
+
const casl = setupFactory();
|
|
180
|
+
const Guard = (0, policy_guard_1.PoliciesGuard)((ability) => ability.can('read', 'Post'), (ability) => ability.can('delete', 'Post'), (ability) => ability.can('create', 'Post'));
|
|
181
|
+
const guard = new Guard(casl);
|
|
182
|
+
const ctx = makeCtx({ id: 'admin-1', role: 'admin' });
|
|
183
|
+
await expect(guard.canActivate(ctx)).resolves.toBe(true);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// @CheckPolicies decorator
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
describe('@CheckPolicies decorator', () => {
|
|
190
|
+
it('registers a PoliciesGuard on the method metadata under hazel:guards', () => {
|
|
191
|
+
class TestController {
|
|
192
|
+
getAll() {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
__decorate([
|
|
197
|
+
(0, check_policies_decorator_1.CheckPolicies)((ability) => ability.can('read', 'Post')),
|
|
198
|
+
__metadata("design:type", Function),
|
|
199
|
+
__metadata("design:paramtypes", []),
|
|
200
|
+
__metadata("design:returntype", void 0)
|
|
201
|
+
], TestController.prototype, "getAll", null);
|
|
202
|
+
const guards = Reflect.getMetadata('hazel:guards', TestController.prototype, 'getAll');
|
|
203
|
+
expect(Array.isArray(guards)).toBe(true);
|
|
204
|
+
expect(guards).toHaveLength(1);
|
|
205
|
+
});
|
|
206
|
+
it('appends to existing guards without overwriting them', () => {
|
|
207
|
+
// Pre-register a guard manually the way @UseGuards would
|
|
208
|
+
class MockGuard {
|
|
209
|
+
}
|
|
210
|
+
class TestController {
|
|
211
|
+
dummy() { }
|
|
212
|
+
}
|
|
213
|
+
Reflect.defineMetadata('hazel:guards', [MockGuard], TestController.prototype, 'dummy');
|
|
214
|
+
// Now apply CheckPolicies
|
|
215
|
+
const decorator = (0, check_policies_decorator_1.CheckPolicies)((ability) => ability.can('read', 'Post'));
|
|
216
|
+
const descriptor = Object.getOwnPropertyDescriptor(TestController.prototype, 'dummy');
|
|
217
|
+
decorator(TestController.prototype, 'dummy', descriptor);
|
|
218
|
+
const guards = Reflect.getMetadata('hazel:guards', TestController.prototype, 'dummy');
|
|
219
|
+
expect(guards).toHaveLength(2);
|
|
220
|
+
expect(guards[0]).toBe(MockGuard);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// @Ability() decorator
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
describe('@Ability() decorator', () => {
|
|
227
|
+
const INJECT_KEY = 'hazel:inject';
|
|
228
|
+
it('stores a custom injection on the method metadata', () => {
|
|
229
|
+
class TestController {
|
|
230
|
+
getTask(_ability) {
|
|
231
|
+
return _ability;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
__decorate([
|
|
235
|
+
__param(0, (0, ability_decorator_1.Ability)()),
|
|
236
|
+
__metadata("design:type", Function),
|
|
237
|
+
__metadata("design:paramtypes", [Object]),
|
|
238
|
+
__metadata("design:returntype", void 0)
|
|
239
|
+
], TestController.prototype, "getTask", null);
|
|
240
|
+
const injections = Reflect.getMetadata(INJECT_KEY, TestController, 'getTask');
|
|
241
|
+
expect(Array.isArray(injections)).toBe(true);
|
|
242
|
+
expect(injections).toHaveLength(1);
|
|
243
|
+
const inj = injections[0];
|
|
244
|
+
expect(inj.type).toBe('custom');
|
|
245
|
+
expect(typeof inj.resolve).toBe('function');
|
|
246
|
+
});
|
|
247
|
+
it('resolve() calls CaslService.createForUser with context.user', () => {
|
|
248
|
+
const mockAbility = { can: jest.fn() };
|
|
249
|
+
const mockCaslSvc = { createForUser: jest.fn().mockReturnValue(mockAbility) };
|
|
250
|
+
const mockContainer = { resolve: jest.fn().mockReturnValue(mockCaslSvc) };
|
|
251
|
+
class TestController {
|
|
252
|
+
getTask(_ability) {
|
|
253
|
+
return _ability;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
__decorate([
|
|
257
|
+
__param(0, (0, ability_decorator_1.Ability)()),
|
|
258
|
+
__metadata("design:type", Function),
|
|
259
|
+
__metadata("design:paramtypes", [Object]),
|
|
260
|
+
__metadata("design:returntype", void 0)
|
|
261
|
+
], TestController.prototype, "getTask", null);
|
|
262
|
+
const injections = Reflect.getMetadata(INJECT_KEY, TestController, 'getTask');
|
|
263
|
+
const context = { user: { sub: 'u1', role: 'admin' } };
|
|
264
|
+
const result = injections[0].resolve(null, context, mockContainer);
|
|
265
|
+
expect(mockContainer.resolve).toHaveBeenCalledWith(casl_service_1.CaslService);
|
|
266
|
+
expect(mockCaslSvc.createForUser).toHaveBeenCalledWith({ sub: 'u1', role: 'admin' });
|
|
267
|
+
expect(result).toBe(mockAbility);
|
|
268
|
+
});
|
|
269
|
+
it('resolve() falls back to empty user object when context.user is undefined', () => {
|
|
270
|
+
const mockCaslSvc = { createForUser: jest.fn().mockReturnValue({}) };
|
|
271
|
+
const mockContainer = { resolve: jest.fn().mockReturnValue(mockCaslSvc) };
|
|
272
|
+
class TestController {
|
|
273
|
+
getTask(_ability) {
|
|
274
|
+
return _ability;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
__decorate([
|
|
278
|
+
__param(0, (0, ability_decorator_1.Ability)()),
|
|
279
|
+
__metadata("design:type", Function),
|
|
280
|
+
__metadata("design:paramtypes", [Object]),
|
|
281
|
+
__metadata("design:returntype", void 0)
|
|
282
|
+
], TestController.prototype, "getTask", null);
|
|
283
|
+
const injections = Reflect.getMetadata(INJECT_KEY, TestController, 'getTask');
|
|
284
|
+
injections[0].resolve(null, {}, mockContainer);
|
|
285
|
+
expect(mockCaslSvc.createForUser).toHaveBeenCalledWith({});
|
|
286
|
+
});
|
|
287
|
+
it('throws when used outside a method parameter', () => {
|
|
288
|
+
expect(() => {
|
|
289
|
+
const decorator = (0, ability_decorator_1.Ability)();
|
|
290
|
+
decorator({}, undefined, 0);
|
|
291
|
+
}).toThrow('@Ability() must be used on a method parameter');
|
|
292
|
+
});
|
|
293
|
+
it('does not overwrite existing injections at other parameter indices', () => {
|
|
294
|
+
class TestController {
|
|
295
|
+
getTask(_ability, _id) {
|
|
296
|
+
return _ability;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
__decorate([
|
|
300
|
+
__param(0, (0, ability_decorator_1.Ability)()),
|
|
301
|
+
__metadata("design:type", Function),
|
|
302
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
303
|
+
__metadata("design:returntype", void 0)
|
|
304
|
+
], TestController.prototype, "getTask", null);
|
|
305
|
+
// Only index 0 should be set; index 1 is unset
|
|
306
|
+
const injections = Reflect.getMetadata(INJECT_KEY, TestController, 'getTask');
|
|
307
|
+
expect(injections[0]).toBeDefined();
|
|
308
|
+
expect(injections[1]).toBeUndefined();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// AbilityFactory abstract class
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
describe('AbilityFactory', () => {
|
|
315
|
+
it('can be extended and createForUser can be implemented', () => {
|
|
316
|
+
const factory = new TestAbilityFactory();
|
|
317
|
+
const ability = factory.createForUser({ id: 'u1', role: 'admin' });
|
|
318
|
+
expect(ability.can('manage', 'all')).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
/**
|
|
3
|
+
* Parameter decorator that injects the current user's CASL ability directly
|
|
4
|
+
* into a controller method parameter.
|
|
5
|
+
*
|
|
6
|
+
* Requires `JwtAuthGuard` (or any guard that sets `req.user`) to run before
|
|
7
|
+
* the route handler, and `CaslModule.forRoot()` to be configured.
|
|
8
|
+
*
|
|
9
|
+
* The ability is resolved once per request by calling
|
|
10
|
+
* `CaslService.createForUser(context.user)`. No need to inject `CaslService`
|
|
11
|
+
* into your services — pass the pre-built ability down instead.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { UseGuards } from '@hazeljs/core';
|
|
16
|
+
* import { JwtAuthGuard } from '@hazeljs/auth';
|
|
17
|
+
* import { Ability } from '@hazeljs/casl';
|
|
18
|
+
* import type { AppAbility } from './casl/app-ability.factory';
|
|
19
|
+
*
|
|
20
|
+
* @UseGuards(JwtAuthGuard, RoleGuard('user'))
|
|
21
|
+
* @Patch('/:id')
|
|
22
|
+
* update(
|
|
23
|
+
* @Ability() ability: AppAbility,
|
|
24
|
+
* @Param('id') id: string,
|
|
25
|
+
* @Body() dto: UpdateTaskDto,
|
|
26
|
+
* ) {
|
|
27
|
+
* return this.tasksService.update(ability, id, dto);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function Ability(): ParameterDecorator;
|
|
32
|
+
//# sourceMappingURL=ability.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ability.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/ability.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAM1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,OAAO,IAAI,kBAAkB,CA6B5C"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Ability = Ability;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const casl_service_1 = require("../casl.service");
|
|
6
|
+
const INJECT_METADATA_KEY = 'hazel:inject';
|
|
7
|
+
/**
|
|
8
|
+
* Parameter decorator that injects the current user's CASL ability directly
|
|
9
|
+
* into a controller method parameter.
|
|
10
|
+
*
|
|
11
|
+
* Requires `JwtAuthGuard` (or any guard that sets `req.user`) to run before
|
|
12
|
+
* the route handler, and `CaslModule.forRoot()` to be configured.
|
|
13
|
+
*
|
|
14
|
+
* The ability is resolved once per request by calling
|
|
15
|
+
* `CaslService.createForUser(context.user)`. No need to inject `CaslService`
|
|
16
|
+
* into your services — pass the pre-built ability down instead.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { UseGuards } from '@hazeljs/core';
|
|
21
|
+
* import { JwtAuthGuard } from '@hazeljs/auth';
|
|
22
|
+
* import { Ability } from '@hazeljs/casl';
|
|
23
|
+
* import type { AppAbility } from './casl/app-ability.factory';
|
|
24
|
+
*
|
|
25
|
+
* @UseGuards(JwtAuthGuard, RoleGuard('user'))
|
|
26
|
+
* @Patch('/:id')
|
|
27
|
+
* update(
|
|
28
|
+
* @Ability() ability: AppAbility,
|
|
29
|
+
* @Param('id') id: string,
|
|
30
|
+
* @Body() dto: UpdateTaskDto,
|
|
31
|
+
* ) {
|
|
32
|
+
* return this.tasksService.update(ability, id, dto);
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function Ability() {
|
|
37
|
+
return (target, propertyKey, parameterIndex) => {
|
|
38
|
+
if (!propertyKey) {
|
|
39
|
+
throw new Error('@Ability() must be used on a method parameter');
|
|
40
|
+
}
|
|
41
|
+
const constructor = target.constructor;
|
|
42
|
+
const injections = Reflect.getMetadata(INJECT_METADATA_KEY, constructor, propertyKey) ?? [];
|
|
43
|
+
injections[parameterIndex] = {
|
|
44
|
+
type: 'custom',
|
|
45
|
+
// Called by the router with (req, context, container) after guards have run.
|
|
46
|
+
resolve: (_req, context, container) => {
|
|
47
|
+
const user = (context.user ?? {});
|
|
48
|
+
const caslSvc = container.resolve(casl_service_1.CaslService);
|
|
49
|
+
return caslSvc.createForUser(user);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
Reflect.defineMetadata(INJECT_METADATA_KEY, injections, constructor, propertyKey);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { AnyAbility } from '@casl/ability';
|
|
3
|
+
import type { AnyPolicyHandler } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Shorthand method decorator that registers one or more CASL policy handlers
|
|
6
|
+
* directly on the route — equivalent to `@UseGuards(PoliciesGuard(...handlers))`.
|
|
7
|
+
*
|
|
8
|
+
* Accepts both **function** handlers and **class-instance** handlers that
|
|
9
|
+
* implement `IPolicyHandler`.
|
|
10
|
+
*
|
|
11
|
+
* > Requires `JwtAuthGuard` (or any guard that sets `req.user`) to run first,
|
|
12
|
+
* > and `CaslModule.forRoot()` to be configured.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // Function handler (inline, no DI needed)
|
|
17
|
+
* @CheckPolicies((ability: AppAbility) => ability.can('read', 'Post'))
|
|
18
|
+
* @Get('/')
|
|
19
|
+
* list() { ... }
|
|
20
|
+
*
|
|
21
|
+
* // Multiple handlers — all must pass
|
|
22
|
+
* @CheckPolicies(
|
|
23
|
+
* (ability: AppAbility) => ability.can('read', 'Post'),
|
|
24
|
+
* (ability: AppAbility) => ability.can('update', 'Post'),
|
|
25
|
+
* )
|
|
26
|
+
* @Get('/:id/edit')
|
|
27
|
+
* editForm() { ... }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function CheckPolicies<A extends AnyAbility>(...handlers: AnyPolicyHandler<A>[]): MethodDecorator;
|
|
31
|
+
//# sourceMappingURL=check-policies.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-policies.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/check-policies.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGjD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAChD,GAAG,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,GACjC,eAAe,CAWjB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckPolicies = CheckPolicies;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const policy_guard_1 = require("../policy.guard");
|
|
6
|
+
/**
|
|
7
|
+
* Shorthand method decorator that registers one or more CASL policy handlers
|
|
8
|
+
* directly on the route — equivalent to `@UseGuards(PoliciesGuard(...handlers))`.
|
|
9
|
+
*
|
|
10
|
+
* Accepts both **function** handlers and **class-instance** handlers that
|
|
11
|
+
* implement `IPolicyHandler`.
|
|
12
|
+
*
|
|
13
|
+
* > Requires `JwtAuthGuard` (or any guard that sets `req.user`) to run first,
|
|
14
|
+
* > and `CaslModule.forRoot()` to be configured.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // Function handler (inline, no DI needed)
|
|
19
|
+
* @CheckPolicies((ability: AppAbility) => ability.can('read', 'Post'))
|
|
20
|
+
* @Get('/')
|
|
21
|
+
* list() { ... }
|
|
22
|
+
*
|
|
23
|
+
* // Multiple handlers — all must pass
|
|
24
|
+
* @CheckPolicies(
|
|
25
|
+
* (ability: AppAbility) => ability.can('read', 'Post'),
|
|
26
|
+
* (ability: AppAbility) => ability.can('update', 'Post'),
|
|
27
|
+
* )
|
|
28
|
+
* @Get('/:id/edit')
|
|
29
|
+
* editForm() { ... }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function CheckPolicies(...handlers) {
|
|
33
|
+
const guardClass = (0, policy_guard_1.PoliciesGuard)(...handlers);
|
|
34
|
+
// `target` is the class prototype; `propertyKey` is the method name.
|
|
35
|
+
// This mirrors the exact metadata key/target pair that @UseGuards uses for
|
|
36
|
+
// method-level guards, and that the HazelJS router reads.
|
|
37
|
+
return (target, propertyKey, descriptor) => {
|
|
38
|
+
const existing = Reflect.getMetadata('hazel:guards', target, propertyKey) ?? [];
|
|
39
|
+
Reflect.defineMetadata('hazel:guards', [...existing, guardClass], target, propertyKey);
|
|
40
|
+
return descriptor;
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { AbilityFactory } from './ability.factory';
|
|
2
|
+
export { CaslService } from './casl.service';
|
|
3
|
+
export { CaslModule } from './casl.module';
|
|
4
|
+
export type { CaslModuleOptions } from './casl.module';
|
|
5
|
+
export { PoliciesGuard } from './policy.guard';
|
|
6
|
+
export { CheckPolicies } from './decorators/check-policies.decorator';
|
|
7
|
+
export { Ability } from './decorators/ability.decorator';
|
|
8
|
+
export type { PolicyHandler, IPolicyHandler, AnyPolicyHandler } from './types';
|
|
9
|
+
export { AbilityBuilder, createMongoAbility, subject } from '@casl/ability';
|
|
10
|
+
export type { MongoAbility, AnyAbility } from '@casl/ability';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAK/E,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.subject = exports.createMongoAbility = exports.AbilityBuilder = exports.Ability = exports.CheckPolicies = exports.PoliciesGuard = exports.CaslModule = exports.CaslService = exports.AbilityFactory = void 0;
|
|
4
|
+
var ability_factory_1 = require("./ability.factory");
|
|
5
|
+
Object.defineProperty(exports, "AbilityFactory", { enumerable: true, get: function () { return ability_factory_1.AbilityFactory; } });
|
|
6
|
+
var casl_service_1 = require("./casl.service");
|
|
7
|
+
Object.defineProperty(exports, "CaslService", { enumerable: true, get: function () { return casl_service_1.CaslService; } });
|
|
8
|
+
var casl_module_1 = require("./casl.module");
|
|
9
|
+
Object.defineProperty(exports, "CaslModule", { enumerable: true, get: function () { return casl_module_1.CaslModule; } });
|
|
10
|
+
var policy_guard_1 = require("./policy.guard");
|
|
11
|
+
Object.defineProperty(exports, "PoliciesGuard", { enumerable: true, get: function () { return policy_guard_1.PoliciesGuard; } });
|
|
12
|
+
var check_policies_decorator_1 = require("./decorators/check-policies.decorator");
|
|
13
|
+
Object.defineProperty(exports, "CheckPolicies", { enumerable: true, get: function () { return check_policies_decorator_1.CheckPolicies; } });
|
|
14
|
+
var ability_decorator_1 = require("./decorators/ability.decorator");
|
|
15
|
+
Object.defineProperty(exports, "Ability", { enumerable: true, get: function () { return ability_decorator_1.Ability; } });
|
|
16
|
+
// Re-export the most commonly used @casl/ability symbols so applications
|
|
17
|
+
// that depend only on @hazeljs/casl do not need @casl/ability as a direct
|
|
18
|
+
// dependency in their own package.json.
|
|
19
|
+
var ability_1 = require("@casl/ability");
|
|
20
|
+
Object.defineProperty(exports, "AbilityBuilder", { enumerable: true, get: function () { return ability_1.AbilityBuilder; } });
|
|
21
|
+
Object.defineProperty(exports, "createMongoAbility", { enumerable: true, get: function () { return ability_1.createMongoAbility; } });
|
|
22
|
+
Object.defineProperty(exports, "subject", { enumerable: true, get: function () { return ability_1.subject; } });
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CanActivate, Type } from '@hazeljs/core';
|
|
2
|
+
import type { AnyAbility } from '@casl/ability';
|
|
3
|
+
import type { AnyPolicyHandler } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Factory that returns a guard running all provided policy handlers against
|
|
6
|
+
* the current user's CASL ability. Every handler must return `true`; if any
|
|
7
|
+
* returns `false` the guard throws a 403.
|
|
8
|
+
*
|
|
9
|
+
* Must be placed **after** `JwtAuthGuard` (or any guard that sets `req.user`).
|
|
10
|
+
* Requires `CaslModule.forRoot()` to be configured.
|
|
11
|
+
*
|
|
12
|
+
* Accepts both function handlers and class-instance handlers:
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // Inline function handler
|
|
17
|
+
* @UseGuards(JwtAuthGuard, PoliciesGuard((ability: AppAbility) => ability.can('read', 'Post')))
|
|
18
|
+
* @Get('/')
|
|
19
|
+
* list() { ... }
|
|
20
|
+
*
|
|
21
|
+
* // Class-instance handler (dependencies resolved manually or via new)
|
|
22
|
+
* @UseGuards(JwtAuthGuard, PoliciesGuard(new CanCreatePost()))
|
|
23
|
+
* @Post('/')
|
|
24
|
+
* create() { ... }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Prefer the `@CheckPolicies()` decorator for cleaner syntax:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* @CheckPolicies((ability: AppAbility) => ability.can('read', 'Post'))
|
|
31
|
+
* @Get('/')
|
|
32
|
+
* list() { ... }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function PoliciesGuard<A extends AnyAbility>(...handlers: AnyPolicyHandler<A>[]): Type<CanActivate>;
|
|
36
|
+
//# sourceMappingURL=policy.guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.guard.d.ts","sourceRoot":"","sources":["../src/policy.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,WAAW,EAAoB,IAAI,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAiC,MAAM,SAAS,CAAC;AAY/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAChD,GAAG,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,GACjC,IAAI,CAAC,WAAW,CAAC,CAqCnB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.PoliciesGuard = PoliciesGuard;
|
|
13
|
+
const core_1 = require("@hazeljs/core");
|
|
14
|
+
const casl_service_1 = require("./casl.service");
|
|
15
|
+
function isClassHandler(handler) {
|
|
16
|
+
return (typeof handler === 'object' &&
|
|
17
|
+
handler !== null &&
|
|
18
|
+
typeof handler.handle === 'function');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Factory that returns a guard running all provided policy handlers against
|
|
22
|
+
* the current user's CASL ability. Every handler must return `true`; if any
|
|
23
|
+
* returns `false` the guard throws a 403.
|
|
24
|
+
*
|
|
25
|
+
* Must be placed **after** `JwtAuthGuard` (or any guard that sets `req.user`).
|
|
26
|
+
* Requires `CaslModule.forRoot()` to be configured.
|
|
27
|
+
*
|
|
28
|
+
* Accepts both function handlers and class-instance handlers:
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* // Inline function handler
|
|
33
|
+
* @UseGuards(JwtAuthGuard, PoliciesGuard((ability: AppAbility) => ability.can('read', 'Post')))
|
|
34
|
+
* @Get('/')
|
|
35
|
+
* list() { ... }
|
|
36
|
+
*
|
|
37
|
+
* // Class-instance handler (dependencies resolved manually or via new)
|
|
38
|
+
* @UseGuards(JwtAuthGuard, PoliciesGuard(new CanCreatePost()))
|
|
39
|
+
* @Post('/')
|
|
40
|
+
* create() { ... }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Prefer the `@CheckPolicies()` decorator for cleaner syntax:
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* @CheckPolicies((ability: AppAbility) => ability.can('read', 'Post'))
|
|
47
|
+
* @Get('/')
|
|
48
|
+
* list() { ... }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function PoliciesGuard(...handlers) {
|
|
52
|
+
let PoliciesGuardMixin = class PoliciesGuardMixin {
|
|
53
|
+
constructor(casl) {
|
|
54
|
+
this.casl = casl;
|
|
55
|
+
}
|
|
56
|
+
async canActivate(context) {
|
|
57
|
+
if (handlers.length === 0)
|
|
58
|
+
return true;
|
|
59
|
+
const req = context.switchToHttp().getRequest();
|
|
60
|
+
const user = req.user;
|
|
61
|
+
if (!user) {
|
|
62
|
+
throw Object.assign(new Error('Unauthorized'), { status: 401 });
|
|
63
|
+
}
|
|
64
|
+
const ability = this.casl.createForUser(user);
|
|
65
|
+
for (const handler of handlers) {
|
|
66
|
+
let result;
|
|
67
|
+
if (isClassHandler(handler)) {
|
|
68
|
+
result = await handler.handle(ability);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
result = handler(ability);
|
|
72
|
+
}
|
|
73
|
+
if (!result) {
|
|
74
|
+
throw Object.assign(new Error('Insufficient permissions for this action'), {
|
|
75
|
+
status: 403,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
PoliciesGuardMixin = __decorate([
|
|
83
|
+
(0, core_1.Injectable)(),
|
|
84
|
+
__metadata("design:paramtypes", [casl_service_1.CaslService])
|
|
85
|
+
], PoliciesGuardMixin);
|
|
86
|
+
return PoliciesGuardMixin;
|
|
87
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AnyAbility } from '@casl/ability';
|
|
2
|
+
/**
|
|
3
|
+
* A function-style policy handler. Receives the current user's ability and
|
|
4
|
+
* returns `true` if the action is allowed.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const canReadPost: PolicyHandler<AppAbility> =
|
|
9
|
+
* (ability) => ability.can('read', 'Post');
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export type PolicyHandler<A extends AnyAbility = AnyAbility> = (ability: A) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Class-style policy handler. Implement this interface and pass the class to
|
|
15
|
+
* `@CheckPolicies()` when you need constructor-injected dependencies.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* @Injectable()
|
|
20
|
+
* export class CanCreatePost implements IPolicyHandler<AppAbility> {
|
|
21
|
+
* handle(ability: AppAbility) {
|
|
22
|
+
* return ability.can('create', 'Post');
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface IPolicyHandler<A extends AnyAbility = AnyAbility> {
|
|
28
|
+
handle(ability: A): boolean | Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
/** Union of the two supported handler shapes. */
|
|
31
|
+
export type AnyPolicyHandler<A extends AnyAbility = AnyAbility> = PolicyHandler<A> | IPolicyHandler<A>;
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC;AAEvF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC/D,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChD;AAED,iDAAiD;AACjD,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAC1D,aAAa,CAAC,CAAC,CAAC,GAChB,cAAc,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/casl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Attribute-level (resource-level) authorization for HazelJS via CASL",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@hazeljs/core": ">=0.2.0-beta.0"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "e0ed98ca074dd4f7472816d3c32ef576900dcca6"
|
|
56
56
|
}
|