@dudousxd/nestjs-authz 0.5.1 → 0.6.1
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 +64 -0
- package/dist/can-endpoint.controller.d.ts +16 -0
- package/dist/can-endpoint.controller.d.ts.map +1 -1
- package/dist/can-endpoint.controller.js +54 -0
- package/dist/can-endpoint.controller.js.map +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/gate.d.ts +120 -2
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +305 -35
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/permission-cache.d.ts +33 -0
- package/dist/permission-cache.d.ts.map +1 -0
- package/dist/permission-cache.js +48 -0
- package/dist/permission-cache.js.map +1 -0
- package/dist/permission-matcher.d.ts +22 -0
- package/dist/permission-matcher.d.ts.map +1 -0
- package/dist/permission-matcher.js +55 -0
- package/dist/permission-matcher.js.map +1 -0
- package/dist/permission-provider.d.ts +18 -0
- package/dist/permission-provider.d.ts.map +1 -1
- package/dist/scope.d.ts +99 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +60 -0
- package/dist/scope.js.map +1 -0
- package/dist/store-kit.d.ts +24 -0
- package/dist/store-kit.d.ts.map +1 -0
- package/dist/store-kit.js +15 -0
- package/dist/store-kit.js.map +1 -0
- package/dist/types.d.ts +53 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { permissionSatisfied } from './permission-matcher.js';
|
|
2
|
+
/**
|
|
3
|
+
* A memo over a {@link PermissionProvider}'s `getPermissions(user)` so a user's
|
|
4
|
+
* granted-permission set is fetched AT MOST ONCE per logical scope (a single
|
|
5
|
+
* `allowsMany` batch, or — when wired to nestjs-context — a single HTTP request).
|
|
6
|
+
*
|
|
7
|
+
* Without it, every `gate.allows(...)` re-fetches the set (an N+1 within a list
|
|
8
|
+
* page, since each card runs its own check). The cache keys by the resolved
|
|
9
|
+
* `user` identity so two checks for the SAME user share one fetch while a
|
|
10
|
+
* different user still triggers its own.
|
|
11
|
+
*
|
|
12
|
+
* Only the `getPermissions` (full-set, wildcard-matched) path is memoized — the
|
|
13
|
+
* exact-match `hasPermission` fast path is left to the provider, which is free to
|
|
14
|
+
* memoize internally. A provider WITHOUT `getPermissions` makes this a no-op.
|
|
15
|
+
*/
|
|
16
|
+
export class PermissionCache {
|
|
17
|
+
// Keyed by the user object identity. `Map` (not WeakMap) because a batch/request
|
|
18
|
+
// cache is short-lived and we also accept primitive ids/refs as keys.
|
|
19
|
+
byUser = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the user's granted-permission set via the provider, memoized for the
|
|
22
|
+
* cache's lifetime. Returns `undefined` when the provider can't list grants
|
|
23
|
+
* (no `getPermissions`, or it yielded nullish) so the caller falls back to the
|
|
24
|
+
* exact-match `hasPermission` path.
|
|
25
|
+
*/
|
|
26
|
+
async getPermissions(provider, user) {
|
|
27
|
+
if (typeof provider.getPermissions !== 'function')
|
|
28
|
+
return undefined;
|
|
29
|
+
const cached = this.byUser.get(user);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
32
|
+
const pending = Promise.resolve(provider.getPermissions(user)).then((granted) => granted == null ? undefined : new Set(granted));
|
|
33
|
+
this.byUser.set(user, pending);
|
|
34
|
+
return pending;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when the user's (memoized) granted set satisfies `ability` under the
|
|
38
|
+
* core's wildcard matcher. `undefined` when no set is available (caller should
|
|
39
|
+
* defer to `hasPermission`).
|
|
40
|
+
*/
|
|
41
|
+
async satisfies(provider, user, ability) {
|
|
42
|
+
const set = await this.getPermissions(provider, user);
|
|
43
|
+
if (set === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
return permissionSatisfied(set, ability);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=permission-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-cache.js","sourceRoot":"","sources":["../src/permission-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAI9D;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,eAAe;IAC1B,iFAAiF;IACjF,sEAAsE;IACrD,MAAM,GAAG,IAAI,GAAG,EAAqD,CAAC;IAEvF;;;;;OAKG;IACH,KAAK,CAAC,cAAc,CAClB,QAA4B,EAC5B,IAAU;QAEV,IAAI,OAAO,QAAQ,CAAC,cAAc,KAAK,UAAU;YAAE,OAAO,SAAS,CAAC;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAC9E,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAC/C,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS,CACb,QAA4B,EAC5B,IAAU,EACV,OAAe;QAEf,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACtD,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACxC,OAAO,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laravel/spatie-style wildcard permission matching.
|
|
3
|
+
*
|
|
4
|
+
* A GRANTED permission (the left-hand side) may contain `*` wildcard segments;
|
|
5
|
+
* the REQUIRED ability (the check) is always a concrete, literal string. Matching
|
|
6
|
+
* is segment-based on the `.` separator:
|
|
7
|
+
*
|
|
8
|
+
* - exact string equality matches (`posts.update` ⇒ `posts.update`).
|
|
9
|
+
* - a trailing `*` segment matches ONE-OR-MORE remaining segments
|
|
10
|
+
* (`posts.*` ⇒ `posts.update`, `posts.update.draft`) — but not zero
|
|
11
|
+
* (`posts.*` does NOT match the bare `posts`).
|
|
12
|
+
* - a bare `*` grant matches anything.
|
|
13
|
+
* - `*` only matches at a segment boundary, so `posts.*` does NOT match
|
|
14
|
+
* `comments.update`.
|
|
15
|
+
*
|
|
16
|
+
* Only the GRANTED side is expanded — a `*` in the required ability is treated
|
|
17
|
+
* literally, since a check is never a pattern.
|
|
18
|
+
*/
|
|
19
|
+
export declare function permissionMatches(granted: string, required: string): boolean;
|
|
20
|
+
/** True when ANY permission in the granted set satisfies the required ability. */
|
|
21
|
+
export declare function permissionSatisfied(granted: Iterable<string>, required: string): boolean;
|
|
22
|
+
//# sourceMappingURL=permission-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-matcher.d.ts","sourceRoot":"","sources":["../src/permission-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CA0B5E;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKxF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laravel/spatie-style wildcard permission matching.
|
|
3
|
+
*
|
|
4
|
+
* A GRANTED permission (the left-hand side) may contain `*` wildcard segments;
|
|
5
|
+
* the REQUIRED ability (the check) is always a concrete, literal string. Matching
|
|
6
|
+
* is segment-based on the `.` separator:
|
|
7
|
+
*
|
|
8
|
+
* - exact string equality matches (`posts.update` ⇒ `posts.update`).
|
|
9
|
+
* - a trailing `*` segment matches ONE-OR-MORE remaining segments
|
|
10
|
+
* (`posts.*` ⇒ `posts.update`, `posts.update.draft`) — but not zero
|
|
11
|
+
* (`posts.*` does NOT match the bare `posts`).
|
|
12
|
+
* - a bare `*` grant matches anything.
|
|
13
|
+
* - `*` only matches at a segment boundary, so `posts.*` does NOT match
|
|
14
|
+
* `comments.update`.
|
|
15
|
+
*
|
|
16
|
+
* Only the GRANTED side is expanded — a `*` in the required ability is treated
|
|
17
|
+
* literally, since a check is never a pattern.
|
|
18
|
+
*/
|
|
19
|
+
export function permissionMatches(granted, required) {
|
|
20
|
+
if (granted === required)
|
|
21
|
+
return true;
|
|
22
|
+
if (granted === '*')
|
|
23
|
+
return true;
|
|
24
|
+
const grantedParts = granted.split('.');
|
|
25
|
+
const requiredParts = required.split('.');
|
|
26
|
+
for (let i = 0; i < grantedParts.length; i++) {
|
|
27
|
+
const g = grantedParts[i];
|
|
28
|
+
if (g === '*') {
|
|
29
|
+
// A trailing `*` matches one-or-more remaining required segments.
|
|
30
|
+
// (Only the final segment may be a wildcard; an interior `*` still works
|
|
31
|
+
// positionally but a trailing `*` is the idiomatic form.)
|
|
32
|
+
if (i === grantedParts.length - 1) {
|
|
33
|
+
return requiredParts.length > i;
|
|
34
|
+
}
|
|
35
|
+
// interior wildcard: this single segment is consumed; continue matching.
|
|
36
|
+
if (requiredParts[i] === undefined)
|
|
37
|
+
return false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (g !== requiredParts[i])
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
// All granted segments consumed and matched exactly → only a true match when
|
|
44
|
+
// the required ability has no extra trailing segments.
|
|
45
|
+
return grantedParts.length === requiredParts.length;
|
|
46
|
+
}
|
|
47
|
+
/** True when ANY permission in the granted set satisfies the required ability. */
|
|
48
|
+
export function permissionSatisfied(granted, required) {
|
|
49
|
+
for (const g of granted) {
|
|
50
|
+
if (permissionMatches(g, required))
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=permission-matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-matcher.js","sourceRoot":"","sources":["../src/permission-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,QAAgB;IACjE,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,kEAAkE;YAClE,yEAAyE;YACzE,0DAA0D;YAC1D,IAAI,CAAC,KAAK,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,OAAO,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;YAClC,CAAC;YACD,yEAAyE;YACzE,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;YACjD,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IAED,6EAA6E;IAC7E,uDAAuD;IACvD,OAAO,YAAY,CAAC,MAAM,KAAK,aAAa,CAAC,MAAM,CAAC;AACtD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,mBAAmB,CAAC,OAAyB,EAAE,QAAgB;IAC7E,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -18,8 +18,26 @@ import type { Resource, User } from './types.js';
|
|
|
18
18
|
* when anonymous). `permission` is the ability name passed to `gate.allows(...)`.
|
|
19
19
|
* `resource` is the dispatch target, when one was given; providers MAY ignore it —
|
|
20
20
|
* model-less, named-ability grants (e.g. the typeorm RBAC adapter) do.
|
|
21
|
+
*
|
|
22
|
+
* ## Wildcard / hierarchical permissions
|
|
23
|
+
*
|
|
24
|
+
* To support Laravel/spatie-style wildcard grants (a granted `posts.*` satisfying a
|
|
25
|
+
* check for `posts.update`, or `*` satisfying anything), a provider may ALSO expose
|
|
26
|
+
* {@link PermissionProvider.getPermissions} — the user's full granted permission set.
|
|
27
|
+
* When present, the core applies segment-based wildcard matching against that set
|
|
28
|
+
* (see `permission-matcher.ts`), so wildcard semantics work regardless of adapter
|
|
29
|
+
* and without each adapter re-implementing the matching. `hasPermission` remains the
|
|
30
|
+
* exact-match fast path (and the sole contract for providers that don't list grants).
|
|
21
31
|
*/
|
|
22
32
|
export interface PermissionProvider {
|
|
23
33
|
hasPermission(user: User, permission: string, resource?: Resource): boolean | undefined | Promise<boolean | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Optional: the full set of permission names granted to `user` (each possibly a
|
|
36
|
+
* wildcard pattern such as `posts.*` or `*`). When implemented, the core matches
|
|
37
|
+
* the checked ability against this set using segment-based wildcard semantics, so
|
|
38
|
+
* a granted `posts.*` satisfies `posts.update`. Return `undefined` to defer to
|
|
39
|
+
* `hasPermission` (e.g. for an anonymous/unmappable user).
|
|
40
|
+
*/
|
|
41
|
+
getPermissions?(user: User): Iterable<string> | undefined | Promise<Iterable<string> | undefined>;
|
|
24
42
|
}
|
|
25
43
|
//# sourceMappingURL=permission-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-provider.d.ts","sourceRoot":"","sources":["../src/permission-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEjD
|
|
1
|
+
{"version":3,"file":"permission-provider.d.ts","sourceRoot":"","sources":["../src/permission-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,CACX,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACtD;;;;;;OAMG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;CACnG"}
|
package/dist/scope.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Resource, User } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Conservative identifier allowlist for any name interpolated into a query by an adapter —
|
|
4
|
+
* table/schema names and policy-supplied scope `field`s. BYO/policy-supplied names are validated
|
|
5
|
+
* against this before interpolation; values are always bound, never concatenated.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SAFE_IDENTIFIER: RegExp;
|
|
8
|
+
/**
|
|
9
|
+
* Validate an identifier (table/schema/column name) against {@link SAFE_IDENTIFIER}, throwing on
|
|
10
|
+
* anything containing quotes, dots, whitespace, or other unsafe characters. Single source of the
|
|
11
|
+
* injection guard for every adapter.
|
|
12
|
+
*
|
|
13
|
+
* @param value the identifier to check
|
|
14
|
+
* @param what a label for the error message (e.g. `table name "roles"`)
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertSafeIdentifier(value: string, what: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* ORM-neutral query-scope constraint (the `accessibleBy` / Pundit `policy_scope` /
|
|
19
|
+
* Cerbos `PlanResources` concept). Where a policy METHOD decides yes/no for a SINGLE
|
|
20
|
+
* resource, a policy SCOPE produces a constraint that filters a COLLECTION to the rows
|
|
21
|
+
* the user may access — applied at the DB layer instead of over-fetch-then-filter.
|
|
22
|
+
*
|
|
23
|
+
* The representation is a small, pure-data condition AST (no callbacks, fully
|
|
24
|
+
* serializable) so every ORM adapter can walk it and emit a parameterized,
|
|
25
|
+
* identifier-safe `WHERE`. Compare:
|
|
26
|
+
*
|
|
27
|
+
* - **CASL `accessibleBy`** — turns ability rules into an ORM `where` object.
|
|
28
|
+
* - **Pundit `policy_scope`** — a Scope class returning a narrowed relation.
|
|
29
|
+
* - **Cerbos Query Plan (`PlanResources`)** — returns `ALWAYS_ALLOWED` /
|
|
30
|
+
* `ALWAYS_DENIED` / a condition AST of operands. This AST mirrors that shape.
|
|
31
|
+
*
|
|
32
|
+
* Two terminal verdicts bracket the AST and keep it aligned with the Gate's
|
|
33
|
+
* deny-by-default + super-admin/before-grant semantics:
|
|
34
|
+
*
|
|
35
|
+
* - {@link ScopeAll} (`allow-all`) — the user sees every row (super-admin, a
|
|
36
|
+
* `before` grant, or a permission-provider grant). No `WHERE` is added.
|
|
37
|
+
* - {@link ScopeNone} (`deny-all`) — the user sees no rows (anonymous, or a policy
|
|
38
|
+
* with no `scope`/`viewAny`). The adapter emits an always-false predicate.
|
|
39
|
+
*/
|
|
40
|
+
export type ScopeConstraint = ScopeAll | ScopeNone | ScopeNode;
|
|
41
|
+
/** Terminal: the user may access every row — no filter is applied. */
|
|
42
|
+
export interface ScopeAll {
|
|
43
|
+
kind: 'all';
|
|
44
|
+
}
|
|
45
|
+
/** Terminal: the user may access no rows — an always-false predicate is applied. */
|
|
46
|
+
export interface ScopeNone {
|
|
47
|
+
kind: 'none';
|
|
48
|
+
}
|
|
49
|
+
/** A non-terminal constraint: a single condition or a boolean group of them. */
|
|
50
|
+
export type ScopeNode = ScopeCondition | ScopeGroup;
|
|
51
|
+
/** Comparison operators a {@link ScopeCondition} may use. ORM-neutral; adapters map them. */
|
|
52
|
+
export type ScopeOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'isNull' | 'isNotNull';
|
|
53
|
+
/**
|
|
54
|
+
* A leaf condition: `field <op> value`. `field` names a COLUMN on the scoped entity
|
|
55
|
+
* (validated as a safe identifier by the adapter). `value` is bound as a parameter
|
|
56
|
+
* (never interpolated). `isNull`/`isNotNull` ignore `value`.
|
|
57
|
+
*/
|
|
58
|
+
export interface ScopeCondition {
|
|
59
|
+
kind: 'condition';
|
|
60
|
+
field: string;
|
|
61
|
+
op: ScopeOperator;
|
|
62
|
+
value?: unknown;
|
|
63
|
+
}
|
|
64
|
+
/** A boolean group: AND/OR over child nodes. An empty group is the group's identity. */
|
|
65
|
+
export interface ScopeGroup {
|
|
66
|
+
kind: 'and' | 'or';
|
|
67
|
+
nodes: ScopeNode[];
|
|
68
|
+
}
|
|
69
|
+
/** The allow-all terminal (super-admin / before-grant). */
|
|
70
|
+
export declare const scopeAll: ScopeAll;
|
|
71
|
+
/** The deny-all terminal (anonymous / no access). */
|
|
72
|
+
export declare const scopeNone: ScopeNone;
|
|
73
|
+
/** Build a leaf `field <op> value` condition. */
|
|
74
|
+
export declare function where(field: string, op: ScopeOperator, value?: unknown): ScopeCondition;
|
|
75
|
+
/** Shorthand for the common `field = value` (equality) condition. */
|
|
76
|
+
export declare function eq(field: string, value: unknown): ScopeCondition;
|
|
77
|
+
/** Combine nodes with AND. A single node is returned as-is; empty → allow-all. */
|
|
78
|
+
export declare function and(...nodes: ScopeNode[]): ScopeConstraint;
|
|
79
|
+
/** Combine nodes with OR. A single node is returned as-is; empty → deny-all. */
|
|
80
|
+
export declare function or(...nodes: ScopeNode[]): ScopeConstraint;
|
|
81
|
+
/**
|
|
82
|
+
* What a policy `scope` method may return:
|
|
83
|
+
* - a {@link ScopeConstraint} (terminal or AST),
|
|
84
|
+
* - `true` → allow-all (sugar for {@link scopeAll}),
|
|
85
|
+
* - `false`/`null`/`undefined` → deny-all (sugar for {@link scopeNone}).
|
|
86
|
+
*
|
|
87
|
+
* May be async.
|
|
88
|
+
*/
|
|
89
|
+
export type ScopeResult = ScopeConstraint | boolean | null | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* A policy scope method. Mirrors Pundit's `Scope#resolve`: given the current user,
|
|
92
|
+
* return the constraint that filters the entity's collection to the accessible rows.
|
|
93
|
+
* Conventionally named `scope` on a `@Policy` class (a `viewAny`-style fallback is
|
|
94
|
+
* also honored by the Gate when no `scope` is present).
|
|
95
|
+
*/
|
|
96
|
+
export type ScopeMethod = (user: User, resource?: Resource) => ScopeResult | Promise<ScopeResult>;
|
|
97
|
+
/** Normalize a {@link ScopeResult} (incl. boolean/nullish sugar) into a {@link ScopeConstraint}. */
|
|
98
|
+
export declare function normalizeScope(result: ScopeResult): ScopeConstraint;
|
|
99
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAA6B,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,sEAAsE;AACtE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,KAAK,CAAC;CACb;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,UAAU,CAAC;AAEpD,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GACrB,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,QAAQ,GACR,WAAW,CAAC;AAEhB;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,2DAA2D;AAC3D,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAC;AAElD,qDAAqD;AACrD,eAAO,MAAM,SAAS,EAAE,SAA4B,CAAC;AAErD,iDAAiD;AACjD,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,cAAc,CAIvF;AAED,qEAAqE;AACrE,wBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,cAAc,CAEhE;AAED,kFAAkF;AAClF,wBAAgB,GAAG,CAAC,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,eAAe,CAI1D;AAED,gFAAgF;AAChF,wBAAgB,EAAE,CAAC,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,eAAe,CAIzD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AAElG,oGAAoG;AACpG,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,eAAe,CAKnE"}
|
package/dist/scope.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative identifier allowlist for any name interpolated into a query by an adapter —
|
|
3
|
+
* table/schema names and policy-supplied scope `field`s. BYO/policy-supplied names are validated
|
|
4
|
+
* against this before interpolation; values are always bound, never concatenated.
|
|
5
|
+
*/
|
|
6
|
+
export const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7
|
+
/**
|
|
8
|
+
* Validate an identifier (table/schema/column name) against {@link SAFE_IDENTIFIER}, throwing on
|
|
9
|
+
* anything containing quotes, dots, whitespace, or other unsafe characters. Single source of the
|
|
10
|
+
* injection guard for every adapter.
|
|
11
|
+
*
|
|
12
|
+
* @param value the identifier to check
|
|
13
|
+
* @param what a label for the error message (e.g. `table name "roles"`)
|
|
14
|
+
*/
|
|
15
|
+
export function assertSafeIdentifier(value, what) {
|
|
16
|
+
if (!SAFE_IDENTIFIER.test(value)) {
|
|
17
|
+
throw new Error(`Unsafe ${what}: ${JSON.stringify(value)}. Identifiers must match /^[A-Za-z_][A-Za-z0-9_]*$/ (letters, digits, underscore; not starting with a digit). This blocks injection via configured names.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** The allow-all terminal (super-admin / before-grant). */
|
|
21
|
+
export const scopeAll = { kind: 'all' };
|
|
22
|
+
/** The deny-all terminal (anonymous / no access). */
|
|
23
|
+
export const scopeNone = { kind: 'none' };
|
|
24
|
+
/** Build a leaf `field <op> value` condition. */
|
|
25
|
+
export function where(field, op, value) {
|
|
26
|
+
return op === 'isNull' || op === 'isNotNull'
|
|
27
|
+
? { kind: 'condition', field, op }
|
|
28
|
+
: { kind: 'condition', field, op, value };
|
|
29
|
+
}
|
|
30
|
+
/** Shorthand for the common `field = value` (equality) condition. */
|
|
31
|
+
export function eq(field, value) {
|
|
32
|
+
return { kind: 'condition', field, op: 'eq', value };
|
|
33
|
+
}
|
|
34
|
+
/** Combine nodes with AND. A single node is returned as-is; empty → allow-all. */
|
|
35
|
+
export function and(...nodes) {
|
|
36
|
+
if (nodes.length === 0)
|
|
37
|
+
return scopeAll;
|
|
38
|
+
if (nodes.length === 1)
|
|
39
|
+
return nodes[0];
|
|
40
|
+
return { kind: 'and', nodes };
|
|
41
|
+
}
|
|
42
|
+
/** Combine nodes with OR. A single node is returned as-is; empty → deny-all. */
|
|
43
|
+
export function or(...nodes) {
|
|
44
|
+
if (nodes.length === 0)
|
|
45
|
+
return scopeNone;
|
|
46
|
+
if (nodes.length === 1)
|
|
47
|
+
return nodes[0];
|
|
48
|
+
return { kind: 'or', nodes };
|
|
49
|
+
}
|
|
50
|
+
/** Normalize a {@link ScopeResult} (incl. boolean/nullish sugar) into a {@link ScopeConstraint}. */
|
|
51
|
+
export function normalizeScope(result) {
|
|
52
|
+
if (result == null)
|
|
53
|
+
return scopeNone;
|
|
54
|
+
if (result === true)
|
|
55
|
+
return scopeAll;
|
|
56
|
+
if (result === false)
|
|
57
|
+
return scopeNone;
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,IAAY;IAC9D,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,UAAU,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,2JAA2J,CACpM,CAAC;IACJ,CAAC;AACH,CAAC;AAuED,2DAA2D;AAC3D,MAAM,CAAC,MAAM,QAAQ,GAAa,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAElD,qDAAqD;AACrD,MAAM,CAAC,MAAM,SAAS,GAAc,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAErD,iDAAiD;AACjD,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,EAAiB,EAAE,KAAe;IACrE,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,WAAW;QAC1C,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE;QAClC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,EAAE,CAAC,KAAa,EAAE,KAAc;IAC9C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACvD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,GAAG,CAAC,GAAG,KAAkB;IACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAc,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,EAAE,CAAC,GAAG,KAAkB;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAc,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC/B,CAAC;AAoBD,oGAAoG;AACpG,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IACrC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACrC,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,SAAS,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reference to a user. Matches nestjs-context's `UserRef` shape (`{ type, id }`),
|
|
3
|
+
* but the store accepts either a full ref or a bare id (defaulting `type` to `'user'`).
|
|
4
|
+
*/
|
|
5
|
+
export interface UserRefInput {
|
|
6
|
+
type?: string;
|
|
7
|
+
id: string | number;
|
|
8
|
+
}
|
|
9
|
+
/** A user reference, or just its id (treated as `{ type: 'user', id }`). */
|
|
10
|
+
export type UserRef = UserRefInput | string | number;
|
|
11
|
+
/**
|
|
12
|
+
* A user's effective permissions: the role names they hold and the flattened set of
|
|
13
|
+
* permission names granted by those roles.
|
|
14
|
+
*/
|
|
15
|
+
export interface UserAuthz {
|
|
16
|
+
roles: string[];
|
|
17
|
+
permissions: string[];
|
|
18
|
+
}
|
|
19
|
+
/** Normalize a {@link UserRef} to `{ type, id }` with `id` stringified. */
|
|
20
|
+
export declare function normalizeUserRef(ref: UserRef): {
|
|
21
|
+
type: string;
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=store-kit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-kit.d.ts","sourceRoot":"","sources":["../src/store-kit.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAK3E"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// packages/core/src/store-kit.ts
|
|
2
|
+
//
|
|
3
|
+
// The shared persistence contract for ORM-backed RBAC stores (typeorm/prisma/
|
|
4
|
+
// mikro-orm adapters). Exposed via the `@dudousxd/nestjs-authz/store-kit` subpath
|
|
5
|
+
// rather than the main barrel, because the barrel already exports a different
|
|
6
|
+
// `UserRef` (nestjs-context's `{ type, id }` shape). The adapters re-export these
|
|
7
|
+
// under their own public names so a single definition can't drift across them.
|
|
8
|
+
/** Normalize a {@link UserRef} to `{ type, id }` with `id` stringified. */
|
|
9
|
+
export function normalizeUserRef(ref) {
|
|
10
|
+
if (typeof ref === 'string' || typeof ref === 'number') {
|
|
11
|
+
return { type: 'user', id: String(ref) };
|
|
12
|
+
}
|
|
13
|
+
return { type: ref.type ?? 'user', id: String(ref.id) };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=store-kit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-kit.js","sourceRoot":"","sources":["../src/store-kit.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,8EAA8E;AAC9E,kFAAkF;AAClF,8EAA8E;AAC9E,kFAAkF;AAClF,+EAA+E;AAuB/E,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1D,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,26 +11,62 @@ export type Resource = object;
|
|
|
11
11
|
* is whatever the app's auth layer produced (or `undefined` when unauthenticated).
|
|
12
12
|
*/
|
|
13
13
|
export type User = unknown;
|
|
14
|
+
/**
|
|
15
|
+
* The richer result a policy method / ad-hoc gate may return instead of a bare
|
|
16
|
+
* boolean: a verdict plus an optional human-readable `message` explaining a denial.
|
|
17
|
+
* The message is surfaced on the thrown `ForbiddenException` (so a 403 is debuggable)
|
|
18
|
+
* and published on the diagnostics decision. Returning a bare boolean is unchanged.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors Laravel's `Response::allow()/deny('message')`.
|
|
21
|
+
*/
|
|
22
|
+
export interface PolicyResponse {
|
|
23
|
+
allowed: boolean;
|
|
24
|
+
message?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* What a policy method / gate / hook may return: a bare boolean (unchanged) or the
|
|
28
|
+
* richer {@link PolicyResponse}. Hooks additionally allow `void`/`undefined` to mean
|
|
29
|
+
* "no opinion — fall through".
|
|
30
|
+
*/
|
|
31
|
+
export type PolicyResult = boolean | PolicyResponse;
|
|
14
32
|
/**
|
|
15
33
|
* A policy method: decides a single ability for a `(user, resource?)` pair.
|
|
16
|
-
* Class-level abilities (e.g. `create`) omit the resource. May be async.
|
|
34
|
+
* Class-level abilities (e.g. `create`) omit the resource. May be async. May return
|
|
35
|
+
* a bare boolean (unchanged) or a richer {@link PolicyResponse} with a deny message.
|
|
17
36
|
*/
|
|
18
|
-
export type PolicyMethod = (user: User, resource?: Resource) =>
|
|
37
|
+
export type PolicyMethod = (user: User, resource?: Resource) => PolicyResult | Promise<PolicyResult>;
|
|
19
38
|
/**
|
|
20
39
|
* Optional per-policy `before` hook. Runs before the ability method.
|
|
21
40
|
* - return `true` → allow (short-circuit)
|
|
22
41
|
* - return `false` → deny (short-circuit)
|
|
23
42
|
* - return `void`/`undefined` → fall through to the ability method
|
|
24
43
|
*/
|
|
25
|
-
export type PolicyBeforeHook = (user: User, ability: string) =>
|
|
44
|
+
export type PolicyBeforeHook = (user: User, ability: string) => PolicyResult | undefined | Promise<PolicyResult | undefined>;
|
|
26
45
|
/** A policy instance is an arbitrary object whose methods are abilities. */
|
|
27
46
|
export type PolicyInstance = Record<string, unknown> & {
|
|
28
47
|
before?: PolicyBeforeHook;
|
|
29
48
|
};
|
|
30
49
|
/**
|
|
31
50
|
* An ad-hoc gate: a model-less ability resolved by name via `gate.define`.
|
|
51
|
+
* May return a bare boolean (unchanged) or a richer {@link PolicyResponse}.
|
|
32
52
|
*/
|
|
33
|
-
export type GateFn = (user: User, resource?: Resource) =>
|
|
53
|
+
export type GateFn = (user: User, resource?: Resource) => PolicyResult | Promise<PolicyResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Global `after` hook. Mirrors Laravel's `Gate::after()`: runs AFTER policy/gate
|
|
56
|
+
* resolution and may observe or override the decision.
|
|
57
|
+
*
|
|
58
|
+
* `result` is the verdict produced by the policy/gate (`true`/`false`), or
|
|
59
|
+
* `undefined` when the policy/gate returned a nullish value (it had "no opinion").
|
|
60
|
+
*
|
|
61
|
+
* Override semantics (Laravel-idiomatic): the hook's return value REPLACES the
|
|
62
|
+
* decision ONLY when the policy/gate produced no explicit verdict (`result` is
|
|
63
|
+
* `undefined`). When the policy/gate DID decide (returned a boolean), `after` is
|
|
64
|
+
* purely an observer and its return value is ignored — so it can never silently
|
|
65
|
+
* overturn an explicit allow/deny. Returning `void`/`undefined` always leaves the
|
|
66
|
+
* decision untouched. Like a policy, it may return a {@link PolicyResponse} to
|
|
67
|
+
* attach a deny message.
|
|
68
|
+
*/
|
|
69
|
+
export type AfterHook = (user: User, ability: string, result: boolean | undefined, resource?: Resource) => PolicyResult | undefined | Promise<PolicyResult | undefined>;
|
|
34
70
|
/**
|
|
35
71
|
* A resource loader: turns an `id` into the REAL entity instance for a resource
|
|
36
72
|
* `type`. Used by the opt-in `POST /authz/can` endpoint to rehydrate the client's
|
|
@@ -47,7 +83,7 @@ export type ResourceLoaderMap = Record<string, ResourceLoader>;
|
|
|
47
83
|
* Global super-admin before-hook. Runs before any policy/gate; truthy → allow.
|
|
48
84
|
* `void`/`undefined`/`false` falls through to normal resolution.
|
|
49
85
|
*/
|
|
50
|
-
export type SuperAdminHook = (user: User, ability: string) =>
|
|
86
|
+
export type SuperAdminHook = (user: User, ability: string) => PolicyResult | undefined | Promise<PolicyResult | undefined>;
|
|
51
87
|
export interface AuthzModuleOptions {
|
|
52
88
|
/**
|
|
53
89
|
* Policy classes to register explicitly. Each must be decorated with
|
|
@@ -67,6 +103,18 @@ export interface AuthzModuleOptions {
|
|
|
67
103
|
* explicit `gate.forUser(entity)` path you receive exactly what you passed.
|
|
68
104
|
*/
|
|
69
105
|
superAdmin?: SuperAdminHook;
|
|
106
|
+
/**
|
|
107
|
+
* Global `after` hook (Laravel's `Gate::after()`). Runs AFTER policy/gate
|
|
108
|
+
* resolution and may observe or override the decision. It can only OVERRIDE the
|
|
109
|
+
* verdict when the policy/gate produced no explicit result (returned nullish);
|
|
110
|
+
* when the policy/gate decided explicitly, `after` is observe-only and its return
|
|
111
|
+
* value is ignored. Returning `void`/`undefined` always leaves the decision
|
|
112
|
+
* untouched. See {@link AfterHook}.
|
|
113
|
+
*
|
|
114
|
+
* Receives the same (possibly un-hydrated) `user` as {@link superAdmin} on the
|
|
115
|
+
* context path — apply {@link resolveUser} if you need the full entity.
|
|
116
|
+
*/
|
|
117
|
+
after?: AfterHook;
|
|
70
118
|
/**
|
|
71
119
|
* Optional hook to hydrate the full user entity from the context's
|
|
72
120
|
* {@link UserRef} (`{ type, id }`) before policies / `before` / `superAdmin`
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B;;;GAGG;AACH,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC;AAE3B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-authz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "NestJS authorization — Laravel-style Gates & Policies, @Can guard, resource resolver (zero DB).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"import": "./dist/index.js",
|
|
19
19
|
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./store-kit": {
|
|
22
|
+
"types": "./dist/store-kit.d.ts",
|
|
23
|
+
"import": "./dist/store-kit.js",
|
|
24
|
+
"default": "./dist/store-kit.js"
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"files": [
|