@dudousxd/nestjs-authz 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/can-endpoint.controller.d.ts +39 -0
  3. package/dist/can-endpoint.controller.d.ts.map +1 -0
  4. package/dist/can-endpoint.controller.js +97 -0
  5. package/dist/can-endpoint.controller.js.map +1 -0
  6. package/dist/decorator/roles.decorator.d.ts +15 -0
  7. package/dist/decorator/roles.decorator.d.ts.map +1 -0
  8. package/dist/decorator/roles.decorator.js +19 -0
  9. package/dist/decorator/roles.decorator.js.map +1 -0
  10. package/dist/diagnostics.d.ts +42 -0
  11. package/dist/diagnostics.d.ts.map +1 -0
  12. package/dist/diagnostics.js +68 -0
  13. package/dist/diagnostics.js.map +1 -0
  14. package/dist/gate.d.ts +42 -1
  15. package/dist/gate.d.ts.map +1 -1
  16. package/dist/gate.js +116 -12
  17. package/dist/gate.js.map +1 -1
  18. package/dist/guard/roles.guard.d.ts +21 -0
  19. package/dist/guard/roles.guard.d.ts.map +1 -0
  20. package/dist/guard/roles.guard.js +50 -0
  21. package/dist/guard/roles.guard.js.map +1 -0
  22. package/dist/index.d.ts +10 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/module.d.ts +13 -0
  27. package/dist/module.d.ts.map +1 -1
  28. package/dist/module.js +48 -2
  29. package/dist/module.js.map +1 -1
  30. package/dist/permission-provider.d.ts +2 -0
  31. package/dist/permission-provider.d.ts.map +1 -1
  32. package/dist/policy-registry.d.ts +21 -0
  33. package/dist/policy-registry.d.ts.map +1 -1
  34. package/dist/policy-registry.js +42 -0
  35. package/dist/policy-registry.js.map +1 -1
  36. package/dist/role-provider.d.ts +40 -0
  37. package/dist/role-provider.d.ts.map +1 -0
  38. package/dist/role-provider.js +32 -0
  39. package/dist/role-provider.js.map +1 -0
  40. package/dist/tokens.d.ts +29 -0
  41. package/dist/tokens.d.ts.map +1 -1
  42. package/dist/tokens.js +29 -0
  43. package/dist/tokens.js.map +1 -1
  44. package/dist/types.d.ts +67 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,89 @@
