@dudousxd/nestjs-authz 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @dudousxd/nestjs-authz
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#61](https://github.com/DavideCarvalho/nestjs-authz/pull/61) [`d1c719d`](https://github.com/DavideCarvalho/nestjs-authz/commit/d1c719d9ee973bc802ba8bf6854246875c4c2da9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Admin management operations on every RBAC store adapter (TypeORM, MikroORM, Prisma, Drizzle), with the same semantics everywhere:
8
+
9
+ - `deleteRole(roleName): Promise<boolean>` deletes the role, its role→permission links and every user assignment of it (all tenants, all sources) in one transaction. Returns whether the role existed. Permissions are kept.
10
+ - `syncRolePermissions(roleName, permissionNames): Promise<void>` replaces the role's permission set with exactly these names (spatie's `syncPermissions`), creating the role and permissions by name when missing.
11
+ - `getRolePermissions(roleNames): Promise<Record<string, string[]>>` returns each role's sorted permission names without N+1 queries. Every existing requested role is a key, even with no permissions (`[]`). Roles that don't exist are absent.
12
+ - `listRoleAssignments(filter?): Promise<UserRoleAssignment[]>` returns raw `{ userType, userId, role, source, tenantId }` rows for admin listings, ordered by `(userType, userId, role, source, tenantId)`. Filters: `role` (a name or a list), `user`, `source`, and `tenantId`. Omitting `tenantId` applies no tenant filter, `null` returns global assignments only, and a string returns only that tenant's scoped assignments, without the global ones. MikroORM and Prisma have no tenant-scoped assignments, so there a tenant id string matches nothing.
13
+ - `removeUser(user): Promise<void>` deletes every role assignment of the user (all tenants, all sources) and, on TypeORM and Drizzle, every direct permission.
14
+
15
+ Core's `store-kit` exports the `RoleAssignmentFilter` and `UserRoleAssignment` types, plus the `roleFilterNames` and `compareUserRoleAssignments` helpers the adapters share. The adapters now require `@dudousxd/nestjs-authz >= 0.8.0`.
16
+
3
17
  ## 0.7.0
4
18
 
5
19
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.7.0";
1
+ export declare const VERSION = "0.8.0";
2
2
  export { Gate, BoundGate } from './gate.js';
3
3
  export type { BatchAbility, BatchResult } from './gate.js';
4
4
  export { PermissionCache } from './permission-cache.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Rewritten from this package's package.json by `scripts/sync-version.mjs`, which the root
2
2
  // `version-packages` script runs right after `changeset version`. The build is plain `tsc`
3
3
  // and injects nothing, so the literal below is what ships in dist.
4
- export const VERSION = '0.7.0';
4
+ export const VERSION = '0.8.0';
5
5
  export { Gate, BoundGate } from './gate.js';
6
6
  export { PermissionCache } from './permission-cache.js';
7
7
  export { authzDecisionChannel, publishAuthzDecision } from './diagnostics.js';
@@ -38,4 +38,42 @@ export interface RoleAssignment {
38
38
  /** Tenant the assignment is scoped to, or `null` for a global one (tenant-aware adapters). */
39
39
  tenantId: string | null;
40
40
  }
