@spinajs/rbac 2.0.522 → 2.0.523

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 (47) hide show
  1. package/lib/cjs/actions.d.ts.map +1 -1
  2. package/lib/cjs/actions.js.map +1 -1
  3. package/lib/cjs/index.d.ts +2 -0
  4. package/lib/cjs/index.d.ts.map +1 -1
  5. package/lib/cjs/index.js +2 -0
  6. package/lib/cjs/index.js.map +1 -1
  7. package/lib/cjs/interfaces.d.ts +0 -24
  8. package/lib/cjs/interfaces.d.ts.map +1 -1
  9. package/lib/cjs/interfaces.js +1 -5
  10. package/lib/cjs/interfaces.js.map +1 -1
  11. package/lib/cjs/middleware.d.ts +16 -6
  12. package/lib/cjs/middleware.d.ts.map +1 -1
  13. package/lib/cjs/middleware.js +151 -90
  14. package/lib/cjs/middleware.js.map +1 -1
  15. package/lib/cjs/orm-permission.d.ts +77 -0
  16. package/lib/cjs/orm-permission.d.ts.map +1 -0
  17. package/lib/cjs/orm-permission.js +128 -0
  18. package/lib/cjs/orm-permission.js.map +1 -0
  19. package/lib/cjs/permission-service.d.ts +80 -0
  20. package/lib/cjs/permission-service.d.ts.map +1 -0
  21. package/lib/cjs/permission-service.js +144 -0
  22. package/lib/cjs/permission-service.js.map +1 -0
  23. package/lib/mjs/actions.d.ts.map +1 -1
  24. package/lib/mjs/actions.js.map +1 -1
  25. package/lib/mjs/index.d.ts +2 -0
  26. package/lib/mjs/index.d.ts.map +1 -1
  27. package/lib/mjs/index.js +2 -0
  28. package/lib/mjs/index.js.map +1 -1
  29. package/lib/mjs/interfaces.d.ts +0 -24
  30. package/lib/mjs/interfaces.d.ts.map +1 -1
  31. package/lib/mjs/interfaces.js +0 -4
  32. package/lib/mjs/interfaces.js.map +1 -1
  33. package/lib/mjs/middleware.d.ts +16 -6
  34. package/lib/mjs/middleware.d.ts.map +1 -1
  35. package/lib/mjs/middleware.js +152 -91
  36. package/lib/mjs/middleware.js.map +1 -1
  37. package/lib/mjs/orm-permission.d.ts +77 -0
  38. package/lib/mjs/orm-permission.d.ts.map +1 -0
  39. package/lib/mjs/orm-permission.js +119 -0
  40. package/lib/mjs/orm-permission.js.map +1 -0
  41. package/lib/mjs/permission-service.d.ts +80 -0
  42. package/lib/mjs/permission-service.d.ts.map +1 -0
  43. package/lib/mjs/permission-service.js +137 -0
  44. package/lib/mjs/permission-service.js.map +1 -0
  45. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  46. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  47. package/package.json +11 -11