1
1
  # @dudousxd/nestjs-authz
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4](https://github.com/DavideCarvalho/nestjs-authz/pull/4) [`8b7711d`](https://github.com/DavideCarvalho/nestjs-authz/commit/8b7711d11bdb25b3407fea742f6c1158afb36296) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add `resourceLoaders` to `AuthzModule.forRoot`/`forRootAsync` options — a map keyed by
8
+ the resource `type` name (e.g. `{ Post: (id) => postRepo.findOneBy({ id: Number(id) }) }`).
9
+ Closes the per-instance gap in the opt-in `POST /authz/can` fallback endpoint: when a loader
10
+ is registered for `resource.type`, the endpoint rehydrates the client's `{ type, id }` shim
11
+ into the REAL entity before authorizing, so an instance-bound `@Policy` matches by constructor
12
+ and its method decides correctly. A loader returning nullish is treated as "not found" (deny).
13
+ Types without a loader keep the prior class-level / ad-hoc-only behavior. Opt-in and additive:
14
+ unset → endpoint behavior unchanged. Also exposes a `RESOURCE_HYDRATOR` token and the
15
+ `ResourceLoader`/`ResourceLoaderMap` types.
16
+
17
+ ## 0.3.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#2](https://github.com/DavideCarvalho/nestjs-authz/pull/2) [`2ecb0f4`](https://github.com/DavideCarvalho/nestjs-authz/commit/2ecb0f46342fa4527fc01f1097720f2e7bcf9aa7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add coarse, role-based authorization alongside the granular ability checks.
22
+
23
+ - **`@Roles('admin', 'teacher')` + `RolesGuard`**: a route is allowed when the current
24
+ user holds ANY of the listed roles. Registered as an `APP_GUARD` by `AuthzModule`
25
+ (inert on un-annotated routes), it resolves the current user exactly as the `Gate`
26
+ does and denies an unauthenticated request by default.
27
+ - **`Gate.hasRole(role)` / `Gate.hasAnyRole(roles[])`** (and `gate.forUser(user).hasRole(...)`),
28
+ async, resolving the user's effective roles and testing membership.
29
+ - **Pluggable role source, two layers (unioned):**
30
+ 1. A default `RoleResolver` that reads roles off the user object — `user.roles`
31
+ (`string[]`) OR `user.role` (`string | string[]`), normalized to a `string[]`.
32
+ This makes role checks work with ZERO RBAC tables. Override via
33
+ `AuthzModule.forRoot({ resolveRoles })`.
34
+ 2. An OPTIONAL `ROLE_PROVIDER` seam (`Symbol.for('@dudousxd/nestjs-authz:role-provider')`,
35
+ consulted with `@Optional()`) — mirroring `PERMISSION_PROVIDER` — so an RBAC adapter
36
+ can supply roles from a store. When both yield roles, the Gate unions them; when
37
+ neither does, the check denies.
38
+
39
+ Exports the `Roles` decorator, `RolesGuard`, `RoleResolver`, `defaultRoleResolver`,
40
+ `ROLE_PROVIDER`, `ROLES_METADATA`, and a `RoleProvider` interface. Purely additive — the
41
+ existing permission-provider / can-endpoint / diagnostics behavior is unchanged.
42
+
43
+ ### Patch Changes
44
+
45
+ - [#2](https://github.com/DavideCarvalho/nestjs-authz/pull/2) [`2ecb0f4`](https://github.com/DavideCarvalho/nestjs-authz/commit/2ecb0f46342fa4527fc01f1097720f2e7bcf9aa7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add `@dudousxd/nestjs-authz-inertia`: a 3-tier Inertia integration that lets client `can(...)`
46
+ checks resolve **without a network request** (the Laravel/Inertia model).
47
+
48
+ - **Tier 1 — shared props (no request):** `AuthzInertiaModule` (a global interceptor that calls
49
+ `req.inertia.share(...)`) or `createAuthzShare(gate, policyRegistry)` (a factory for
50
+ `InertiaModule.forRoot({ share })`) resolves the current user's class-level abilities — all
51
+ ad-hoc gates + class-level `@Policy` methods — into `props.auth.can` via direct in-process
52
+ `gate.allows(...)` calls.
53
+ - **Tier 2 — per-resource map (no request):** `authorizeResource(gate, instance, abilities)`
54
+ returns a `{ update, delete }` map a controller attaches to a serialized resource.
55
+ - **Tier 3 — fallback endpoint (last resort):** consumes core's opt-in `POST /authz/can`.
56
+ - **Framework-neutral client** (`@dudousxd/nestjs-authz-inertia/client`): an `AbilityStore`,
57
+ `hydrateFromInertiaProps` / `hydrateResource`, and a `createCan` resolver that reads hydrated
58
+ decisions synchronously (no fetch on a cache hit) and only falls back (fetch the endpoint, or
59
+ deny) when the ability/resource is unknown.
60
+ - **Denial filter (`AuthzDenialFilter`):** `AuthzInertiaModule.forRoot` also registers a global
61
+ `APP_FILTER` that converts `@Roles`/`@Can` denials (`ForbiddenException`) into a friendly 303
62
+ redirect on Inertia requests (`X-Inertia` header) — `/login` when unauthenticated, `/403` when
63
+ authenticated-but-forbidden (auth-state read from the optional `CONTEXT_ACCESSOR`). Non-Inertia
64
+ (REST) requests are rethrown and keep the normal 403 JSON. Configurable via
65
+ `forRoot({ denial: { loginUrl, forbiddenUrl, enabled, handler } })` (`enabled: false` opts out;
66
+ `handler(exception, host)` is a full escape hatch). Targets pass the same open-redirect guard
67
+ nestjs-inertia uses, falling back to a safe default if unsafe.
68
+
69
+ `@dudousxd/nestjs-authz` (patch): add an opt-in `POST /authz/can` fallback controller behind
70
+ `AuthzModule.forRoot({ canEndpoint: true })` (default off; accepts a custom path string). It runs
71
+ `gate.allows(ability, resource?)` for the current context user and returns `{ allowed }`, failing
72
+ closed for unresolved abilities. Also exposes `Gate.gateNames()` and `PolicyRegistry.classAbilities()`
73
+ so integrations can enumerate a user's class-level abilities.
74
+
75
+ - [#2](https://github.com/DavideCarvalho/nestjs-authz/pull/2) [`2ecb0f4`](https://github.com/DavideCarvalho/nestjs-authz/commit/2ecb0f46342fa4527fc01f1097720f2e7bcf9aa7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add `@dudousxd/nestjs-authz-telescope`: a `@dudousxd/nestjs-telescope` extension that records every
76
+ authorization decision the `Gate` reaches (ability, allow/deny, the reason it was decided, the user
77
+ and the resource) as an `authorization` Telescope entry plus an "Authorization" dashboard page (a
78
+ top-N of denied abilities and a table of recent decisions) — so a 403 is debuggable. The extension's
79
+ `AuthorizationWatcher` subscribes to the new `nestjs-authz:decision` diagnostics channel; nothing is
80
+ emitted (and nothing recorded) when no observer is listening.
81
+
82
+ The core `Gate` now publishes each decision on a dependency-free `node:diagnostics_channel`
83
+ (`nestjs-authz:decision`, exported as `AUTHZ_DECISION_CHANNEL`) after a verdict is reached. The
84
+ emission is gated on `channel.hasSubscribers` and fully guarded, so it is zero-overhead with no
85
+ subscriber and can never affect a check. No existing behavior changes.
86
+
3
87
  ## 0.2.0
4
88
 
5
89
  ### Minor Changes
@@ -0,0 +1,39 @@
1
+ import { type Type } from '@nestjs/common';
2
+ /**
3
+ * Request body for the fallback `can` endpoint. Mirrors the payload emitted by
4
+ * the codegen `can()` helper: `{ ability, resource?: { type, id } | null }`.
5
+ */
6
+ export interface CanRequestBody {
7
+ ability: string;
8
+ resource?: {
9
+ type: string;
10
+ id?: string | number;
11
+ } | null;
12
+ }
13
+ /** Response shape: a single boolean verdict. */
14
+ export interface CanResponseBody {
15
+ allowed: boolean;
16
+ }
17
+ /**
18
+ * Build the opt-in fallback controller class, mounted at `path`. Kept as a
19
+ * factory so the route path is configurable via `AuthzModule.forRoot({ canEndpoint })`.
20
+ *
21
+ * The endpoint runs `gate.allows(ability, resource?)` for the CURRENT (context)
22
+ * user.
23
+ *
24
+ * RESOURCE REHYDRATION — how per-instance decisions resolve:
25
+ * the client sends a `{ type, id }` shim whose constructor is `Object`, which never
26
+ * matches a registered instance `@Policy` (those are keyed by the entity constructor).
27
+ * To bridge that, register `resourceLoaders` in `AuthzModule.forRoot({ resourceLoaders })`
28
+ * (keyed by the `type` name): when a loader exists for `resource.type`, the endpoint
29
+ * `await`s `loader(id)` and authorizes the REAL entity, so the instance `@Policy`
30
+ * matches and its method runs. A loader returning nullish → `{ allowed: false }` (not
31
+ * found). With NO loader for the type the raw shim is forwarded as before, so only
32
+ * class-level abilities and ad-hoc gates resolve and a resource-bound ability denies
33
+ * (a deliberate, documented fall-through, not a silent dead-end). An unresolved ability
34
+ * is likewise treated as a deny (never a 500).
35
+ */
36
+ export declare function createCanController(path: string): Type<unknown>;
37
+ /** Default mount path for the fallback endpoint (matches codegen's `/authz/can`). */
38
+ export declare const DEFAULT_CAN_ENDPOINT_PATH = "authz/can";
39
+ //# sourceMappingURL=can-endpoint.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"can-endpoint.controller.d.ts","sourceRoot":"","sources":["../src/can-endpoint.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAM/E;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC1D;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAgD/D;AAED,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,cAAc,CAAC"}
@@ -0,0 +1,97 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Body, Controller, Inject, Optional } from '@nestjs/common';
14
+ import { Post as HttpPost } from '@nestjs/common';
15
+ import { Gate } from './gate.js';
16
+ import { RESOURCE_HYDRATOR } from './tokens.js';
17
+ /**
18
+ * Build the opt-in fallback controller class, mounted at `path`. Kept as a
19
+ * factory so the route path is configurable via `AuthzModule.forRoot({ canEndpoint })`.
20
+ *
21
+ * The endpoint runs `gate.allows(ability, resource?)` for the CURRENT (context)
22
+ * user.
23
+ *
24
+ * RESOURCE REHYDRATION — how per-instance decisions resolve:
25
+ * the client sends a `{ type, id }` shim whose constructor is `Object`, which never
26
+ * matches a registered instance `@Policy` (those are keyed by the entity constructor).
27
+ * To bridge that, register `resourceLoaders` in `AuthzModule.forRoot({ resourceLoaders })`
28
+ * (keyed by the `type` name): when a loader exists for `resource.type`, the endpoint
29
+ * `await`s `loader(id)` and authorizes the REAL entity, so the instance `@Policy`
30
+ * matches and its method runs. A loader returning nullish → `{ allowed: false }` (not
31
+ * found). With NO loader for the type the raw shim is forwarded as before, so only
32
+ * class-level abilities and ad-hoc gates resolve and a resource-bound ability denies
33
+ * (a deliberate, documented fall-through, not a silent dead-end). An unresolved ability
34
+ * is likewise treated as a deny (never a 500).
35
+ */
36
+ export function createCanController(path) {
37
+ let AuthzCanController = class AuthzCanController {
38
+ gate;
39
+ loaders;
40
+ constructor(gate, loaders) {
41
+ this.gate = gate;
42
+ this.loaders = loaders;
43
+ }
44
+ async can(body) {
45
+ const ability = body?.ability;
46
+ if (typeof ability !== 'string' || ability.length === 0) {
47
+ return { allowed: false };
48
+ }
49
+ const resource = body?.resource ?? undefined;
50
+ try {
51
+ const target = await this.rehydrate(resource);
52
+ // A registered loader that produced nothing → not found → deny.
53
+ if (target === null)
54
+ return { allowed: false };
55
+ const allowed = await this.gate.allows(ability, target);
56
+ return { allowed };
57
+ }
58
+ catch {
59
+ // Unresolved/ambiguous ability — fail closed.
60
+ return { allowed: false };
61
+ }
62
+ }
63
+ /**
64
+ * Turn the client's `{ type, id }` shim into the resource the gate authorizes.
65
+ * - No resource → `undefined` (class-level ability).
66
+ * - Loader registered for `type` → the loaded entity, or `null` when the loader
67
+ * yields nullish ("not found", which the caller maps to a deny).
68
+ * - No loader for `type` → the raw shim, forwarded as before (class-level / gate only).
69
+ */
70
+ async rehydrate(resource) {
71
+ if (resource == null)
72
+ return undefined;
73
+ const loader = this.loaders?.[resource.type];
74
+ if (typeof loader !== 'function')
75
+ return resource;
76
+ const loaded = await loader(resource.id);
77
+ return loaded == null ? null : loaded;
78
+ }
79
+ };
80
+ __decorate([
81
+ HttpPost(),
82
+ __param(0, Body()),
83
+ __metadata("design:type", Function),
84
+ __metadata("design:paramtypes", [Object]),
85
+ __metadata("design:returntype", Promise)
86
+ ], AuthzCanController.prototype, "can", null);
87
+ AuthzCanController = __decorate([
88
+ Controller(path),
89
+ __param(1, Optional()),
90
+ __param(1, Inject(RESOURCE_HYDRATOR)),
91
+ __metadata("design:paramtypes", [Gate, Object])
92
+ ], AuthzCanController);
93
+ return AuthzCanController;
94
+ }
95
+ /** Default mount path for the fallback endpoint (matches codegen's `/authz/can`). */
96
+ export const DEFAULT_CAN_ENDPOINT_PATH = 'authz/can';
97
+ //# sourceMappingURL=can-endpoint.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"can-endpoint.controller.js","sourceRoot":"","sources":["../src/can-endpoint.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAa,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAiBhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IACM,kBAAkB,GADxB,MACM,kBAAkB;QAEH;QAGA;QAJnB,YACmB,IAAU,EAGV,OAA2B;YAH3B,SAAI,GAAJ,IAAI,CAAM;YAGV,YAAO,GAAP,OAAO,CAAoB;QAC3C,CAAC;QAGE,AAAN,KAAK,CAAC,GAAG,CAAS,IAAoB;YACpC,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;YAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YAC5B,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,IAAI,SAAS,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAC9C,gEAAgE;gBAChE,IAAI,MAAM,KAAK,IAAI;oBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;gBAC/C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBACxD,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,8CAA8C;gBAC9C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YAC5B,CAAC;QACH,CAAC;QAED;;;;;;WAMG;QACK,KAAK,CAAC,SAAS,CACrB,QAAgD;YAEhD,IAAI,QAAQ,IAAI,IAAI;gBAAE,OAAO,SAAS,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,OAAO,MAAM,KAAK,UAAU;gBAAE,OAAO,QAAkB,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAqB,CAAC,CAAC;YAC5D,OAAO,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,MAAiB,CAAC;QACpD,CAAC;KACF,CAAA;IAlCO;QADL,QAAQ,EAAE;QACA,WAAA,IAAI,EAAE,CAAA;;;;iDAgBhB;IAzBG,kBAAkB;QADvB,UAAU,CAAC,IAAI,CAAC;QAIZ,WAAA,QAAQ,EAAE,CAAA;QACV,WAAA,MAAM,CAAC,iBAAiB,CAAC,CAAA;yCAFH,IAAI;OAFzB,kBAAkB,CA2CvB;IAED,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,yBAAyB,GAAG,WAAW,CAAC"}
@@ -0,0 +1,15 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Guard a route by COARSE role membership: the request is allowed when the current
4
+ * user holds ANY of the listed roles. This is the broad "is teacher?" check, as
5
+ * opposed to the granular ability check of `@Can`.
6
+ *
7
+ * Roles come from the user object (`user.roles` / `user.role`) by default, plus the
8
+ * optional `ROLE_PROVIDER` seam (a persisted RBAC store) when registered — see
9
+ * {@link RolesGuard}.
10
+ *
11
+ * @example
12
+ * `@Roles('admin', 'teacher')` // allow if the user is an admin OR a teacher
13
+ */
14
+ export declare function Roles(...roles: string[]): MethodDecorator & ClassDecorator;
15
+ //# sourceMappingURL=roles.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/roles.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAI1B;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,GAAG,cAAc,CAE1E"}
@@ -0,0 +1,19 @@
1
+ import 'reflect-metadata';
2
+ import { SetMetadata } from '@nestjs/common';
3
+ import { ROLES_METADATA } from '../tokens.js';
4
+ /**
5
+ * Guard a route by COARSE role membership: the request is allowed when the current
6
+ * user holds ANY of the listed roles. This is the broad "is teacher?" check, as
7
+ * opposed to the granular ability check of `@Can`.
8
+ *
9
+ * Roles come from the user object (`user.roles` / `user.role`) by default, plus the
10
+ * optional `ROLE_PROVIDER` seam (a persisted RBAC store) when registered — see
11
+ * {@link RolesGuard}.
12
+ *
13
+ * @example
14
+ * `@Roles('admin', 'teacher')` // allow if the user is an admin OR a teacher
15
+ */
16
+ export function Roles(...roles) {
17
+ return SetMetadata(ROLES_METADATA, roles);
18
+ }
19
+ //# sourceMappingURL=roles.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.decorator.js","sourceRoot":"","sources":["../../src/decorator/roles.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,CAAC,GAAG,KAAe;IACtC,OAAO,WAAW,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,42 @@
1
+ import diagnostics_channel from 'node:diagnostics_channel';
2
+ import type { Resource, User } from './types.js';
3
+ /**
4
+ * Channel name — the cross-repo wire contract with `@dudousxd/nestjs-authz-telescope`'s
5
+ * authorization watcher (and any other observer). Versioned by the payload's `v` field,
6
+ * not by name. Keep this string byte-identical on both sides.
7
+ */
8
+ export declare const AUTHZ_DECISION_CHANNEL = "nestjs-authz:decision";
9
+ /**
10
+ * Memoized by Node: the same channel object is returned for the same name. Read
11
+ * `.hasSubscribers` to gate any work before publishing — when nothing subscribes
12
+ * (the common case) emitting a decision is effectively free.
13
+ */
14
+ export declare const authzDecisionChannel: diagnostics_channel.Channel<unknown, unknown>;
15
+ /**
16
+ * Every authorization decision the {@link Gate} reaches, published once per
17
+ * `allows`/`authorize`/`denies` call. `reason` names which resolution path
18
+ * produced the verdict so a 403 is debuggable.
19
+ */
20
+ export interface AuthzDecisionDiagnostic {
21
+ v: 1;
22
+ /** The ability checked, e.g. `'update'` or `'access-admin'`. */
23
+ ability: string;
24
+ /** The verdict — `true` allow, `false` deny. */
25
+ allowed: boolean;
26
+ /** Which resolution path decided it (see {@link AuthzDecisionReason}). */
27
+ reason: AuthzDecisionReason;
28
+ /** A label for the resolved user (`<type>#<id>` / constructor name), or `null` when anonymous. */
29
+ userRef: string | null;
30
+ /** The resource class name the ability targeted, or `null` for a model-less ability. */
31
+ resourceType: string | null;
32
+ /** The resource's `id` when discoverable on the instance, else `null`. */
33
+ resourceId: string | number | null;
34
+ }
35
+ /** The resolution path that produced a decision — the "why" behind a 403 (or a grant). */
36
+ export type AuthzDecisionReason = 'super-admin' | 'permission-provider' | 'policy-before' | 'policy' | 'gate' | 'anonymous';
37
+ /**
38
+ * Publish a decision to the channel, but only when something is listening. Never
39
+ * throws — building the payload or notifying a subscriber must not break a check.
40
+ */
41
+ export declare function publishAuthzDecision(ability: string, allowed: boolean, reason: AuthzDecisionReason, user: User, resource: Resource | undefined): void;
42
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,+CAAsD,CAAC;AAExF;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,CAAC,EAAE,CAAC,CAAC;IACL,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,MAAM,EAAE,mBAAmB,CAAC;IAC5B,kGAAkG;IAClG,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wFAAwF;IACxF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,0FAA0F;AAC1F,MAAM,MAAM,mBAAmB,GAC3B,aAAa,GACb,qBAAqB,GACrB,eAAe,GACf,QAAQ,GACR,MAAM,GACN,WAAW,CAAC;AAEhB;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAC7B,IAAI,CAgBN"}
@@ -0,0 +1,68 @@
1
+ import diagnostics_channel from 'node:diagnostics_channel';
2
+ /**
3
+ * Channel name — the cross-repo wire contract with `@dudousxd/nestjs-authz-telescope`'s
4
+ * authorization watcher (and any other observer). Versioned by the payload's `v` field,
5
+ * not by name. Keep this string byte-identical on both sides.
6
+ */
7
+ export const AUTHZ_DECISION_CHANNEL = 'nestjs-authz:decision';
8
+ /**
9
+ * Memoized by Node: the same channel object is returned for the same name. Read
10
+ * `.hasSubscribers` to gate any work before publishing — when nothing subscribes
11
+ * (the common case) emitting a decision is effectively free.
12
+ */
13
+ export const authzDecisionChannel = diagnostics_channel.channel(AUTHZ_DECISION_CHANNEL);
14
+ /**
15
+ * Publish a decision to the channel, but only when something is listening. Never
16
+ * throws — building the payload or notifying a subscriber must not break a check.
17
+ */
18
+ export function publishAuthzDecision(ability, allowed, reason, user, resource) {
19
+ if (!authzDecisionChannel.hasSubscribers)
20
+ return;
21
+ try {
22
+ const payload = {
23
+ v: 1,
24
+ ability,
25
+ allowed,
26
+ reason,
27
+ userRef: labelUser(user),
28
+ resourceType: resourceType(resource),
29
+ resourceId: resourceId(resource),
30
+ };
31
+ authzDecisionChannel.publish(payload);
32
+ }
33
+ catch {
34
+ // Observability must never break authorization.
35
+ }
36
+ }
37
+ /** A short, JSON-safe label for the user — `<type>#<id>`, a constructor name, or `null`. */
38
+ function labelUser(user) {
39
+ if (user == null)
40
+ return null;
41
+ if (typeof user === 'object') {
42
+ const obj = user;
43
+ const type = (obj.type ?? obj.constructor?.name);
44
+ const id = obj.id;
45
+ if (type && (typeof id === 'string' || typeof id === 'number'))
46
+ return `${type}#${id}`;
47
+ if (typeof obj.id === 'string' || typeof obj.id === 'number')
48
+ return String(obj.id);
49
+ return obj.constructor?.name ?? null;
50
+ }
51
+ return String(user);
52
+ }
53
+ /** The resource's class name (`Post`), or `null` when model-less. A class itself maps to its name. */
54
+ function resourceType(resource) {
55
+ if (resource == null)
56
+ return null;
57
+ if (typeof resource === 'function')
58
+ return resource.name ?? null;
59
+ return resource.constructor?.name ?? null;
60
+ }
61
+ /** The resource instance's `id`, when present and primitive; otherwise `null`. */
62
+ function resourceId(resource) {
63
+ if (resource == null || typeof resource === 'function')
64
+ return null;
65
+ const id = resource.id;
66
+ return typeof id === 'string' || typeof id === 'number' ? id : null;
67
+ }
68
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,0BAA0B,CAAC;AAG3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAgCxF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,OAAgB,EAChB,MAA2B,EAC3B,IAAU,EACV,QAA8B;IAE9B,IAAI,CAAC,oBAAoB,CAAC,cAAc;QAAE,OAAO;IACjD,IAAI,CAAC;QACH,MAAM,OAAO,GAA4B;YACvC,CAAC,EAAE,CAAC;YACJ,OAAO;YACP,OAAO;YACP,MAAM;YACN,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC;YACxB,YAAY,EAAE,YAAY,CAAC,QAAQ,CAAC;YACpC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC;SACjC,CAAC;QACF,oBAAoB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,SAAS,SAAS,CAAC,IAAU;IAC3B,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,IAA+B,CAAC;QAC5C,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,WAAW,EAAE,IAAI,CAAuB,CAAC;QACvE,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC;QAClB,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,CAAC;YAAE,OAAO,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QACvF,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpF,OAAO,GAAG,CAAC,WAAW,EAAE,IAAI,IAAI,IAAI,CAAC;IACvC,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,sGAAsG;AACtG,SAAS,YAAY,CAAC,QAA8B;IAClD,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,OAAO,QAAQ,KAAK,UAAU;QAAE,OAAQ,QAA8B,CAAC,IAAI,IAAI,IAAI,CAAC;IACxF,OAAQ,QAAgD,CAAC,WAAW,EAAE,IAAI,IAAI,IAAI,CAAC;AACrF,CAAC;AAED,kFAAkF;AAClF,SAAS,UAAU,CAAC,QAA8B;IAChD,IAAI,QAAQ,IAAI,IAAI,IAAI,OAAO,QAAQ,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IACpE,MAAM,EAAE,GAAI,QAAoC,CAAC,EAAE,CAAC;IACpD,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC"}
package/dist/gate.d.ts CHANGED
@@ -2,6 +2,7 @@ import { ModuleRef } from '@nestjs/core';
2
2
  import type { ContextAccessor } from './context-accessor.js';
3
3
  import type { PermissionProvider } from './permission-provider.js';
4
4
  import { PolicyRegistry } from './policy-registry.js';
5
+ import { type RoleProvider } from './role-provider.js';
5
6
  import type { AuthzModuleOptions, GateFn, Resource, User } from './types.js';
6
7
  declare const NO_USER: unique symbol;
7
8
  type MaybeUser = User | typeof NO_USER;
@@ -21,10 +22,12 @@ export declare class Gate {
21
22
  private readonly context?;
22
23
  private readonly moduleRef?;
23
24
  private readonly permissionProvider?;
25
+ private readonly roleProvider?;
24
26
  private readonly gates;
25
27
  private readonly superAdmin;
26
28
  private readonly resolveUser;
27
- constructor(policies: PolicyRegistry, options: AuthzModuleOptions | undefined, context?: ContextAccessor | undefined, moduleRef?: ModuleRef | undefined, permissionProvider?: PermissionProvider | undefined);
29
+ private readonly roleResolver;
30
+ constructor(policies: PolicyRegistry, options: AuthzModuleOptions | undefined, context?: ContextAccessor | undefined, moduleRef?: ModuleRef | undefined, permissionProvider?: PermissionProvider | undefined, roleProvider?: RoleProvider | undefined);
28
31
  /**
29
32
  * Locate the context accessor. Prefers the value injected into this module;
30
33
  * falls back to a non-strict {@link ModuleRef} lookup so an accessor provided
@@ -37,10 +40,22 @@ export declare class Gate {
37
40
  * provider registered by ANY module (e.g. the RBAC adapter's global module) is found.
38
41
  */
39
42
  private resolvePermissionProvider;
43
+ /**
44
+ * Locate the optional {@link RoleProvider} (the coarse role seam). Prefers the value
45
+ * injected into this module; falls back to a non-strict {@link ModuleRef} lookup so a
46
+ * provider registered by ANY module (e.g. the RBAC adapter's global module) is found.
47
+ */
48
+ private resolveRoleProvider;
40
49
  /** Register an ad-hoc, model-less gate resolved by `ability` name. */
41
50
  define(ability: string, fn: GateFn): this;
42
51
  /** True when an ad-hoc gate is registered for `ability`. */
43
52
  hasGate(ability: string): boolean;
53
+ /**
54
+ * Names of every ad-hoc gate registered via {@link define}. Used by integrations
55
+ * (e.g. `@dudousxd/nestjs-authz-inertia`) that enumerate the user's class-level
56
+ * abilities to share them as Inertia props — no network round-trip needed.
57
+ */
58
+ gateNames(): string[];
44
59
  /**
45
60
  * Bind an explicit user, bypassing the context accessor. Use when no
46
61
  * nestjs-context is wired, or to check a user other than the current one.
@@ -61,9 +76,31 @@ export declare class Gate {
61
76
  allows(ability: string, resource?: Resource): Promise<boolean>;
62
77
  denies(ability: string, resource?: Resource): Promise<boolean>;
63
78
  authorize(ability: string, resource?: Resource): Promise<void>;
79
+ /** True when the current user holds `role`. */
80
+ hasRole(role: string): Promise<boolean>;
81
+ /** True when the current user holds ANY of `roles`. */
82
+ hasAnyRole(roles: string[]): Promise<boolean>;
64
83
  /** @internal */
65
84
  allowsForUser(user: MaybeUser, ability: string, resource?: Resource): Promise<boolean>;
85
+ /** @internal */
86
+ hasAnyRoleForUser(user: MaybeUser, roles: string[]): Promise<boolean>;
87
+ /**
88
+ * Resolve the user's effective roles and test membership against `roles`. Returns
89
+ * `false` for an anonymous (NO_USER) caller and whenever no source yields a role
90
+ * (deny-by-default). Roles come from the UNION of the default/overridden
91
+ * {@link RoleResolver} (reads the user object) and the optional {@link RoleProvider}
92
+ * seam (a persisted store) — so an app needs neither to opt in.
93
+ */
94
+ private checkRoles;
95
+ /** The current user's effective role names (resolver ∪ provider). */
96
+ private rolesOf;
66
97
  private check;
98
+ /**
99
+ * Resolve an ability to a verdict plus the path that decided it. Throws
100
+ * {@link AbilityNotResolvedException}/{@link AmbiguousAbilityException} when no
101
+ * decision can be reached (those paths emit no decision).
102
+ */
103
+ private resolve;
67
104
  private resolvePolicy;
68
105
  }
69
106
  /**
@@ -76,6 +113,10 @@ export declare class BoundGate {
76
113
  allows(ability: string, resource?: Resource): Promise<boolean>;
77
114
  denies(ability: string, resource?: Resource): Promise<boolean>;
78
115
  authorize(ability: string, resource?: Resource): Promise<void>;
116
+ /** True when the bound user holds `role`. */
117
+ hasRole(role: string): Promise<boolean>;
118
+ /** True when the bound user holds ANY of `roles`. */
119
+ hasAnyRole(roles: string[]): Promise<boolean>;
79
120
  }
80
121
  export {};
81
122
  //# sourceMappingURL=gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAW,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,KAAK,EACV,kBAAkB,EAClB,MAAM,EAGN,QAAQ,EAER,IAAI,EACL,MAAM,YAAY,CAAC;AAIpB,QAAA,MAAM,OAAO,eAA0B,CAAC;AACxC,KAAK,SAAS,GAAG,IAAI,GAAG,OAAO,OAAO,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,qBACa,IAAI;IAMb,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAMzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAG3B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAhBtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;gBAG7C,QAAQ,EAAE,cAAc,EAGzC,OAAO,EAAE,kBAAkB,GAAG,SAAS,EAGtB,OAAO,CAAC,EAAE,eAAe,YAAA,EAEzB,SAAS,CAAC,EAAE,SAAS,YAAA,EAGrB,kBAAkB,CAAC,EAAE,kBAAkB,YAAA;IAM1D;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAUjC,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAKzC,4DAA4D;IAC5D,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIjC;;;;;;;;;OASG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS;IAI9B;;;;OAIG;YACW,WAAW;IAcnB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAI9D,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAI9D,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpE,gBAAgB;IAChB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAIxE,KAAK;IAsDnB,OAAO,CAAC,aAAa;CAwBtB;AAED;;GAEG;AACH,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS;IAGlC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAI9D,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAKrE"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}