@arikajs/authorization 0.0.4 → 0.0.5

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 (82) hide show
  1. package/README.md +173 -78
  2. package/dist/AuthResponse.d.ts +23 -0
  3. package/dist/AuthResponse.d.ts.map +1 -0
  4. package/dist/AuthResponse.js +40 -0
  5. package/dist/AuthResponse.js.map +1 -0
  6. package/dist/AuthorizationContext.d.ts +31 -0
  7. package/dist/AuthorizationContext.d.ts.map +1 -0
  8. package/dist/AuthorizationContext.js +87 -0
  9. package/dist/AuthorizationContext.js.map +1 -0
  10. package/dist/AuthorizationManager.d.ts +15 -7
  11. package/dist/AuthorizationManager.d.ts.map +1 -1
  12. package/dist/AuthorizationManager.js +46 -12
  13. package/dist/AuthorizationManager.js.map +1 -1
  14. package/dist/Exceptions/AuthorizationException.d.ts +2 -1
  15. package/dist/Exceptions/AuthorizationException.d.ts.map +1 -1
  16. package/dist/Exceptions/AuthorizationException.js +2 -1
  17. package/dist/Exceptions/AuthorizationException.js.map +1 -1
  18. package/dist/Gate.d.ts +39 -3
  19. package/dist/Gate.d.ts.map +1 -1
  20. package/dist/Gate.js +121 -14
  21. package/dist/Gate.js.map +1 -1
  22. package/dist/Middleware/Authorize.d.ts +5 -4
  23. package/dist/Middleware/Authorize.d.ts.map +1 -1
  24. package/dist/Middleware/Authorize.js +24 -6
  25. package/dist/Middleware/Authorize.js.map +1 -1
  26. package/dist/PolicyResolver.d.ts +7 -2
  27. package/dist/PolicyResolver.d.ts.map +1 -1
  28. package/dist/PolicyResolver.js +26 -4
  29. package/dist/PolicyResolver.js.map +1 -1
  30. package/dist/RolePermission.d.ts +36 -0
  31. package/dist/RolePermission.d.ts.map +1 -0
  32. package/dist/RolePermission.js +59 -0
  33. package/dist/RolePermission.js.map +1 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/src/AuthResponse.d.ts +23 -0
  39. package/dist/src/AuthResponse.d.ts.map +1 -0
  40. package/dist/src/AuthResponse.js +40 -0
  41. package/dist/src/AuthResponse.js.map +1 -0
  42. package/dist/src/AuthorizationContext.d.ts +31 -0
  43. package/dist/src/AuthorizationContext.d.ts.map +1 -0
  44. package/dist/src/AuthorizationContext.js +87 -0
  45. package/dist/src/AuthorizationContext.js.map +1 -0
  46. package/dist/src/AuthorizationManager.d.ts +25 -0
  47. package/dist/src/AuthorizationManager.d.ts.map +1 -0
  48. package/dist/src/AuthorizationManager.js +64 -0
  49. package/dist/src/AuthorizationManager.js.map +1 -0
  50. package/dist/src/Contracts/Policy.d.ts +4 -0
  51. package/dist/src/Contracts/Policy.d.ts.map +1 -0
  52. package/dist/src/Contracts/Policy.js +3 -0
  53. package/dist/src/Contracts/Policy.js.map +1 -0
  54. package/dist/src/Exceptions/AuthorizationException.d.ts +6 -0
  55. package/dist/src/Exceptions/AuthorizationException.d.ts.map +1 -0
  56. package/dist/src/Exceptions/AuthorizationException.js +13 -0
  57. package/dist/src/Exceptions/AuthorizationException.js.map +1 -0
  58. package/dist/src/Gate.d.ts +76 -0
  59. package/dist/src/Gate.d.ts.map +1 -0
  60. package/dist/src/Gate.js +189 -0
  61. package/dist/src/Gate.js.map +1 -0
  62. package/dist/src/Middleware/Authorize.d.ts +13 -0
  63. package/dist/src/Middleware/Authorize.d.ts.map +1 -0
  64. package/dist/src/Middleware/Authorize.js +51 -0
  65. package/dist/src/Middleware/Authorize.js.map +1 -0
  66. package/dist/src/PolicyResolver.d.ts +21 -0
  67. package/dist/src/PolicyResolver.d.ts.map +1 -0
  68. package/dist/src/PolicyResolver.js +67 -0
  69. package/dist/src/PolicyResolver.js.map +1 -0
  70. package/dist/src/RolePermission.d.ts +36 -0
  71. package/dist/src/RolePermission.d.ts.map +1 -0
  72. package/dist/src/RolePermission.js +59 -0
  73. package/dist/src/RolePermission.js.map +1 -0
  74. package/dist/src/index.d.ts +10 -0
  75. package/dist/src/index.d.ts.map +1 -0
  76. package/dist/src/index.js +26 -0
  77. package/dist/src/index.js.map +1 -0
  78. package/dist/tests/Authorization.test.d.ts +2 -0
  79. package/dist/tests/Authorization.test.d.ts.map +1 -0
  80. package/dist/tests/Authorization.test.js +236 -0
  81. package/dist/tests/Authorization.test.js.map +1 -0
  82. package/package.json +44 -42
