@dudousxd/nestjs-authz 0.2.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/context-accessor.d.ts +31 -0
- package/dist/context-accessor.d.ts.map +1 -0
- package/dist/context-accessor.js +2 -0
- package/dist/context-accessor.js.map +1 -0
- package/dist/decorator/can.decorator.d.ts +29 -0
- package/dist/decorator/can.decorator.d.ts.map +1 -0
- package/dist/decorator/can.decorator.js +23 -0
- package/dist/decorator/can.decorator.js.map +1 -0
- package/dist/decorator/policy.decorator.d.ts +18 -0
- package/dist/decorator/policy.decorator.d.ts.map +1 -0
- package/dist/decorator/policy.decorator.js +28 -0
- package/dist/decorator/policy.decorator.js.map +1 -0
- package/dist/errors/exceptions.d.ts +37 -0
- package/dist/errors/exceptions.d.ts.map +1 -0
- package/dist/errors/exceptions.js +53 -0
- package/dist/errors/exceptions.js.map +1 -0
- package/dist/gate.d.ts +81 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +247 -0
- package/dist/gate.js.map +1 -0
- package/dist/guard/can.guard.d.ts +31 -0
- package/dist/guard/can.guard.d.ts.map +1 -0
- package/dist/guard/can.guard.js +92 -0
- package/dist/guard/can.guard.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +20 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +194 -0
- package/dist/module.js.map +1 -0
- package/dist/permission-provider.d.ts +23 -0
- package/dist/permission-provider.d.ts.map +1 -0
- package/dist/permission-provider.js +2 -0
- package/dist/permission-provider.js.map +1 -0
- package/dist/policy-registry.d.ts +23 -0
- package/dist/policy-registry.d.ts.map +1 -0
- package/dist/policy-registry.js +49 -0
- package/dist/policy-registry.js.map +1 -0
- package/dist/resource-resolver.d.ts +29 -0
- package/dist/resource-resolver.d.ts.map +1 -0
- package/dist/resource-resolver.js +24 -0
- package/dist/resource-resolver.js.map +1 -0
- package/dist/tokens.d.ts +31 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +31 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +71 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @dudousxd/nestjs-authz
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`51d2fbc`](https://github.com/DavideCarvalho/nestjs-authz/commit/51d2fbc6b966c101a6ff0675c42843f040e02703) - Initial release: Laravel-style authorization for NestJS — gates, `@Policy`/`@Can`, the `Gate` service, a default `:id` resource resolver, `before`/`superAdmin` hooks, optional `nestjs-context` current-user with `resolveUser` hydration, and a `PERMISSION_PROVIDER` seam. The `@dudousxd/nestjs-authz-typeorm` adapter adds opt-in RBAC persistence (roles/permissions store, dialect-correct SQL, identifier allowlist, non-destructive schema auto-manage).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local, structural mirror of `@dudousxd/nestjs-context`'s public accessor
|
|
3
|
+
* (`packages/core/src/accessor.ts`).
|
|
4
|
+
*
|
|
5
|
+
* We deliberately do NOT import nestjs-context (it is an OPTIONAL peer). Instead
|
|
6
|
+
* we declare the same shape here and inject it via the shared
|
|
7
|
+
* {@link CONTEXT_ACCESSOR} token with `@Optional()`. Any object that structurally
|
|
8
|
+
* satisfies this interface — including nestjs-context's real accessor — works.
|
|
9
|
+
*
|
|
10
|
+
* Kept byte-aligned with nestjs-context's `ContextAccessor`: `traceId()` /
|
|
11
|
+
* `tenantId()` are REQUIRED (the real accessor always provides them) and `get()`
|
|
12
|
+
* is included, so a future authz use of `tenantId()`/`get()` is type-safe and
|
|
13
|
+
* the structural match stays exact.
|
|
14
|
+
*/
|
|
15
|
+
export interface UserRef {
|
|
16
|
+
type: string;
|
|
17
|
+
id: string | number;
|
|
18
|
+
}
|
|
19
|
+
/** Opaque shape of the context store. authz never reads it; mirrors the upstream surface. */
|
|
20
|
+
export type ContextStore = Record<string, unknown>;
|
|
21
|
+
export interface ContextAccessor {
|
|
22
|
+
/** Trace id for the current request, or `undefined` when unavailable. */
|
|
23
|
+
traceId(): string | undefined;
|
|
24
|
+
/** Current tenant id, or `undefined` when no multi-tenant context is populated. */
|
|
25
|
+
tenantId(): string | undefined;
|
|
26
|
+
/** Reference to the current user, or `undefined` when unauthenticated. */
|
|
27
|
+
userRef(): UserRef | undefined;
|
|
28
|
+
/** The raw context store for the current request, or `undefined`. */
|
|
29
|
+
get(): ContextStore | undefined;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=context-accessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-accessor.d.ts","sourceRoot":"","sources":["../src/context-accessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB;AAED,6FAA6F;AAC7F,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,OAAO,IAAI,MAAM,GAAG,SAAS,CAAC;IAC9B,mFAAmF;IACnF,QAAQ,IAAI,MAAM,GAAG,SAAS,CAAC;IAC/B,0EAA0E;IAC1E,OAAO,IAAI,OAAO,GAAG,SAAS,CAAC;IAC/B,qEAAqE;IACrE,GAAG,IAAI,YAAY,GAAG,SAAS,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-accessor.js","sourceRoot":"","sources":["../src/context-accessor.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { type Type } from '@nestjs/common';
|
|
3
|
+
export interface CanOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Class-level ability (e.g. `create`): the policy method is keyed to the
|
|
6
|
+
* resource class but receives NO instance, so the guard skips resource loading.
|
|
7
|
+
*/
|
|
8
|
+
classLevel?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface CanMetadata {
|
|
11
|
+
ability: string;
|
|
12
|
+
/** Resource class — to load an instance, or (for class-level) to locate the policy. */
|
|
13
|
+
resource?: Type<unknown>;
|
|
14
|
+
/** When true, skip resource loading and dispatch against the resource class. */
|
|
15
|
+
classLevel?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Guard a route with an authorization check.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* `@Can('update', Post)` // resolve Post by :id, run PostPolicy.update(user, post)
|
|
22
|
+
* `@Can('create', Post, { classLevel: true })` // run PostPolicy.create(user) — no instance loaded
|
|
23
|
+
* `@Can('access-admin')` // ad-hoc gate, no resource
|
|
24
|
+
*
|
|
25
|
+
* When a resource class is provided (and not `classLevel`), the guard loads an
|
|
26
|
+
* instance via the registered `ResourceResolver` (default: by route `:id`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function Can(ability: string, resource?: Type<unknown>, options?: CanOptions): MethodDecorator & ClassDecorator;
|
|
29
|
+
//# sourceMappingURL=can.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"can.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/can.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGxD,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,gFAAgF;IAChF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EACxB,OAAO,CAAC,EAAE,UAAU,GACnB,eAAe,GAAG,cAAc,CAKlC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { SetMetadata } from '@nestjs/common';
|
|
3
|
+
import { CAN_METADATA } from '../tokens.js';
|
|
4
|
+
/**
|
|
5
|
+
* Guard a route with an authorization check.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* `@Can('update', Post)` // resolve Post by :id, run PostPolicy.update(user, post)
|
|
9
|
+
* `@Can('create', Post, { classLevel: true })` // run PostPolicy.create(user) — no instance loaded
|
|
10
|
+
* `@Can('access-admin')` // ad-hoc gate, no resource
|
|
11
|
+
*
|
|
12
|
+
* When a resource class is provided (and not `classLevel`), the guard loads an
|
|
13
|
+
* instance via the registered `ResourceResolver` (default: by route `:id`).
|
|
14
|
+
*/
|
|
15
|
+
export function Can(ability, resource, options) {
|
|
16
|
+
const meta = { ability };
|
|
17
|
+
if (resource !== undefined)
|
|
18
|
+
meta.resource = resource;
|
|
19
|
+
if (options?.classLevel)
|
|
20
|
+
meta.classLevel = true;
|
|
21
|
+
return SetMetadata(CAN_METADATA, meta);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=can.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"can.decorator.js","sourceRoot":"","sources":["../../src/decorator/can.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAa,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkB5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,CACjB,OAAe,EACf,QAAwB,EACxB,OAAoB;IAEpB,MAAM,IAAI,GAAgB,EAAE,OAAO,EAAE,CAAC;IACtC,IAAI,QAAQ,KAAK,SAAS;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACrD,IAAI,OAAO,EAAE,UAAU;QAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IAChD,OAAO,WAAW,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { type Type } from '@nestjs/common';
|
|
3
|
+
/**
|
|
4
|
+
* Marks a class as the policy for `resource`. The class methods become abilities
|
|
5
|
+
* dispatched by name (e.g. `update(user, post)` answers `gate.authorize('update', post)`).
|
|
6
|
+
*
|
|
7
|
+
* Applying `@Policy` also makes the class an `@Injectable()` provider so it can
|
|
8
|
+
* be auto-discovered and resolved from the Nest container.
|
|
9
|
+
*/
|
|
10
|
+
export declare function Policy(resource: Type<unknown>): ClassDecorator;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the resource class a policy was declared for, or `undefined`.
|
|
13
|
+
*
|
|
14
|
+
* Uses `getMetadata` (not `getOwnMetadata`) so a subclass of a `@Policy`-decorated
|
|
15
|
+
* class still resolves the inherited resource via the prototype chain.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getPolicyResource(policy: Type<unknown> | object): Type<unknown> | undefined;
|
|
18
|
+
//# sourceMappingURL=policy.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/policy.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAIvD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,cAAc,CAM9D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,SAAS,CAG3F"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Injectable } from '@nestjs/common';
|
|
3
|
+
import { POLICY_RESOURCE_METADATA } from '../tokens.js';
|
|
4
|
+
/**
|
|
5
|
+
* Marks a class as the policy for `resource`. The class methods become abilities
|
|
6
|
+
* dispatched by name (e.g. `update(user, post)` answers `gate.authorize('update', post)`).
|
|
7
|
+
*
|
|
8
|
+
* Applying `@Policy` also makes the class an `@Injectable()` provider so it can
|
|
9
|
+
* be auto-discovered and resolved from the Nest container.
|
|
10
|
+
*/
|
|
11
|
+
export function Policy(resource) {
|
|
12
|
+
return (target) => {
|
|
13
|
+
Reflect.defineMetadata(POLICY_RESOURCE_METADATA, resource, target);
|
|
14
|
+
// Make the policy a provider without forcing the app to add @Injectable().
|
|
15
|
+
Injectable()(target);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the resource class a policy was declared for, or `undefined`.
|
|
20
|
+
*
|
|
21
|
+
* Uses `getMetadata` (not `getOwnMetadata`) so a subclass of a `@Policy`-decorated
|
|
22
|
+
* class still resolves the inherited resource via the prototype chain.
|
|
23
|
+
*/
|
|
24
|
+
export function getPolicyResource(policy) {
|
|
25
|
+
const ctor = typeof policy === 'function' ? policy : policy.constructor;
|
|
26
|
+
return Reflect.getMetadata(POLICY_RESOURCE_METADATA, ctor);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=policy.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.decorator.js","sourceRoot":"","sources":["../../src/decorator/policy.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAa,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAGxD;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,QAAuB;IAC5C,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,OAAO,CAAC,cAAc,CAAC,wBAAwB,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnE,2EAA2E;QAC3E,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA8B;IAC9D,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,MAAyB,CAAC,WAAW,CAAC;IAC5F,OAAO,OAAO,CAAC,WAAW,CAAC,wBAAwB,EAAE,IAAI,CAA8B,CAAC;AAC1F,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
export declare abstract class AuthzException extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class PolicyNotDecoratedException extends AuthzException {
|
|
6
|
+
readonly policyName: string;
|
|
7
|
+
constructor(policyName: string);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Thrown when an ability cannot be matched to any policy method or ad-hoc gate.
|
|
11
|
+
*
|
|
12
|
+
* This is a *programming* error (typically a typo'd ability name), not an
|
|
13
|
+
* authorization denial — so it maps to a `400 Bad Request` HttpException rather
|
|
14
|
+
* than surfacing as a 500. (A genuine denial throws `ForbiddenException` → 403.)
|
|
15
|
+
*/
|
|
16
|
+
export declare class AbilityNotResolvedException extends BadRequestException {
|
|
17
|
+
readonly ability: string;
|
|
18
|
+
readonly name = "AbilityNotResolvedException";
|
|
19
|
+
constructor(ability: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when a class-level ability (`gate.allows('create')`, no resource) is
|
|
23
|
+
* ambiguous: more than one registered policy defines a method with that name, so
|
|
24
|
+
* picking one by Map-insertion order would be a silent, arbitrary choice. Pass an
|
|
25
|
+
* explicit resource class (`gate.allows('create', Post)`) to disambiguate.
|
|
26
|
+
*/
|
|
27
|
+
export declare class AmbiguousAbilityException extends BadRequestException {
|
|
28
|
+
readonly ability: string;
|
|
29
|
+
readonly policyNames: string[];
|
|
30
|
+
readonly name = "AmbiguousAbilityException";
|
|
31
|
+
constructor(ability: string, policyNames: string[]);
|
|
32
|
+
}
|
|
33
|
+
export declare class ResourceResolverMissingException extends AuthzException {
|
|
34
|
+
readonly ability: string;
|
|
35
|
+
constructor(ability: string);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=exceptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exceptions.d.ts","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,8BAAsB,cAAe,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,2BAA4B,SAAQ,cAAc;aACjC,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAG/C;AAED;;;;;;GAMG;AACH,qBAAa,2BAA4B,SAAQ,mBAAmB;aAEtC,OAAO,EAAE,MAAM;IAD3C,SAAkB,IAAI,iCAAiC;gBAC3B,OAAO,EAAE,MAAM;CAK5C;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,mBAAmB;aAG9C,OAAO,EAAE,MAAM;aACf,WAAW,EAAE,MAAM,EAAE;IAHvC,SAAkB,IAAI,+BAA+B;gBAEnC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EAAE;CAQxC;AAED,qBAAa,gCAAiC,SAAQ,cAAc;aACtC,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;CAK5C"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
export class AuthzException extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class PolicyNotDecoratedException extends AuthzException {
|
|
9
|
+
policyName;
|
|
10
|
+
constructor(policyName) {
|
|
11
|
+
super(`${policyName} is missing @Policy(Resource). Decorate it with the resource class.`);
|
|
12
|
+
this.policyName = policyName;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when an ability cannot be matched to any policy method or ad-hoc gate.
|
|
17
|
+
*
|
|
18
|
+
* This is a *programming* error (typically a typo'd ability name), not an
|
|
19
|
+
* authorization denial — so it maps to a `400 Bad Request` HttpException rather
|
|
20
|
+
* than surfacing as a 500. (A genuine denial throws `ForbiddenException` → 403.)
|
|
21
|
+
*/
|
|
22
|
+
export class AbilityNotResolvedException extends BadRequestException {
|
|
23
|
+
ability;
|
|
24
|
+
name = 'AbilityNotResolvedException';
|
|
25
|
+
constructor(ability) {
|
|
26
|
+
super(`Ability "${ability}" could not be resolved. No matching policy method or gate.define('${ability}', ...).`);
|
|
27
|
+
this.ability = ability;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Thrown when a class-level ability (`gate.allows('create')`, no resource) is
|
|
32
|
+
* ambiguous: more than one registered policy defines a method with that name, so
|
|
33
|
+
* picking one by Map-insertion order would be a silent, arbitrary choice. Pass an
|
|
34
|
+
* explicit resource class (`gate.allows('create', Post)`) to disambiguate.
|
|
35
|
+
*/
|
|
36
|
+
export class AmbiguousAbilityException extends BadRequestException {
|
|
37
|
+
ability;
|
|
38
|
+
policyNames;
|
|
39
|
+
name = 'AmbiguousAbilityException';
|
|
40
|
+
constructor(ability, policyNames) {
|
|
41
|
+
super(`Ability "${ability}" is ambiguous: it is defined by multiple policies (${policyNames.join(', ')}). Pass the resource class explicitly, e.g. gate.allows('${ability}', Resource).`);
|
|
42
|
+
this.ability = ability;
|
|
43
|
+
this.policyNames = policyNames;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class ResourceResolverMissingException extends AuthzException {
|
|
47
|
+
ability;
|
|
48
|
+
constructor(ability) {
|
|
49
|
+
super(`@Can('${ability}', Resource) needs an instance but no ResourceResolver is registered. Register one via AuthzModule.forRoot or pass the resource programmatically.`);
|
|
50
|
+
this.ability = ability;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=exceptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,OAAgB,cAAe,SAAQ,KAAK;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,2BAA4B,SAAQ,cAAc;IACjC;IAA5B,YAA4B,UAAkB;QAC5C,KAAK,CAAC,GAAG,UAAU,qEAAqE,CAAC,CAAC;QADhE,eAAU,GAAV,UAAU,CAAQ;IAE9C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,2BAA4B,SAAQ,mBAAmB;IAEtC;IADV,IAAI,GAAG,6BAA6B,CAAC;IACvD,YAA4B,OAAe;QACzC,KAAK,CACH,YAAY,OAAO,sEAAsE,OAAO,UAAU,CAC3G,CAAC;QAHwB,YAAO,GAAP,OAAO,CAAQ;IAI3C,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,yBAA0B,SAAQ,mBAAmB;IAG9C;IACA;IAHA,IAAI,GAAG,2BAA2B,CAAC;IACrD,YACkB,OAAe,EACf,WAAqB;QAErC,KAAK,CACH,YAAY,OAAO,uDAAuD,WAAW,CAAC,IAAI,CACxF,IAAI,CACL,4DAA4D,OAAO,eAAe,CACpF,CAAC;QAPc,YAAO,GAAP,OAAO,CAAQ;QACf,gBAAW,GAAX,WAAW,CAAU;IAOvC,CAAC;CACF;AAED,MAAM,OAAO,gCAAiC,SAAQ,cAAc;IACtC;IAA5B,YAA4B,OAAe;QACzC,KAAK,CACH,SAAS,OAAO,mJAAmJ,CACpK,CAAC;QAHwB,YAAO,GAAP,OAAO,CAAQ;IAI3C,CAAC;CACF"}
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ModuleRef } from '@nestjs/core';
|
|
2
|
+
import type { ContextAccessor } from './context-accessor.js';
|
|
3
|
+
import type { PermissionProvider } from './permission-provider.js';
|
|
4
|
+
import { PolicyRegistry } from './policy-registry.js';
|
|
5
|
+
import type { AuthzModuleOptions, GateFn, Resource, User } from './types.js';
|
|
6
|
+
declare const NO_USER: unique symbol;
|
|
7
|
+
type MaybeUser = User | typeof NO_USER;
|
|
8
|
+
/**
|
|
9
|
+
* Laravel-style authorization Gate.
|
|
10
|
+
*
|
|
11
|
+
* Resolves an ability against either a registered `@Policy` method (by ability
|
|
12
|
+
* name, matched against the resource's policy) or an ad-hoc gate defined via
|
|
13
|
+
* {@link define}. Applies the global `superAdmin` before-hook and the per-policy
|
|
14
|
+
* `before` hook with short-circuit semantics.
|
|
15
|
+
*
|
|
16
|
+
* The current user comes from the optional {@link ContextAccessor} (nestjs-context)
|
|
17
|
+
* when present, or is supplied explicitly via {@link forUser}.
|
|
18
|
+
*/
|
|
19
|
+
export declare class Gate {
|
|
20
|
+
private readonly policies;
|
|
21
|
+
private readonly context?;
|
|
22
|
+
private readonly moduleRef?;
|
|
23
|
+
private readonly permissionProvider?;
|
|
24
|
+
private readonly gates;
|
|
25
|
+
private readonly superAdmin;
|
|
26
|
+
private readonly resolveUser;
|
|
27
|
+
constructor(policies: PolicyRegistry, options: AuthzModuleOptions | undefined, context?: ContextAccessor | undefined, moduleRef?: ModuleRef | undefined, permissionProvider?: PermissionProvider | undefined);
|
|
28
|
+
/**
|
|
29
|
+
* Locate the context accessor. Prefers the value injected into this module;
|
|
30
|
+
* falls back to a non-strict {@link ModuleRef} lookup so an accessor provided
|
|
31
|
+
* by ANY module (e.g. a global ContextModule, or the app root) is still found.
|
|
32
|
+
*/
|
|
33
|
+
private resolveContext;
|
|
34
|
+
/**
|
|
35
|
+
* Locate the optional {@link PermissionProvider} (the RBAC seam). Prefers the value
|
|
36
|
+
* injected into this module; falls back to a non-strict {@link ModuleRef} lookup so a
|
|
37
|
+
* provider registered by ANY module (e.g. the RBAC adapter's global module) is found.
|
|
38
|
+
*/
|
|
39
|
+
private resolvePermissionProvider;
|
|
40
|
+
/** Register an ad-hoc, model-less gate resolved by `ability` name. */
|
|
41
|
+
define(ability: string, fn: GateFn): this;
|
|
42
|
+
/** True when an ad-hoc gate is registered for `ability`. */
|
|
43
|
+
hasGate(ability: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Bind an explicit user, bypassing the context accessor. Use when no
|
|
46
|
+
* nestjs-context is wired, or to check a user other than the current one.
|
|
47
|
+
*
|
|
48
|
+
* A nullish user (`forUser(undefined)` / `forUser(null)`) is an explicit
|
|
49
|
+
* anonymous request: it maps to the same deny path as an unauthenticated
|
|
50
|
+
* context (`NO_USER`), so policies/gates are never invoked with `undefined` and
|
|
51
|
+
* never throw. The `resolveUser` hook is NOT applied to `forUser` — the value
|
|
52
|
+
* you pass is used verbatim.
|
|
53
|
+
*/
|
|
54
|
+
forUser(user: User): BoundGate;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the current user from the context accessor, or `NO_USER`.
|
|
57
|
+
* When a `resolveUser` hook is configured, hydrate the full entity from the
|
|
58
|
+
* context's {@link UserRef}; a hook returning nullish maps to `NO_USER`.
|
|
59
|
+
*/
|
|
60
|
+
private currentUser;
|
|
61
|
+
allows(ability: string, resource?: Resource): Promise<boolean>;
|
|
62
|
+
denies(ability: string, resource?: Resource): Promise<boolean>;
|
|
63
|
+
authorize(ability: string, resource?: Resource): Promise<void>;
|
|
64
|
+
/** @internal */
|
|
65
|
+
allowsForUser(user: MaybeUser, ability: string, resource?: Resource): Promise<boolean>;
|
|
66
|
+
private check;
|
|
67
|
+
private resolvePolicy;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A {@link Gate} bound to an explicit user. Returned by {@link Gate.forUser}.
|
|
71
|
+
*/
|
|
72
|
+
export declare class BoundGate {
|
|
73
|
+
private readonly gate;
|
|
74
|
+
private readonly user;
|
|
75
|
+
constructor(gate: Gate, user: MaybeUser);
|
|
76
|
+
allows(ability: string, resource?: Resource): Promise<boolean>;
|
|
77
|
+
denies(ability: string, resource?: Resource): Promise<boolean>;
|
|
78
|
+
authorize(ability: string, resource?: Resource): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
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 { ForbiddenException, Inject, Injectable, Optional } from '@nestjs/common';
|
|
14
|
+
import { ModuleRef } from '@nestjs/core';
|
|
15
|
+
import { AbilityNotResolvedException, AmbiguousAbilityException } from './errors/exceptions.js';
|
|
16
|
+
import { PolicyRegistry } from './policy-registry.js';
|
|
17
|
+
import { AUTHZ_MODULE_OPTIONS, CONTEXT_ACCESSOR, PERMISSION_PROVIDER } from './tokens.js';
|
|
18
|
+
// A sentinel marking "no user resolved" distinct from a legitimately-`undefined`
|
|
19
|
+
// user. `forUser(undefined)` explicitly authorizes an anonymous user.
|
|
20
|
+
const NO_USER = Symbol('authz:no-user');
|
|
21
|
+
/**
|
|
22
|
+
* Laravel-style authorization Gate.
|
|
23
|
+
*
|
|
24
|
+
* Resolves an ability against either a registered `@Policy` method (by ability
|
|
25
|
+
* name, matched against the resource's policy) or an ad-hoc gate defined via
|
|
26
|
+
* {@link define}. Applies the global `superAdmin` before-hook and the per-policy
|
|
27
|
+
* `before` hook with short-circuit semantics.
|
|
28
|
+
*
|
|
29
|
+
* The current user comes from the optional {@link ContextAccessor} (nestjs-context)
|
|
30
|
+
* when present, or is supplied explicitly via {@link forUser}.
|
|
31
|
+
*/
|
|
32
|
+
let Gate = class Gate {
|
|
33
|
+
policies;
|
|
34
|
+
context;
|
|
35
|
+
moduleRef;
|
|
36
|
+
permissionProvider;
|
|
37
|
+
gates = new Map();
|
|
38
|
+
superAdmin;
|
|
39
|
+
resolveUser;
|
|
40
|
+
constructor(policies, options, context, moduleRef, permissionProvider) {
|
|
41
|
+
this.policies = policies;
|
|
42
|
+
this.context = context;
|
|
43
|
+
this.moduleRef = moduleRef;
|
|
44
|
+
this.permissionProvider = permissionProvider;
|
|
45
|
+
this.superAdmin = options?.superAdmin;
|
|
46
|
+
this.resolveUser = options?.resolveUser;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Locate the context accessor. Prefers the value injected into this module;
|
|
50
|
+
* falls back to a non-strict {@link ModuleRef} lookup so an accessor provided
|
|
51
|
+
* by ANY module (e.g. a global ContextModule, or the app root) is still found.
|
|
52
|
+
*/
|
|
53
|
+
resolveContext() {
|
|
54
|
+
if (this.context)
|
|
55
|
+
return this.context;
|
|
56
|
+
if (!this.moduleRef)
|
|
57
|
+
return undefined;
|
|
58
|
+
try {
|
|
59
|
+
return this.moduleRef.get(CONTEXT_ACCESSOR, { strict: false });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Locate the optional {@link PermissionProvider} (the RBAC seam). Prefers the value
|
|
67
|
+
* injected into this module; falls back to a non-strict {@link ModuleRef} lookup so a
|
|
68
|
+
* provider registered by ANY module (e.g. the RBAC adapter's global module) is found.
|
|
69
|
+
*/
|
|
70
|
+
resolvePermissionProvider() {
|
|
71
|
+
if (this.permissionProvider)
|
|
72
|
+
return this.permissionProvider;
|
|
73
|
+
if (!this.moduleRef)
|
|
74
|
+
return undefined;
|
|
75
|
+
try {
|
|
76
|
+
return this.moduleRef.get(PERMISSION_PROVIDER, { strict: false });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Register an ad-hoc, model-less gate resolved by `ability` name. */
|
|
83
|
+
define(ability, fn) {
|
|
84
|
+
this.gates.set(ability, fn);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
/** True when an ad-hoc gate is registered for `ability`. */
|
|
88
|
+
hasGate(ability) {
|
|
89
|
+
return this.gates.has(ability);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Bind an explicit user, bypassing the context accessor. Use when no
|
|
93
|
+
* nestjs-context is wired, or to check a user other than the current one.
|
|
94
|
+
*
|
|
95
|
+
* A nullish user (`forUser(undefined)` / `forUser(null)`) is an explicit
|
|
96
|
+
* anonymous request: it maps to the same deny path as an unauthenticated
|
|
97
|
+
* context (`NO_USER`), so policies/gates are never invoked with `undefined` and
|
|
98
|
+
* never throw. The `resolveUser` hook is NOT applied to `forUser` — the value
|
|
99
|
+
* you pass is used verbatim.
|
|
100
|
+
*/
|
|
101
|
+
forUser(user) {
|
|
102
|
+
return new BoundGate(this, user == null ? NO_USER : user);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the current user from the context accessor, or `NO_USER`.
|
|
106
|
+
* When a `resolveUser` hook is configured, hydrate the full entity from the
|
|
107
|
+
* context's {@link UserRef}; a hook returning nullish maps to `NO_USER`.
|
|
108
|
+
*/
|
|
109
|
+
async currentUser() {
|
|
110
|
+
const context = this.resolveContext();
|
|
111
|
+
if (!context)
|
|
112
|
+
return NO_USER;
|
|
113
|
+
const ref = context.userRef();
|
|
114
|
+
if (ref === undefined)
|
|
115
|
+
return NO_USER;
|
|
116
|
+
if (this.resolveUser) {
|
|
117
|
+
const hydrated = await this.resolveUser(ref);
|
|
118
|
+
return hydrated == null ? NO_USER : hydrated;
|
|
119
|
+
}
|
|
120
|
+
return ref;
|
|
121
|
+
}
|
|
122
|
+
// --- public API (operates on the current/context user) ---
|
|
123
|
+
async allows(ability, resource) {
|
|
124
|
+
return this.check(await this.currentUser(), ability, resource);
|
|
125
|
+
}
|
|
126
|
+
async denies(ability, resource) {
|
|
127
|
+
return !(await this.allows(ability, resource));
|
|
128
|
+
}
|
|
129
|
+
async authorize(ability, resource) {
|
|
130
|
+
if (!(await this.allows(ability, resource))) {
|
|
131
|
+
throw new ForbiddenException(`Unauthorized: ${ability}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// --- internal: used by BoundGate too ---
|
|
135
|
+
/** @internal */
|
|
136
|
+
allowsForUser(user, ability, resource) {
|
|
137
|
+
return this.check(user, ability, resource);
|
|
138
|
+
}
|
|
139
|
+
async check(maybeUser, ability, resource) {
|
|
140
|
+
const user = maybeUser === NO_USER ? undefined : maybeUser;
|
|
141
|
+
// Global super-admin hook first.
|
|
142
|
+
const sa = await this.superAdmin?.(user, ability);
|
|
143
|
+
if (sa === true)
|
|
144
|
+
return true;
|
|
145
|
+
if (sa === false)
|
|
146
|
+
return false;
|
|
147
|
+
// RBAC seam (Laravel/spatie `Gate::before` grant): if a PermissionProvider is
|
|
148
|
+
// registered and the (authenticated) user holds the named permission, grant it.
|
|
149
|
+
// Grant-only — a `false`/`undefined` result falls through to normal resolution,
|
|
150
|
+
// so this never *denies* an ability a policy/gate would otherwise allow.
|
|
151
|
+
if (maybeUser !== NO_USER) {
|
|
152
|
+
const provider = this.resolvePermissionProvider();
|
|
153
|
+
if (provider) {
|
|
154
|
+
const granted = await provider.hasPermission(user, ability, resource);
|
|
155
|
+
if (granted === true)
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const policy = this.resolvePolicy(ability, resource);
|
|
160
|
+
if (policy) {
|
|
161
|
+
const method = policy[ability];
|
|
162
|
+
// The `before` hook may only answer abilities the policy actually defines.
|
|
163
|
+
// Gate it on method existence FIRST so a policy with a `before` but no
|
|
164
|
+
// matching method falls through to AbilityNotResolved instead of letting
|
|
165
|
+
// `before` grant/deny an ability the policy never declared.
|
|
166
|
+
if (typeof method === 'function') {
|
|
167
|
+
const before = policy.before;
|
|
168
|
+
if (typeof before === 'function') {
|
|
169
|
+
const result = await before.call(policy, user, ability);
|
|
170
|
+
if (result === true)
|
|
171
|
+
return true;
|
|
172
|
+
if (result === false)
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
// Anonymous users are denied unless a hook granted access above.
|
|
176
|
+
if (maybeUser === NO_USER)
|
|
177
|
+
return false;
|
|
178
|
+
return Boolean(await method.call(policy, user, resource));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Fall back to an ad-hoc gate.
|
|
182
|
+
const gate = this.gates.get(ability);
|
|
183
|
+
if (gate) {
|
|
184
|
+
if (maybeUser === NO_USER)
|
|
185
|
+
return false;
|
|
186
|
+
return Boolean(await gate(user, resource));
|
|
187
|
+
}
|
|
188
|
+
throw new AbilityNotResolvedException(ability);
|
|
189
|
+
}
|
|
190
|
+
resolvePolicy(ability, resource) {
|
|
191
|
+
if (resource === undefined) {
|
|
192
|
+
// Class-level ability with no resource: scan registered policies for the
|
|
193
|
+
// method. If MORE THAN ONE policy defines it, picking by Map-insertion
|
|
194
|
+
// order would be a silent, arbitrary (and likely wrong) choice — throw so
|
|
195
|
+
// the caller disambiguates by passing the resource class explicitly.
|
|
196
|
+
const matches = this.policies
|
|
197
|
+
.all()
|
|
198
|
+
.filter((policy) => typeof policy[ability] === 'function');
|
|
199
|
+
if (matches.length === 0)
|
|
200
|
+
return undefined;
|
|
201
|
+
if (matches.length > 1) {
|
|
202
|
+
throw new AmbiguousAbilityException(ability, matches.map((p) => p.constructor?.name ?? 'Policy'));
|
|
203
|
+
}
|
|
204
|
+
return matches[0];
|
|
205
|
+
}
|
|
206
|
+
// Resource may be an instance or a class (for class-level abilities passed as Type).
|
|
207
|
+
if (typeof resource === 'function') {
|
|
208
|
+
return this.policies.forResource(resource);
|
|
209
|
+
}
|
|
210
|
+
return this.policies.forInstance(resource);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
Gate = __decorate([
|
|
214
|
+
Injectable(),
|
|
215
|
+
__param(1, Optional()),
|
|
216
|
+
__param(1, Inject(AUTHZ_MODULE_OPTIONS)),
|
|
217
|
+
__param(2, Optional()),
|
|
218
|
+
__param(2, Inject(CONTEXT_ACCESSOR)),
|
|
219
|
+
__param(3, Optional()),
|
|
220
|
+
__param(4, Optional()),
|
|
221
|
+
__param(4, Inject(PERMISSION_PROVIDER)),
|
|
222
|
+
__metadata("design:paramtypes", [PolicyRegistry, Object, Object, ModuleRef, Object])
|
|
223
|
+
], Gate);
|
|
224
|
+
export { Gate };
|
|
225
|
+
/**
|
|
226
|
+
* A {@link Gate} bound to an explicit user. Returned by {@link Gate.forUser}.
|
|
227
|
+
*/
|
|
228
|
+
export class BoundGate {
|
|
229
|
+
gate;
|
|
230
|
+
user;
|
|
231
|
+
constructor(gate, user) {
|
|
232
|
+
this.gate = gate;
|
|
233
|
+
this.user = user;
|
|
234
|
+
}
|
|
235
|
+
allows(ability, resource) {
|
|
236
|
+
return this.gate.allowsForUser(this.user, ability, resource);
|
|
237
|
+
}
|
|
238
|
+
async denies(ability, resource) {
|
|
239
|
+
return !(await this.allows(ability, resource));
|
|
240
|
+
}
|
|
241
|
+
async authorize(ability, resource) {
|
|
242
|
+
if (!(await this.allows(ability, resource))) {
|
|
243
|
+
throw new ForbiddenException(`Unauthorized: ${ability}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=gate.js.map
|
package/dist/gate.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
|