@@ -0,0 +1,80 @@
1
+ import { Permission } from 'accesscontrol';
2
+ import { Class } from '@spinajs/di';
3
+ import type { User } from './models/User.js';
4
+ export type PermissionVerb = 'create' | 'read' | 'update' | 'delete';
5
+ export type PermissionGrantScope = 'any' | 'own' | 'none';
6
+ /**
7
+ * Base for per-domain permission services: decides and explains authorization at the
8
+ * domain layer, BEFORE any effect — the ORM rbac middleware stays the enforcement
9
+ * backstop. One concrete class per rbac resource; public API is domain-named assert
10
+ * methods built from the protected primitives below. The rbac User model is the actor —
11
+ * no wrapper objects.
12
+ *
13
+ * Stateless by contract: no instance fields in subclasses. Spinajs DI caches resolved
14
+ * instances, so `@FromDI()` injection in controllers, `@Autoinject` fields and
15
+ * `usePermission()` anywhere else share one instance safely.
16
+ */
17
+ export declare abstract class PermissionService {
18
+ /** Rbac resource name this service guards — same string the grants config uses. */
19
+ protected abstract readonly Resource: string;
20
+ /**
21
+ * The acting user: explicit argument wins, else the rbac AsyncLocalStorage store.
22
+ * No user anywhere throws — a permission decision must never silently pass without
23
+ * an identity.
24
+ */
25
+ protected user(user?: User): User;
26
+ /** Roles the decision runs against — the shared ActiveRole rule against the ambient store. */
27
+ protected effectiveRoles(user: User): string[];
28
+ protected grantScope(user: User, verb: PermissionVerb): PermissionGrantScope;
29
+ /** Single guarded probe against this.Resource — an unknown role answers false. */
30
+ protected can(roles: string[], permission: string): boolean;
31
+ /** Resolve acting user + scope, refusing `none` with the (overridable) domain error. */
32
+ protected assertGrant(verb: PermissionVerb, user?: User): {
33
+ user: User;
34
+ scope: 'any' | 'own';
35
+ };
36
+ /**
37
+ * Runs `fn` as the given user, so rbac-scoped ORM queries inside answer "what does
38
+ * THIS user see" — ownership rules stay stated once, in the query layer. The store's
39
+ * own user runs in the ambient store untouched; any other user impersonates: User is
40
+ * swapped and ActiveRole cleared (it belongs to the ambient user only), everything
41
+ * else in the store kept.
42
+ */
43
+ protected withUser<T>(user: User, fn: () => T | Promise<T>): Promise<T>;
44
+ protected noPermissionError(user: User, verb: PermissionVerb): Error;
45
+ protected noUserError(): Error;
46
+ private storage;
47
+ }
48
+ /**
49
+ * Retrieves a permission service from DI — sugar for domain services and plain
50
+ * functions down the callstack, so code reads as intent
51
+ * (`usePermission(ContentEntriesPermission)`) instead of container plumbing.
52
+ * Answers the same DI-cached instance controllers receive via `@FromDI()`.
53
+ *
54
+ * Accepts ONLY PermissionService subclasses — the generic constraint is erased at
55
+ * runtime, so this is enforced here too instead of degrading into a bare DI.resolve.
56
+ */
57
+ export declare function usePermission<T extends PermissionService>(type: Class<T>): T;
58
+ /**
59
+ * THE guarded accesscontrol probe — single home for the AccessControlError semantics
60
+ * previously copy-pasted into the ORM query middleware and rbac-http's RbacPolicy.
61
+ * Answers the Permission object (grant attributes stay reachable), or null for a role
62
+ * unknown to accesscontrol — which throws AccessControlError for any role absent from
63
+ * the grants map and rejects the whole role array on one unknown member. A genuine
64
+ * programming error (bad permission name) still propagates loud.
65
+ */
66
+ export declare function probeGrant(roles: string | string[], permission: string, resource: string): Permission | null;
67
+ /**
68
+ * THE ActiveRole rule — single home for the narrowing previously inlined in the ORM
69
+ * query middleware and rbac-http's checkRoutePermission. The session's ActiveRole
70
+ * applies only to the user it was selected for — matched by id; any other user answers
71
+ * their full role list.
72
+ *
73
+ * The storage parameter is structural (not IRbacAsyncStorage) so both the rbac ALS
74
+ * store and http's request storage (whose User is `User | null`) fit.
75
+ */
76
+ export declare function effectiveRoles(user: User, storage?: {
77
+ User?: User | null;
78
+ ActiveRole?: string;
79
+ }): string[];
80
+ //# sourceMappingURL=permission-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-service.d.ts","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAM,MAAM,aAAa,CAAC;AAGxC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrE,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,8BAAsB,iBAAiB;IACrC,mFAAmF;IACnF,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE7C;;;;OAIG;IACH,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAWhC;IAED,8FAA8F;IAC9F,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAE7C;IAED,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,GAAG,oBAAoB,CAU3E;IAED,kFAAkF;IAClF,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE1D;IAED,wFAAwF;IACxF,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAA;KAAE,CAS7F;IAED;;;;;;OAMG;IACH,UAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAS5E;IAED,SAAS,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,GAAG,KAAK,CAEnE;IAED,SAAS,CAAC,WAAW,IAAI,KAAK,CAE7B;IAED,OAAO,CAAC,OAAO;CAGhB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,iBAAiB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAM5E;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAc5G;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAK1G"}
@@ -0,0 +1,137 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { AccessControl } from 'accesscontrol';
3
+ import { DI } from '@spinajs/di';
4
+ import { Forbidden } from '@spinajs/exceptions';
5
+ /**
6
+ * Base for per-domain permission services: decides and explains authorization at the
7
+ * domain layer, BEFORE any effect — the ORM rbac middleware stays the enforcement
8
+ * backstop. One concrete class per rbac resource; public API is domain-named assert
9
+ * methods built from the protected primitives below. The rbac User model is the actor —
10
+ * no wrapper objects.
11
+ *
12
+ * Stateless by contract: no instance fields in subclasses. Spinajs DI caches resolved
13
+ * instances, so `@FromDI()` injection in controllers, `@Autoinject` fields and
14
+ * `usePermission()` anywhere else share one instance safely.
15
+ */
16
+ export class PermissionService {
17
+ /**
18
+ * The acting user: explicit argument wins, else the rbac AsyncLocalStorage store.
19
+ * No user anywhere throws — a permission decision must never silently pass without
20
+ * an identity.
21
+ */
22
+ user(user) {
23
+ if (user) {
24
+ return user;
25
+ }
26
+ const store = this.storage().getStore();
27
+ if (!store?.User) {
28
+ throw this.noUserError();
29
+ }
30
+ return store.User;
31
+ }
32
+ /** Roles the decision runs against — the shared ActiveRole rule against the ambient store. */
33
+ effectiveRoles(user) {
34
+ return effectiveRoles(user, this.storage().getStore());
35
+ }
36
+ grantScope(user, verb) {
37
+ const roles = this.effectiveRoles(user);
38
+ if (this.can(roles, `${verb}Any`)) {
39
+ return 'any';
40
+ }
41
+ if (this.can(roles, `${verb}Own`)) {
42
+ return 'own';
43
+ }
44
+ return 'none';
45
+ }
46
+ /** Single guarded probe against this.Resource — an unknown role answers false. */
47
+ can(roles, permission) {
48
+ return probeGrant(roles, permission, this.Resource)?.granted ?? false;
49
+ }
50
+ /** Resolve acting user + scope, refusing `none` with the (overridable) domain error. */
51
+ assertGrant(verb, user) {
52
+ const acting = this.user(user);
53
+ const scope = this.grantScope(acting, verb);
54
+ if (scope === 'none') {
55
+ throw this.noPermissionError(acting, verb);
56
+ }
57
+ return { user: acting, scope };
58
+ }
59
+ /**
60
+ * Runs `fn` as the given user, so rbac-scoped ORM queries inside answer "what does
61
+ * THIS user see" — ownership rules stay stated once, in the query layer. The store's
62
+ * own user runs in the ambient store untouched; any other user impersonates: User is
63
+ * swapped and ActiveRole cleared (it belongs to the ambient user only), everything
64
+ * else in the store kept.
65
+ */
66
+ async withUser(user, fn) {
67
+ const storage = this.storage();
68
+ const parent = storage.getStore() ?? {};
69
+ if (parent.User?.Id === user.Id) {
70
+ return fn();
71
+ }
72
+ return storage.run({ ...parent, User: user, ActiveRole: undefined }, async () => fn());
73
+ }
74
+ noPermissionError(user, verb) {
75
+ return new Forbidden(`user ${user.Id} has no ${verb} grant for resource ${this.Resource}`);
76
+ }
77
+ noUserError() {
78
+ return new Forbidden('no acting user: none was given and none is present in the execution context');
79
+ }
80
+ storage() {
81
+ return DI.resolve(AsyncLocalStorage);
82
+ }
83
+ }
84
+ /**
85
+ * Retrieves a permission service from DI — sugar for domain services and plain
86
+ * functions down the callstack, so code reads as intent
87
+ * (`usePermission(ContentEntriesPermission)`) instead of container plumbing.
88
+ * Answers the same DI-cached instance controllers receive via `@FromDI()`.
89
+ *
90
+ * Accepts ONLY PermissionService subclasses — the generic constraint is erased at
91
+ * runtime, so this is enforced here too instead of degrading into a bare DI.resolve.
92
+ */
93
+ export function usePermission(type) {
94
+ if (typeof type !== 'function' || !(type.prototype instanceof PermissionService)) {
95
+ throw new Error(`usePermission accepts only PermissionService subclasses, got ${typeof type === 'function' ? type.name : typeof type}`);
96
+ }
97
+ return DI.resolve(type);
98
+ }
99
+ /**
100
+ * THE guarded accesscontrol probe — single home for the AccessControlError semantics
101
+ * previously copy-pasted into the ORM query middleware and rbac-http's RbacPolicy.
102
+ * Answers the Permission object (grant attributes stay reachable), or null for a role
103
+ * unknown to accesscontrol — which throws AccessControlError for any role absent from
104
+ * the grants map and rejects the whole role array on one unknown member. A genuine
105
+ * programming error (bad permission name) still propagates loud.
106
+ */
107
+ export function probeGrant(roles, permission, resource) {
108
+ const ac = DI.get('AccessControl');
109
+ if (!ac) {
110
+ throw new Error('no AccessControl registered in DI — rbac is not bootstrapped');
111
+ }
112
+ try {
113
+ return ac.can(roles)[permission](resource);
114
+ }
115
+ catch (err) {
116
+ if (AccessControl.isAccessControlError(err)) {
117
+ return null;
118
+ }
119
+ throw err;
120
+ }
121
+ }
122
+ /**
123
+ * THE ActiveRole rule — single home for the narrowing previously inlined in the ORM
124
+ * query middleware and rbac-http's checkRoutePermission. The session's ActiveRole
125
+ * applies only to the user it was selected for — matched by id; any other user answers
126
+ * their full role list.
127
+ *
128
+ * The storage parameter is structural (not IRbacAsyncStorage) so both the rbac ALS
129
+ * store and http's request storage (whose User is `User | null`) fit.
130
+ */
131
+ export function effectiveRoles(user, storage) {
132
+ if (storage?.ActiveRole && storage.User?.Id === user.Id) {
133
+ return [storage.ActiveRole];
134
+ }
135
+ return user.Role;
136
+ }
137
+ //# sourceMappingURL=permission-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-service.js","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"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"}