package/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  ## Arika Authorization
2
2
 
3
- `@arikajs/authorization` provides authorization (access control) for the ArikaJS framework.
3
+ `@arikajs/authorization` provides a powerful, enterprise-grade authorization (access control) system for the ArikaJS framework.
4
4
 
5
- It answers one critical question: **“Is the authenticated user allowed to perform this action?”**
5
+ It answers one critical question: **"Is the authenticated user allowed to perform this action?"**
6
6
 
7
- This package provides a powerful authorization system with Gates and Policies, designed specifically for a modular, TypeScript-first Node.js framework.
7
+ ```ts
8
+ // Per-request scoped — safe for concurrent requests
9
+ if (await req.can('edit-post', post)) {
10
+ // authorized
11
+ }
12
+ ```
8
13
 
9
14
  ---
10
15
 
@@ -12,10 +17,14 @@ This package provides a powerful authorization system with Gates and Policies, d
12
17
 
13
18
  - **Stage**: Experimental / v0.x
14
19
  - **Scope**:
15
- - Gate-based authorization
16
- - Policy-based authorization
17
- - Authorization middleware
18
- - Centralized permission logic
20
+ - Gate-based authorization with `before()` / `after()` hooks
21
+ - Policy-based authorization with auto-discovery
22
+ - Response-based authorization (custom deny messages)
23
+ - Role & Permission system
24
+ - Bulk ability checks (`any` / `every` / `none`)
25
+ - Request-scoped authorization context (concurrency safe)
26
+ - Authorization middleware with role/permission support
27
+ - Result caching per-request for performance
19
28
  - **Design**:
20
29
  - Framework-agnostic (usable outside HTTP layer)
21
30
  - Decoupled from transport layer
@@ -36,12 +45,14 @@ This package works on top of `@arikajs/auth` but remains fully decoupled from it
36
45
 
37
46
  ## 🚀 Features
38
47
 
39
- - **Gate-based authorization**: Simple closure-based checks.
40
- - **Policy-based authorization**: Organize logic per model/resource.
41
- - **Middleware integration**: Protect routes easily.
42
- - **Controller & service-level checks**: Call authorization logic from anywhere.
43
- - **Type-safe APIs**: Built for TypeScript.
44
- - **Framework-agnostic**: Can be used in CLI, WebSocket, or HTTP contexts.
48
+ - **Gate-based authorization** Simple closure-based checks with before/after hooks.
49
+ - **Policy-based authorization** Organize logic per model/resource with `before()` bypass.
50
+ - **Response-based authorization** Return custom deny messages instead of plain booleans.
51
+ - **Role & Permission system** First-class role and permission checking.
52
+ - **Bulk ability checks** — `Gate.any()`, `Gate.every()`, `Gate.none()` for multi-ability checks.
53
+ - **Request-scoped context** Memory-safe, per-request isolation with built-in result caching.
54
+ - **Middleware integration** — Protect routes with `can:`, `role:`, and `permission:` prefixes.
55
+ - **Type-safe APIs** — Built for TypeScript.
45
56
 