41
+ /**
42
+ * One persisted role assignment together with the user holding it — a raw row of the
43
+ * user↔role pivot, as returned by the adapters' `listRoleAssignments` (admin listings such as
44
+ * "every member's roles in tenant T" or "who holds role R").
45
+ */
46
+ export interface UserRoleAssignment extends RoleAssignment {
47
+ /** The user's type (`'user'` unless the assignment was made with a typed {@link UserRef}). */
48
+ userType: string;
49
+ /** The user's id, stringified. */
50
+ userId: string;
51
+ }
52
+ /** Filter for the adapters' `listRoleAssignments`. Every field is optional; they AND together. */
53
+ export interface RoleAssignmentFilter {
54
+ /**
55
+ * Tenant filter:
56
+ * - `undefined` (omitted): no tenant filter — global AND every tenant's assignments;
57
+ * - `null`: GLOBAL assignments only;
58
+ * - a string: exactly that tenant's scoped assignments. Global assignments are NOT included
59
+ * (unlike the permission checks, where a global assignment applies in every tenant) — pass
60
+ * `null` in a second call when you need them too.
61
+ *
62
+ * Adapters without tenant support store every assignment as global, so a string matches nothing.
63
+ */
64
+ tenantId?: string | null;
65
+ /** Only assignments of this role, or of any of these roles (an empty list matches nothing). */
66
+ role?: string | readonly string[];
67
+ /** Only this user's assignments. */
68
+ user?: UserRef;
69
+ /** Only assignments from this source (`'manual'`, `'sso'`, …). */
70
+ source?: string;
71
+ }
72
+ /** Normalize {@link RoleAssignmentFilter.role} to a list (`undefined` = no role filter). */
73
+ export declare function roleFilterNames(role: RoleAssignmentFilter['role']): string[] | undefined;
74
+ /**
75
+ * Deterministic order for `listRoleAssignments` rows: `(userType, userId, role, source, tenantId)`,
76
+ * by code unit (not locale), so every adapter returns the same order for the same data.
77
+ */
78
+ export declare function compareUserRoleAssignments(a: UserRoleAssignment, b: UserRoleAssignment): number;
41
79
  //# sourceMappingURL=store-kit.d.ts.map
@@ -1 +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;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB"}
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;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kGAAkG;AAClG,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAClC,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,GAAG,SAAS,CAGxF;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAgB/F"}
package/dist/store-kit.js CHANGED
@@ -17,4 +17,32 @@ export function normalizeUserRef(ref) {
17
17
  * (`assignRole`) and every row that predates per-source assignments.
18
18
  */
19
19
  export const DEFAULT_ROLE_SOURCE = 'manual';
20
+ /** Normalize {@link RoleAssignmentFilter.role} to a list (`undefined` = no role filter). */
21
+ export function roleFilterNames(role) {
22
+ if (role === undefined)
23
+ return undefined;
24
+ return typeof role === 'string' ? [role] : [...new Set(role)];
25
+ }
26
+ /**
27
+ * Deterministic order for `listRoleAssignments` rows: `(userType, userId, role, source, tenantId)`,
28
+ * by code unit (not locale), so every adapter returns the same order for the same data.
29
+ */
30
+ export function compareUserRoleAssignments(a, b) {
31
+ const keys = (x) => [
32
+ x.userType,
33
+ x.userId,
34
+ x.role,
35
+ x.source,
36
+ x.tenantId ?? '',
37
+ ];
38
+ const ka = keys(a);
39
+ const kb = keys(b);
40
+ for (let i = 0; i < ka.length; i++) {
41
+ const l = ka[i];
42
+ const r = kb[i];
43
+ if (l !== r)
44
+ return l < r ? -1 : 1;
45
+ }
46
+ return 0;
47
+ }
20
48
  //# sourceMappingURL=store-kit.js.map
@@ -1 +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;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC"}
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;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AAgD5C,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,IAAkC;IAChE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,CAAqB,EAAE,CAAqB;IACrF,MAAM,IAAI,GAAG,CAAC,CAAqB,EAAE,EAAE,CAAC;QACtC,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,QAAQ,IAAI,EAAE;KACjB,CAAC;IACF,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;QAC1B,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-authz",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "NestJS authorization — Laravel-style Gates & Policies, @Can guard, resource resolver (zero DB).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,6 +62,13 @@ await gate.forUser(user).allows('posts.publish'); // true via PERMISSION_PROVIDE
62
62
  await gate.forUser(user).hasRole('editor'); // true via ROLE_PROVIDER
63
63
  ```
64
64
 
65
+ Admin-UI management lives on every adapter's store too (same semantics everywhere):
66
+ `syncRolePermissions(role, names)` replaces a role's permission set, `getRolePermissions(roles)`
67
+ returns `{ [role]: permissionNames }` (missing roles absent), `deleteRole(role)` removes the role
68
+ with its links and assignments, `listRoleAssignments({ tenantId?, role?, user?, source? })` lists
69
+ raw `{ userType, userId, role, source, tenantId }` rows (`tenantId: null` = global only, a string =
70
+ that tenant's scoped rows only), and `removeUser(user)` drops all of a user's assignments.
71
+
65
72
  ## Core patterns
66
73
 
67
74
  ### A hand-rolled PermissionProvider