46
57
  ---
47
58
 
@@ -49,10 +60,6 @@ This package works on top of `@arikajs/auth` but remains fully decoupled from it
49
60
 
50
61
  ```bash
51
62
  npm install @arikajs/authorization
52
- # or
53
- yarn add @arikajs/authorization
54
- # or
55
- pnpm add @arikajs/authorization
56
63
  ```
57
64
 
58
65
  ---
@@ -74,13 +81,8 @@ Gate.define('edit-post', (user, post) => {
74
81
  **Usage:**
75
82
 
76
83
  ```ts
77
- if (Gate.allows('edit-post', post)) {
78
- // ...
79
- }
80
-
81
- if (Gate.denies('edit-post', post)) {
82
- // ...
83
- }
84
+ if (await Gate.forUser(user).allows('edit-post', post)) { ... }
85
+ if (await Gate.forUser(user).denies('edit-post', post)) { ... }
84
86
  ```
85
87
 
86
88
  ### 2️⃣ Policies
@@ -89,101 +91,204 @@ Policies organize authorization logic around a specific model or resource.
89
91
 
90
92
  ```ts
91
93
  class PostPolicy {
92
- view(user, post) {
94
+ // Super admin bypass — runs before any check
95
+ before(user: any, ability: string) {
96
+ if (user.isSuperAdmin) return true;
97
+ return null; // continue to actual check
98
+ }
99
+
100
+ view(user: any, post: Post) {
93
101
  return true;
94
102
  }
95
103
 
96
- update(user, post) {
104
+ update(user: any, post: Post) {
97
105
  return user.id === post.userId;
98
106
  }
107
+
108
+ delete(user: any, post: Post) {
109
+ if (user.id !== post.userId) {
110
+ return AuthResponse.deny('You do not own this post.', 'POST_NOT_OWNED');
111
+ }
112
+ return AuthResponse.allow();
113
+ }
99
114
  }
100
115
  ```
101
116
 
102
- **Register Policy:**
117
+ **Register & Use:**
103
118
 
104
119
  ```ts
105
120
  Gate.policy(Post, PostPolicy);
121
+
122
+ // Automatically resolves to PostPolicy.update
123
+ await Gate.forUser(user).allows('update', post);
106
124
  ```
107
125
 
108
- **Usage:**
126
+ ### 3️⃣ Authorization Context (Per-Request)
127
+
128
+ Each request should get its own `AuthorizationContext` for isolation and caching:
109
129
 
110
130
  ```ts
111
- // Automatically resolves to PostPolicy.update
112
- Gate.allows('update', post);
131
+ const ctx = new AuthorizationContext(user);
132
+
133
+ await ctx.can('edit-post', post); // true
134
+ await ctx.cannot('delete-post', post); // true
135
+ await ctx.authorize('edit-post', post); // throws if denied
113
136
  ```
114
137
 
115
- ### 3️⃣ Authorization Manager
138
+ ---
139
+
140
+ ## 🔑 Role & Permission System
116
141
 
117
- The central engine that evaluates permissions.
142
+ ArikaJS Authorization provides first-class role and permission checking:
118
143
 
119
144
  ```ts
120
- const authz = new AuthorizationManager(user);
121
-
122
- authz.can('edit-post', post);
123
- authz.cannot('delete-post', post);
145
+ import { RolePermissionMixin, AuthorizationContext } from '@arikajs/authorization';
146
+
147
+ // Direct usage
148
+ RolePermissionMixin.hasRole(user, 'admin'); // true/false
149
+ RolePermissionMixin.hasAnyRole(user, ['admin', 'editor']);
150
+ RolePermissionMixin.hasPermission(user, 'edit-posts');
151
+ RolePermissionMixin.hasAllPermissions(user, ['view-posts', 'edit-posts']);
152
+
153
+ // Via request-scoped context
154
+ const ctx = new AuthorizationContext(user);
155
+ ctx.hasRole('admin');
156
+ ctx.hasAnyPermission(['edit-posts', 'delete-posts']);
124
157
  ```
125
158
 
159
+ User objects should have `roles` and `permissions` arrays (strings or `{name: string}` objects).
160
+
126
161
  ---
127
162
 
128
- ## 🧩 Middleware Support
163
+ ## 🛡️ Before / After Hooks
129
164
 
130
- Protect routes using authorization middleware:
165
+ ### Global Before Hook (Super Admin Bypass)
131
166
 
132
167
  ```ts
133
- Route.get('/posts/:id/edit', controller)
134
- .middleware('can:edit-post');
168
+ Gate.before((user, ability) => {
169
+ if (user.isSuperAdmin) return true; // Bypasses ALL checks
170
+ return null; // Continue to actual check
171
+ });
135
172
  ```
136
173
 
137
- WITH arguments (e.g. Policies):
174
+ ### Global After Hook (Audit Logging)
138
175
 
139
176
  ```ts
140
- .middleware('can:update,post');
177
+ Gate.after((user, ability, result) => {
178
+ console.log(`${user.name} → ${ability}: ${result ? 'ALLOWED' : 'DENIED'}`);
179
+ });
141
180
  ```
142
181
 
143
- Middleware automatically:
144
- 1. Resolves the authenticated user.
145
- 2. Executes the authorization check.
146
- 3. Throws `403 Forbidden` on failure.
182
+ ### Policy-Level Before Hook
183
+
184
+ ```ts
185
+ class PostPolicy {
186
+ before(user, ability) {
187
+ if (user.isSuperAdmin) return true;
188
+ return null;
189
+ }
190
+ }
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 🔍 Bulk Ability Checks
196
+
197
+ Check multiple abilities in a single call:
198
+
199
+ ```ts
200
+ // Can the user do ANY of these?
201
+ await Gate.forUser(user).any(['edit-post', 'delete-post'], post);
202
+
203
+ // Can the user do ALL of these?
204
+ await Gate.forUser(user).every(['edit-post', 'publish-post'], post);
205
+
206
+ // Can the user do NONE of these?
207
+ await Gate.forUser(user).none(['admin-only', 'super-admin-only']);
208
+ ```
147
209
 
148
210
  ---
149
211
 
150
- ## 🧑💻 Controller Usage
212
+ ## 💬 Response-Based Authorization
213
+
214
+ Instead of returning plain `true`/`false`, return an `AuthResponse` with custom error messages:
151
215
 
152
216
  ```ts
153
- class PostController {
154
- update(request) {
155
- Gate.authorize('update', request.post);
217
+ import { AuthResponse } from '@arikajs/authorization';
156
218
 
157
- // Authorized logic proceeds here...
219
+ Gate.define('edit-post', (user, post) => {
220
+ if (user.id !== post.userId) {
221
+ return AuthResponse.deny('You do not own this post.', 'POST_NOT_OWNED');
158
222
  }
159
- }
223
+ return AuthResponse.allow();
224
+ });
225
+
226
+ // Inspect the full response
227
+ const response = await Gate.forUser(user).inspect('edit-post', post);
228
+ response.allowed(); // false
229
+ response.message(); // 'You do not own this post.'
230
+ response.code(); // 'POST_NOT_OWNED'
160
231
  ```
161
232
 
162
- If unauthorized, it throws an `AuthorizationException`, which is automatically handled by the HTTP layer.
233
+ ---
234
+
235
+ ## 🧩 Middleware Support
236
+
237
+ Protect routes using authorization middleware with multiple strategies:
238
+
239
+ ```ts
240
+ // Gate/Policy check
241
+ Route.get('/posts/:id/edit', controller).middleware('can:edit-post');
242
+
243
+ // Role-based
244
+ Route.get('/admin', controller).middleware('role:admin');
245
+ Route.get('/dashboard', controller).middleware('role:admin,editor');
246
+
247
+ // Permission-based
248
+ Route.get('/settings', controller).middleware('permission:manage-settings');
249
+ ```
250
+
251
+ Middleware automatically:
252
+ 1. Resolves the authenticated user.
253
+ 2. Executes the authorization check.
254
+ 3. Throws `403 Forbidden` on failure.
255
+
256
+ ---
257
+
258
+ ## ⚡ Performance
259
+
260
+ - **Per-request caching**: `AuthorizationContext` caches ability results within a single request, preventing redundant gate/policy evaluation.
261
+ - **Policy instance caching**: `PolicyResolver` caches instantiated policy objects instead of creating new ones on every call.
262
+ - **Short-circuit evaluation**: `before()` hooks return immediately without running the actual check.
263
+ - **Bulk checks early-exit**: `any()` returns on first `true`, `none()` returns on first `true`.
163
264
 
164
265
  ---
165
266
 
166
267
  ## 🏗 Architecture
167
268
 
168
- ```
269
+ ```text
169
270
  authorization/
170
271
  ├── src/
171
- │ ├── Gate.ts ← Define & evaluate abilities
172
- │ ├── AuthorizationManager.ts ← Core authorization engine
173
- │ ├── PolicyResolver.ts ← Maps models to policies
174
- │ ├── Middleware/
175
- │ │ └── Authorize.ts ← Route-level protection
176
- │ ├── Exceptions/
177
- │ │ └── AuthorizationException.ts
178
- │ ├── Contracts/
272
+ │ ├── Contracts
179
273
  │ │ └── Policy.ts
180
- └── index.ts
274
+ ├── Exceptions
275
+ │ │ └── AuthorizationException.ts
276
+ │ ├── Middleware
277
+ │ │ └── Authorize.ts
278
+ │ ├── AuthorizationContext.ts
279
+ │ ├── AuthorizationManager.ts
280
+ │ ├── AuthResponse.ts
281
+ │ ├── Gate.ts
282
+ │ ├── index.ts
283
+ │ ├── PolicyResolver.ts
284
+ │ └── RolePermission.ts
285
+ ├── tests/
181
286
  ├── package.json
182
287
  ├── tsconfig.json
183
- ├── README.md
184
- └── LICENSE
288
+ └── README.md
185
289
  ```
186
290
 
291
+
187
292
  ---
188
293
 
189
294
  ## 🔌 Integration with ArikaJS
@@ -197,25 +302,15 @@ authorization/
197
302
 
198
303
  ---
199
304
 
200
- ## 🧪 Error Handling
201
-
202
- Unauthorized access throws:
203
- - `AuthorizationException` (403)
204
-
205
- Handled automatically by:
206
- - HTTP kernel
207
- - Middleware pipeline
208
-
209
- ---
210
-
211
305
  ## 📌 Design Philosophy
212
306
 
213
307
  - **Explicit over implicit**: Authorization rules should be clear.
214
308
  - **Centralized rules**: Keep logic in Gates or Policies, not controllers.
215
- - **Readable permission names**: Use descriptive names like `edit-post`.
309
+ - **Rich feedback**: Return custom error messages, not just true/false.
310
+ - **Performance first**: Cache results, short-circuit, instance re-use.
216
311
  - **Decoupled from transport layer**: Logic works for HTTP, CLI, etc.
217
312
 
218
- > Authentication identifies the user. Authorization empowers or restricts them.”
313
+ > "Authentication identifies the user. Authorization empowers or restricts them."
219
314
 
220
315
  ---
221
316
 
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Represents the result of an authorization check.
3
+ * Allows gates/policies to return custom denial messages instead of just true/false.
4
+ */
5
+ export declare class AuthResponse {
6
+ private _allowed;
7
+ private _message;
8
+ private _code;
9
+ constructor(allowed: boolean, message?: string | null, code?: string | null);
10
+ allowed(): boolean;
11
+ denied(): boolean;
12
+ message(): string | null;
13
+ code(): string | null;
14
+ /**
15
+ * Create an "allow" response.
16
+ */
17
+ static allow(message?: string | null): AuthResponse;
18
+ /**
19
+ * Create a "deny" response with an optional custom message.
20
+ */
21
+ static deny(message?: string, code?: string | null): AuthResponse;
22
+ }
23
+ //# sourceMappingURL=AuthResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthResponse.d.ts","sourceRoot":"","sources":["../src/AuthResponse.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,KAAK,CAAgB;gBAEjB,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,GAAG,IAAW,EAAE,IAAI,GAAE,MAAM,GAAG,IAAW;IAMhF,OAAO,IAAI,OAAO;IAIlB,MAAM,IAAI,OAAO;IAIjB,OAAO,IAAI,MAAM,GAAG,IAAI;IAIxB,IAAI,IAAI,MAAM,GAAG,IAAI;IAI5B;;OAEG;WACW,KAAK,CAAC,OAAO,GAAE,MAAM,GAAG,IAAW,GAAG,YAAY;IAIhE;;OAEG;WACW,IAAI,CAAC,OAAO,GAAE,MAAuC,EAAE,IAAI,GAAE,MAAM,GAAG,IAAW,GAAG,YAAY;CAGjH"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthResponse = void 0;
4
+ /**
5
+ * Represents the result of an authorization check.
6
+ * Allows gates/policies to return custom denial messages instead of just true/false.
7
+ */
8
+ class AuthResponse {
9
+ constructor(allowed, message = null, code = null) {
10
+ this._allowed = allowed;
11
+ this._message = message;
12
+ this._code = code;
13
+ }
14
+ allowed() {
15
+ return this._allowed;
16
+ }
17
+ denied() {
18
+ return !this._allowed;
19
+ }
20
+ message() {
21
+ return this._message;
22
+ }
23
+ code() {
24
+ return this._code;
25
+ }
26
+ /**
27
+ * Create an "allow" response.
28
+ */
29
+ static allow(message = null) {
30
+ return new AuthResponse(true, message);
31
+ }
32
+ /**
33
+ * Create a "deny" response with an optional custom message.
34
+ */
35
+ static deny(message = 'This action is unauthorized.', code = null) {
36
+ return new AuthResponse(false, message, code);
37
+ }
38
+ }
39
+ exports.AuthResponse = AuthResponse;
40
+ //# sourceMappingURL=AuthResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthResponse.js","sourceRoot":"","sources":["../src/AuthResponse.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,YAAY;IAKrB,YAAY,OAAgB,EAAE,UAAyB,IAAI,EAAE,OAAsB,IAAI;QACnF,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACtB,CAAC;IAEM,OAAO;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAEM,MAAM;QACT,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;IAC1B,CAAC;IAEM,OAAO;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAEM,IAAI;QACP,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,KAAK,CAAC,UAAyB,IAAI;QAC7C,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,IAAI,CAAC,UAAkB,8BAA8B,EAAE,OAAsB,IAAI;QAC3F,OAAO,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;CACJ;AAxCD,oCAwCC"}
@@ -0,0 +1,31 @@
1
+ import { AuthResponse } from './AuthResponse';
2
+ /**
3
+ * Per-request authorization context.
4
+ * Avoids the global mutable `Gate.currentUser` problem under concurrency.
5
+ * Each request gets its own `AuthorizationContext` with an isolated user reference.
6
+ */
7
+ export declare class AuthorizationContext {
8
+ private user;
9
+ private cache;
10
+ constructor(user: any);
11
+ can(ability: string, ...args: any[]): Promise<boolean>;
12
+ cannot(ability: string, ...args: any[]): Promise<boolean>;
13
+ authorize(ability: string, ...args: any[]): Promise<void>;
14
+ inspect(ability: string, ...args: any[]): Promise<AuthResponse>;
15
+ any(abilities: string[], ...args: any[]): Promise<boolean>;
16
+ every(abilities: string[], ...args: any[]): Promise<boolean>;
17
+ none(abilities: string[], ...args: any[]): Promise<boolean>;
18
+ hasRole(role: string | string[]): boolean;
19
+ hasAnyRole(roles: string[]): boolean;
20
+ hasAllRoles(roles: string[]): boolean;
21
+ hasPermission(permission: string): boolean;
22
+ hasAnyPermission(permissions: string[]): boolean;
23
+ hasAllPermissions(permissions: string[]): boolean;
24
+ getUser(): any;
25
+ /**
26
+ * Clear the authorization cache (e.g. after role change mid-request).
27
+ */
28
+ flushCache(): void;
29
+ private buildCacheKey;
30
+ }
31
+ //# sourceMappingURL=AuthorizationContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthorizationContext.d.ts","sourceRoot":"","sources":["../src/AuthorizationContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C;;;;GAIG;AACH,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,KAAK,CAAmC;gBAEpC,IAAI,EAAE,GAAG;IAMR,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAM/D,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI5D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAMjE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO;IAIzC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAIpC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAIrC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO;IAIhD,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO;IAMjD,OAAO,IAAI,GAAG;IAIrB;;OAEG;IACI,UAAU,IAAI,IAAI;IAIzB,OAAO,CAAC,aAAa;CASxB"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthorizationContext = void 0;
4
+ const Gate_1 = require("./Gate");
5
+ const RolePermission_1 = require("./RolePermission");
6
+ /**
7
+ * Per-request authorization context.
8
+ * Avoids the global mutable `Gate.currentUser` problem under concurrency.
9
+ * Each request gets its own `AuthorizationContext` with an isolated user reference.
10
+ */
11
+ class AuthorizationContext {
12
+ constructor(user) {
13
+ this.cache = new Map();
14
+ this.user = user;
15
+ }
16
+ // ── Single ability checks ───────────────────────────────────────
17
+ async can(ability, ...args) {
18
+ const cacheKey = this.buildCacheKey(ability, args);
19
+ if (this.cache.has(cacheKey)) {
20
+ return this.cache.get(cacheKey);
21
+ }
22
+ const result = await Gate_1.Gate.forUser(this.user).allows(ability, ...args);
23
+ this.cache.set(cacheKey, result);
24
+ return result;
25
+ }
26
+ async cannot(ability, ...args) {
27
+ return !(await this.can(ability, ...args));
28
+ }
29
+ async authorize(ability, ...args) {
30
+ await Gate_1.Gate.forUser(this.user).authorize(ability, ...args);
31
+ }
32
+ async inspect(ability, ...args) {
33
+ return await Gate_1.Gate.forUser(this.user).inspect(ability, ...args);
34
+ }
35
+ // ── Bulk ability checks ─────────────────────────────────────────
36
+ async any(abilities, ...args) {
37
+ return await Gate_1.Gate.forUser(this.user).any(abilities, ...args);
38
+ }
39
+ async every(abilities, ...args) {
40
+ return await Gate_1.Gate.forUser(this.user).every(abilities, ...args);
41
+ }
42
+ async none(abilities, ...args) {
43
+ return await Gate_1.Gate.forUser(this.user).none(abilities, ...args);
44
+ }
45
+ // ── Role & Permission checks (via mixin) ────────────────────────
46
+ hasRole(role) {
47
+ return RolePermission_1.RolePermissionMixin.hasRole(this.user, role);
48
+ }
49
+ hasAnyRole(roles) {
50
+ return RolePermission_1.RolePermissionMixin.hasAnyRole(this.user, roles);
51
+ }
52
+ hasAllRoles(roles) {
53
+ return RolePermission_1.RolePermissionMixin.hasAllRoles(this.user, roles);
54
+ }
55
+ hasPermission(permission) {
56
+ return RolePermission_1.RolePermissionMixin.hasPermission(this.user, permission);
57
+ }
58
+ hasAnyPermission(permissions) {
59
+ return RolePermission_1.RolePermissionMixin.hasAnyPermission(this.user, permissions);
60
+ }
61
+ hasAllPermissions(permissions) {
62
+ return RolePermission_1.RolePermissionMixin.hasAllPermissions(this.user, permissions);
63
+ }
64
+ // ── Internals ───────────────────────────────────────────────────
65
+ getUser() {
66
+ return this.user;
67
+ }
68
+ /**
69
+ * Clear the authorization cache (e.g. after role change mid-request).
70
+ */
71
+ flushCache() {
72
+ this.cache.clear();
73
+ }
74
+ buildCacheKey(ability, args) {
75
+ // Build a simple cache key from ability + resource IDs
76
+ const argIds = args.map(a => {
77
+ if (a && typeof a === 'object' && a.id !== undefined)
78
+ return `${a.constructor?.name || 'obj'}:${a.id}`;
79
+ if (a && typeof a === 'object' && a.constructor)
80
+ return a.constructor.name;
81
+ return String(a);
82
+ });
83
+ return `${ability}:${argIds.join(',')}`;
84
+ }
85
+ }
86
+ exports.AuthorizationContext = AuthorizationContext;
87
+ //# sourceMappingURL=AuthorizationContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthorizationContext.js","sourceRoot":"","sources":["../src/AuthorizationContext.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,25 @@
1
+ import { AuthorizationContext } from './AuthorizationContext';
2
+ import { AuthResponse } from './AuthResponse';
1
3
  export declare class AuthorizationManager {
2
4
  private user;
5
+ private context;
3
6
  constructor(user: any);
4
7
  /**
5
- * Determine if the user can perform the given ability.
8
+ * Create a request-scoped authorization context and bind it to the request.
6
9
  */
10
+ static createContext(request: any): AuthorizationContext;
7
11
  can(ability: string, ...args: any[]): Promise<boolean>;
8
- /**
9
- * Determine if the user cannot perform the given ability.
10
- */
11
12
  cannot(ability: string, ...args: any[]): Promise<boolean>;
12
- /**
13
- * Authorize or throw exception.
14
- */
15
13
  authorize(ability: string, ...args: any[]): Promise<void>;
14
+ inspect(ability: string, ...args: any[]): Promise<AuthResponse>;
15
+ any(abilities: string[], ...args: any[]): Promise<boolean>;
16
+ every(abilities: string[], ...args: any[]): Promise<boolean>;
17
+ none(abilities: string[], ...args: any[]): Promise<boolean>;
18
+ hasRole(role: string | string[]): boolean;
19
+ hasAnyRole(roles: string[]): boolean;
20
+ hasAllRoles(roles: string[]): boolean;
21
+ hasPermission(permission: string): boolean;
22
+ hasAnyPermission(permissions: string[]): boolean;
23
+ hasAllPermissions(permissions: string[]): boolean;
16
24
  }
17
25
  //# sourceMappingURL=AuthorizationManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthorizationManager.d.ts","sourceRoot":"","sources":["../src/AuthorizationManager.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,IAAI,CAAM;gBAEN,IAAI,EAAE,GAAG;IAIrB;;OAEG;IACU,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnE;;OAEG;IACU,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE;;OAEG;IACU,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzE"}
1
+ {"version":3,"file":"AuthorizationManager.d.ts","sourceRoot":"","sources":["../src/AuthorizationManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,OAAO,CAAuB;gBAE1B,IAAI,EAAE,GAAG;IAKrB;;OAEG;WACW,aAAa,CAAC,OAAO,EAAE,GAAG,GAAG,oBAAoB;IAWlD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAI/D,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI5D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAMjE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO;IAIzC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAIpC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAIrC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO;IAIhD,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO;CAG3D"}