@venizia/ignis-docs 0.0.6-2 → 0.0.7-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.
Files changed (120) hide show
  1. package/README.md +125 -388
  2. package/dist/mcp-server/common/config.d.ts +0 -21
  3. package/dist/mcp-server/common/config.d.ts.map +1 -1
  4. package/dist/mcp-server/common/config.js +1 -36
  5. package/dist/mcp-server/common/config.js.map +1 -1
  6. package/dist/mcp-server/helpers/docs.helper.d.ts +0 -24
  7. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  8. package/dist/mcp-server/helpers/docs.helper.js +0 -25
  9. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  10. package/dist/mcp-server/helpers/github.helper.d.ts +0 -13
  11. package/dist/mcp-server/helpers/github.helper.d.ts.map +1 -1
  12. package/dist/mcp-server/helpers/github.helper.js +3 -20
  13. package/dist/mcp-server/helpers/github.helper.js.map +1 -1
  14. package/dist/mcp-server/index.js +1 -20
  15. package/dist/mcp-server/index.js.map +1 -1
  16. package/dist/mcp-server/tools/base.tool.d.ts +4 -85
  17. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/base.tool.js +1 -38
  19. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +8 -2
  21. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/docs/get-document-content.tool.js +1 -10
  23. package/dist/mcp-server/tools/docs/get-document-content.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts +13 -2
  25. package/dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +1 -10
  27. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js.map +1 -1
  28. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +16 -8
  29. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/docs/get-package-overview.tool.js +2 -25
  31. package/dist/mcp-server/tools/docs/get-package-overview.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/docs/list-categories.tool.d.ts +5 -2
  33. package/dist/mcp-server/tools/docs/list-categories.tool.d.ts.map +1 -1
  34. package/dist/mcp-server/tools/docs/list-categories.tool.js +1 -10
  35. package/dist/mcp-server/tools/docs/list-categories.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/docs/list-documents.tool.d.ts +11 -2
  37. package/dist/mcp-server/tools/docs/list-documents.tool.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/docs/list-documents.tool.js +1 -10
  39. package/dist/mcp-server/tools/docs/list-documents.tool.js.map +1 -1
  40. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +13 -2
  41. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts.map +1 -1
  42. package/dist/mcp-server/tools/docs/search-documents.tool.js +1 -10
  43. package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -1
  44. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +9 -2
  45. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/github/list-project-files.tool.js +1 -10
  47. package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -1
  48. package/dist/mcp-server/tools/github/search-code.tool.d.ts +16 -2
  49. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/github/search-code.tool.js +2 -14
  51. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  52. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts +19 -6
  53. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  54. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +2 -19
  55. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  56. package/dist/mcp-server/tools/github/view-source-file.tool.d.ts +8 -2
  57. package/dist/mcp-server/tools/github/view-source-file.tool.d.ts.map +1 -1
  58. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -10
  59. package/dist/mcp-server/tools/github/view-source-file.tool.js.map +1 -1
  60. package/dist/mcp-server/tools/index.d.ts.map +1 -1
  61. package/dist/mcp-server/tools/index.js +0 -2
  62. package/dist/mcp-server/tools/index.js.map +1 -1
  63. package/package.json +68 -54
  64. package/wiki/best-practices/api-usage-examples.md +7 -5
  65. package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
  66. package/wiki/best-practices/code-style-standards/constants-configuration.md +1 -1
  67. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  68. package/wiki/best-practices/code-style-standards/function-patterns.md +1 -1
  69. package/wiki/best-practices/common-pitfalls.md +1 -1
  70. package/wiki/best-practices/data-modeling.md +33 -1
  71. package/wiki/best-practices/error-handling.md +7 -4
  72. package/wiki/best-practices/performance-optimization.md +1 -1
  73. package/wiki/best-practices/security-guidelines.md +5 -4
  74. package/wiki/guides/core-concepts/components-guide.md +1 -1
  75. package/wiki/guides/core-concepts/controllers.md +14 -8
  76. package/wiki/guides/core-concepts/persistent/models.md +32 -0
  77. package/wiki/guides/core-concepts/services.md +2 -1
  78. package/wiki/guides/get-started/5-minute-quickstart.md +1 -1
  79. package/wiki/guides/reference/mcp-docs-server.md +0 -134
  80. package/wiki/guides/tutorials/building-a-crud-api.md +2 -1
  81. package/wiki/guides/tutorials/complete-installation.md +2 -2
  82. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  83. package/wiki/guides/tutorials/realtime-chat.md +7 -6
  84. package/wiki/index.md +2 -1
  85. package/wiki/references/base/components.md +2 -1
  86. package/wiki/references/base/controllers.md +19 -12
  87. package/wiki/references/base/middlewares.md +2 -1
  88. package/wiki/references/base/models.md +11 -2
  89. package/wiki/references/base/services.md +2 -1
  90. package/wiki/references/components/authentication/api.md +525 -205
  91. package/wiki/references/components/authentication/errors.md +502 -105
  92. package/wiki/references/components/authentication/index.md +388 -75
  93. package/wiki/references/components/authentication/usage.md +428 -266
  94. package/wiki/references/components/authorization/api.md +1213 -0
  95. package/wiki/references/components/authorization/errors.md +387 -0
  96. package/wiki/references/components/authorization/index.md +712 -0
  97. package/wiki/references/components/authorization/usage.md +758 -0
  98. package/wiki/references/components/health-check.md +2 -1
  99. package/wiki/references/components/index.md +2 -0
  100. package/wiki/references/components/socket-io/index.md +9 -4
  101. package/wiki/references/components/socket-io/usage.md +1 -1
  102. package/wiki/references/components/static-asset/index.md +3 -5
  103. package/wiki/references/components/swagger.md +2 -1
  104. package/wiki/references/configuration/environment-variables.md +2 -1
  105. package/wiki/references/configuration/index.md +2 -1
  106. package/wiki/references/helpers/error/index.md +1 -1
  107. package/wiki/references/helpers/index.md +1 -0
  108. package/wiki/references/helpers/inversion/index.md +1 -1
  109. package/wiki/references/helpers/kafka/index.md +305 -0
  110. package/wiki/references/helpers/redis/index.md +2 -9
  111. package/wiki/references/quick-reference.md +3 -5
  112. package/wiki/references/utilities/crypto.md +2 -2
  113. package/wiki/references/utilities/date.md +5 -5
  114. package/wiki/references/utilities/index.md +3 -11
  115. package/wiki/references/utilities/jsx.md +4 -2
  116. package/wiki/references/utilities/module.md +1 -1
  117. package/wiki/references/utilities/parse.md +4 -4
  118. package/wiki/references/utilities/performance.md +2 -2
  119. package/wiki/references/utilities/promise.md +4 -4
  120. package/wiki/references/utilities/request.md +2 -2
@@ -0,0 +1,712 @@
1
+ # Authorization -- Setup & Configuration
2
+
3
+ > Enforcer-based authorization with RBAC, voters, and Casbin integration
4
+
5
+ ## Quick Reference
6
+
7
+ | Item | Value |
8
+ |------|-------|
9
+ | **Package** | `@venizia/ignis` |
10
+ | **Class** | `AuthorizeComponent` |
11
+ | **Runtimes** | Both |
12
+
13
+ ### Key Components
14
+
15
+ | Component | Purpose |
16
+ |-----------|---------|
17
+ | **AuthorizeComponent** | Main component validating authorization options and binding global config |
18
+ | **AuthorizationEnforcerRegistry** | Singleton managing registered enforcers (mirrors `AuthenticationStrategyRegistry`) |
19
+ | **CasbinAuthorizationEnforcer** | Casbin-backed enforcer (optional `casbin` peer dep) |
20
+ | **AuthorizationProvider** | IProvider producing the `authorize()` middleware factory |
21
+ | **authorize** | Standalone function wrapping `AuthorizationProvider.value()` |
22
+ | **AuthorizationRole** | Value object for role identity with priority-based comparison |
23
+ | **BaseFilteredAdapter** | Abstract casbin `FilteredAdapter` with template method pattern for query hooks |
24
+ | **DrizzleCasbinAdapter** | Drizzle-based read-only `FilteredAdapter` using raw SQL queries |
25
+ | **StringAuthorizationAction** | `IAuthorizationComparable` implementation for string actions (includes `WILDCARD = '*'`) |
26
+ | **StringAuthorizationResource** | `IAuthorizationComparable` implementation for string resources |
27
+ | **AbstractAuthRegistry** | Shared base class for authentication strategy registry and authorization enforcer registry |
28
+
29
+ ### Authorization Flow (7 Steps)
30
+
31
+ ```mermaid
32
+ flowchart TD
33
+ R([Request]) --> S1{"1. SKIP_AUTHORIZATION?"}
34
+ S1 -->|Yes| Pass1([next])
35
+ S1 -->|No| S2{"2. User on context?"}
36
+ S2 -->|No| E401[/401/]
37
+ S2 -->|Yes| S3{"3. Role shortcuts match?"}
38
+ S3 -->|alwaysAllowRoles| Pass2([next])
39
+ S3 -->|allowedRoles| Pass3([next])
40
+ S3 -->|No match| S4{"4. Voters?"}
41
+ S4 -->|DENY| E403a[/403/]
42
+ S4 -->|ALLOW| Pass4([next])
43
+ S4 -->|ABSTAIN| S5["5. Resolve enforcer"]
44
+ S5 --> S6["6. Build/cache rules"]
45
+ S6 --> S7{"7. Evaluate"}
46
+ S7 -->|ALLOW| Pass5([next])
47
+ S7 -->|DENY/ABSTAIN| E403b[/403/]
48
+ ```
49
+
50
+ | Step | Action | Short-circuits? |
51
+ |------|--------|-----------------|
52
+ | 1 | Check `Authorization.SKIP_AUTHORIZATION` flag | Yes -- skip all |
53
+ | 2 | Get authenticated user from context | Yes -- 401 if missing |
54
+ | 3 | Check role-based shortcuts (`alwaysAllowRoles` + `allowedRoles`) | Yes -- allow if matched |
55
+ | 4 | Execute `voters` (per-route) | Yes -- DENY/ALLOW short-circuits |
56
+ | 5 | Resolve enforcer (by name or default) | No |
57
+ | 6 | Build or retrieve cached rules | No |
58
+ | 7 | Evaluate permission via enforcer | Yes -- 403 if denied |
59
+
60
+ > [!NOTE]
61
+ > Step 3 merges the global `alwaysAllowRoles` check and the per-route `allowedRoles` check into a single step. User roles are extracted once and checked against both lists.
62
+
63
+ ### Authorization Constants
64
+
65
+ | Constant | Value | Description |
66
+ |----------|-------|-------------|
67
+ | `Authorization.RULES` | `'authorization.rules'` | Context key for cached rules |
68
+ | `Authorization.SKIP_AUTHORIZATION` | `'authorization.skip'` | Context key to dynamically skip authorization |
69
+ | `Authorization.ENFORCER` | `'authorization.enforcer'` | Binding key prefix for enforcers |
70
+
71
+ ### Authorization Actions
72
+
73
+ | Constant | Value |
74
+ |----------|-------|
75
+ | `AuthorizationActions.CREATE` | `'create'` |
76
+ | `AuthorizationActions.READ` | `'read'` |
77
+ | `AuthorizationActions.UPDATE` | `'update'` |
78
+ | `AuthorizationActions.DELETE` | `'delete'` |
79
+ | `AuthorizationActions.EXECUTE` | `'execute'` |
80
+
81
+ `AuthorizationActions.SCHEME_SET` contains all valid actions. `AuthorizationActions.isValid(input)` checks membership.
82
+
83
+ ### Authorization Decisions
84
+
85
+ | Constant | Value | Description |
86
+ |----------|-------|-------------|
87
+ | `AuthorizationDecisions.ALLOW` | `'allow'` | Grant access |
88
+ | `AuthorizationDecisions.DENY` | `'deny'` | Deny access |
89
+ | `AuthorizationDecisions.ABSTAIN` | `'abstain'` | No opinion -- fall through to next check |
90
+
91
+ `AuthorizationDecisions` also provides comparison helpers that accept both strings and numbers:
92
+
93
+ | Method | String check | Number check |
94
+ |--------|-------------|--------------|
95
+ | `isAllow(input)` | `input.toLowerCase() === 'allow'` | `input > 0` |
96
+ | `isDeny(input)` | `input.toLowerCase() === 'deny'` | `input < 0` |
97
+ | `isAbstain(input)` | `input.toLowerCase() === 'abstain'` | `input === 0` |
98
+
99
+ `AuthorizationDecisions.SCHEME_SET` contains all valid decisions. `AuthorizationDecisions.isValid(input)` checks membership.
100
+
101
+ ### Authorization Enforcer Types
102
+
103
+ | Constant | Value | Description |
104
+ |----------|-------|-------------|
105
+ | `AuthorizationEnforcerTypes.CASBIN` | `'casbin'` | Casbin-backed enforcer |
106
+ | `AuthorizationEnforcerTypes.CUSTOM` | `'custom'` | Custom enforcer implementation |
107
+
108
+ `AuthorizationEnforcerTypes.SCHEME_SET` contains all valid types. `AuthorizationEnforcerTypes.isValid(input)` checks membership.
109
+
110
+ ### Casbin Enforcer Model Drivers
111
+
112
+ | Constant | Value | Description |
113
+ |----------|-------|-------------|
114
+ | `CasbinEnforcerModelDrivers.FILE` | `'file'` | Load model from `.conf` file path |
115
+ | `CasbinEnforcerModelDrivers.TEXT` | `'text'` | Load model from inline string |
116
+
117
+ `CasbinEnforcerModelDrivers.SCHEME_SET` contains all valid drivers. `CasbinEnforcerModelDrivers.isValid(input)` checks membership.
118
+
119
+ ### Casbin Enforcer Cached Drivers
120
+
121
+ | Constant | Value | Description |
122
+ |----------|-------|-------------|
123
+ | `CasbinEnforcerCachedDrivers.IN_MEMORY` | `'in-memory'` | In-memory cache with periodic invalidation |
124
+ | `CasbinEnforcerCachedDrivers.REDIS` | `'redis'` | Redis-backed cache with TTL |
125
+
126
+ `CasbinEnforcerCachedDrivers.SCHEME_SET` contains all valid drivers. `CasbinEnforcerCachedDrivers.isValid(input)` checks membership.
127
+
128
+ ### Casbin Rule Variants
129
+
130
+ | Constant | Value | Description |
131
+ |----------|-------|-------------|
132
+ | `CasbinRuleVariants.POLICY` | `'policy'` | Database variant column value for permission rules |
133
+ | `CasbinRuleVariants.GROUP` | `'group'` | Database variant column value for role assignments |
134
+ | `CasbinRuleVariants.P` | `'p'` | Casbin line prefix for policy rules |
135
+ | `CasbinRuleVariants.G` | `'g'` | Casbin line prefix for grouping rules |
136
+
137
+ `CasbinRuleVariants.SCHEME_SET` contains `POLICY` and `GROUP` (the DB variants). `CasbinRuleVariants.isValid(input)` checks membership against the DB variants.
138
+
139
+ > [!NOTE]
140
+ > All constant classes follow the same pattern: static readonly values + `SCHEME_SET: Set<string>` + `isValid(input): boolean`. Each class also has a companion type alias generated via `TConstValue<typeof ClassName>` (e.g., `TAuthorizationAction`, `TAuthorizationDecision`, `TCasbinRuleVariant`).
141
+
142
+ ### Built-in Roles
143
+
144
+ | Constant | Identifier | Priority | Description |
145
+ |----------|------------|----------|-------------|
146
+ | `AuthorizationRoles.SUPER_ADMIN` | `'999_super-admin'` | 999 | Highest privilege |
147
+ | `AuthorizationRoles.ADMIN` | `'900_admin'` | 900 | Administrator |
148
+ | `AuthorizationRoles.USER` | `'010_user'` | 10 | Regular user |
149
+ | `AuthorizationRoles.GUEST` | `'001_guest'` | 1 | Guest user |
150
+ | `AuthorizationRoles.UNKNOWN_USER` | `'000_unknown-user'` | 0 | Unauthenticated fallback |
151
+
152
+ Each built-in role is an `AuthorizationRole` instance. `AuthorizationRoles.SCHEME_SET` contains identifier strings. `AuthorizationRoles.isValid(input)` checks membership.
153
+
154
+ ### Import Paths
155
+
156
+ ```typescript
157
+ // Classes & functions
158
+ import {
159
+ // Component & middleware
160
+ AuthorizeComponent,
161
+ AuthorizationProvider,
162
+ authorize,
163
+
164
+ // Registry
165
+ AuthorizationEnforcerRegistry,
166
+
167
+ // Enforcers
168
+ CasbinAuthorizationEnforcer,
169
+
170
+ // Adapters
171
+ BaseFilteredAdapter,
172
+ DrizzleCasbinAdapter,
173
+
174
+ // Models
175
+ AuthorizationRole,
176
+ StringAuthorizationAction,
177
+ StringAuthorizationResource,
178
+
179
+ // Constants
180
+ Authorization,
181
+ AuthorizationActions,
182
+ AuthorizationDecisions,
183
+ AuthorizationRoles,
184
+ AuthorizationEnforcerTypes,
185
+ CasbinEnforcerModelDrivers,
186
+ CasbinEnforcerCachedDrivers,
187
+ CasbinRuleVariants,
188
+
189
+ // Binding keys
190
+ AuthorizeBindingKeys,
191
+ } from '@venizia/ignis';
192
+
193
+ // Types & interfaces
194
+ import type {
195
+ // Core interfaces
196
+ IAuthorizeOptions,
197
+ IAuthorizationEnforcer,
198
+ IAuthorizationSpec,
199
+ IAuthorizationRequest,
200
+ IAuthorizationRole,
201
+ IAuthorizationComparable,
202
+
203
+ // Casbin options
204
+ ICasbinEnforcerOptions,
205
+ ICasbinEnforcerCachedMemory,
206
+ ICasbinEnforcerCachedRedis,
207
+
208
+ // Adapter types
209
+ IBaseFilteredAdapterEntities,
210
+ ICasbinPolicyFilter,
211
+ TBasePolicyRow,
212
+ IDrizzleCasbinEntities,
213
+ IDrizzleCasbinAdapterOptions,
214
+
215
+ // Function & utility types
216
+ TAuthorizeFn,
217
+ TAuthorizationVoter,
218
+ TAuthorizationConditions,
219
+ TRegistryDescriptor,
220
+
221
+ // Value types (from TConstValue)
222
+ TAuthorizationAction,
223
+ TAuthorizationDecision,
224
+ TAuthorizationEnforcerType,
225
+ TCasbinEnforcerCachedDriver,
226
+ TCasbinEnforcerModelDriver,
227
+ TCasbinRuleVariant,
228
+ } from '@venizia/ignis';
229
+ ```
230
+
231
+ ## Setup
232
+
233
+ Authorization setup is a **three-step process**: bind global options, register the component, then register enforcers via the registry.
234
+
235
+ ```mermaid
236
+ flowchart LR
237
+ subgraph Step1["Step 1"]
238
+ A["bind IAuthorizeOptions<br/>to AuthorizeBindingKeys.OPTIONS"]
239
+ end
240
+ subgraph Step2["Step 2"]
241
+ B["this.component(AuthorizeComponent)<br/>validates options + binds alwaysAllowRoles"]
242
+ end
243
+ subgraph Step3["Step 3"]
244
+ C["AuthorizationEnforcerRegistry.register()<br/>class + name + type + options"]
245
+ end
246
+ A --> B --> C
247
+ ```
248
+
249
+ ### Step 1: Bind Global Options
250
+
251
+ Bind `IAuthorizeOptions` to configure global authorization behavior. This interface is minimal -- it only contains global settings, not enforcer-specific configuration.
252
+
253
+ ```typescript
254
+ import {
255
+ AuthorizeBindingKeys,
256
+ AuthorizationDecisions,
257
+ IAuthorizeOptions,
258
+ } from '@venizia/ignis';
259
+
260
+ this.bind<IAuthorizeOptions>({ key: AuthorizeBindingKeys.OPTIONS }).toValue({
261
+ defaultDecision: AuthorizationDecisions.DENY,
262
+ alwaysAllowRoles: ['999_super-admin'],
263
+ });
264
+ ```
265
+
266
+ ### Step 2: Register the Component
267
+
268
+ ```typescript
269
+ import { AuthorizeComponent } from '@venizia/ignis';
270
+
271
+ this.component(AuthorizeComponent);
272
+ ```
273
+
274
+ The component validates that `IAuthorizeOptions` is bound and extracts `alwaysAllowRoles` into a separate binding (`AuthorizeBindingKeys.ALWAYS_ALLOW_ROLES`) for downstream consumers.
275
+
276
+ ### Step 3: Register Enforcers via Registry
277
+
278
+ Enforcer registration is separate from global options. Each enforcer is registered with its **class**, **name**, **type**, and **options** -- all co-located in one call.
279
+
280
+ #### Casbin Enforcer (Recommended)
281
+
282
+ ```typescript
283
+ import {
284
+ AuthorizationEnforcerRegistry,
285
+ AuthorizationEnforcerTypes,
286
+ CasbinAuthorizationEnforcer,
287
+ CasbinEnforcerModelDrivers,
288
+ DrizzleCasbinAdapter,
289
+ } from '@venizia/ignis';
290
+ import path from 'node:path';
291
+
292
+ // Create adapter (Drizzle example)
293
+ const adapter = new DrizzleCasbinAdapter({
294
+ dataSource,
295
+ entities: {
296
+ permission: { tableName: 'Permission', principalType: 'Permission' },
297
+ role: { tableName: 'Role', principalType: 'Role' },
298
+ policyDefinition: { tableName: 'PolicyDefinition', principalType: 'PolicyDefinition' },
299
+ domain: { principalType: 'Organization' },
300
+ },
301
+ });
302
+
303
+ AuthorizationEnforcerRegistry.getInstance().register({
304
+ container: this,
305
+ enforcers: [{
306
+ enforcer: CasbinAuthorizationEnforcer,
307
+ name: 'casbin',
308
+ type: AuthorizationEnforcerTypes.CASBIN,
309
+ options: {
310
+ model: {
311
+ driver: CasbinEnforcerModelDrivers.FILE,
312
+ definition: path.resolve(__dirname, './security/rbac_with_domains_deny.conf'),
313
+ },
314
+ adapter,
315
+ cached: {
316
+ use: true,
317
+ driver: 'redis',
318
+ options: {
319
+ connection: redisHelper,
320
+ expiresIn: 5 * 60 * 1000, // 5 minutes
321
+ keyFn: ({ user }) => `authz:policies:${user.userId}`,
322
+ },
323
+ },
324
+ normalizePayloadFn: ({ user, action, resource }) => ({
325
+ subject: `user_${user.userId}`,
326
+ domain: `Organization_${user.organizationId}`,
327
+ resource,
328
+ action,
329
+ }),
330
+ },
331
+ }],
332
+ });
333
+ ```
334
+
335
+ #### Custom Enforcer
336
+
337
+ ```typescript
338
+ AuthorizationEnforcerRegistry.getInstance().register({
339
+ container: this,
340
+ enforcers: [{
341
+ enforcer: MyCustomEnforcer,
342
+ name: 'my-custom',
343
+ type: AuthorizationEnforcerTypes.CUSTOM,
344
+ options: { /* your enforcer-specific options */ },
345
+ }],
346
+ });
347
+ ```
348
+
349
+ ### Full Setup Example
350
+
351
+ ```typescript
352
+ import {
353
+ AuthorizeComponent,
354
+ AuthorizeBindingKeys,
355
+ AuthorizationEnforcerRegistry,
356
+ AuthorizationEnforcerTypes,
357
+ CasbinAuthorizationEnforcer,
358
+ CasbinEnforcerModelDrivers,
359
+ DrizzleCasbinAdapter,
360
+ BaseApplication,
361
+ IAuthorizeOptions,
362
+ } from '@venizia/ignis';
363
+
364
+ export class Application extends BaseApplication {
365
+ async registerAuthorization() {
366
+ // Step 1: Global options
367
+ this.bind<IAuthorizeOptions>({ key: AuthorizeBindingKeys.OPTIONS }).toValue({
368
+ defaultDecision: 'deny',
369
+ alwaysAllowRoles: ['999_super-admin'],
370
+ });
371
+
372
+ // Step 2: Register component
373
+ this.component(AuthorizeComponent);
374
+
375
+ // Step 3: Register enforcer(s) with co-located options
376
+ const adapter = new DrizzleCasbinAdapter({ dataSource, entities: { ... } });
377
+
378
+ AuthorizationEnforcerRegistry.getInstance().register({
379
+ container: this,
380
+ enforcers: [{
381
+ enforcer: CasbinAuthorizationEnforcer,
382
+ name: 'casbin',
383
+ type: AuthorizationEnforcerTypes.CASBIN,
384
+ options: {
385
+ model: {
386
+ driver: CasbinEnforcerModelDrivers.FILE,
387
+ definition: path.resolve(__dirname, './security/rbac_model.conf'),
388
+ },
389
+ adapter,
390
+ cached: { use: false },
391
+ },
392
+ }],
393
+ });
394
+ }
395
+ }
396
+ ```
397
+
398
+ > [!IMPORTANT]
399
+ > Authorization depends on authentication. Register `AuthenticateComponent` **before** `AuthorizeComponent` so that `Authentication.CURRENT_USER` is populated before authorization checks run.
400
+
401
+ > [!NOTE]
402
+ > Enforcer-specific options (model, adapter, cached, normalizePayloadFn) are co-located with the enforcer registration in `AuthorizationEnforcerRegistry.register()`, not inside `IAuthorizeOptions`. This keeps enforcer configuration next to the enforcer class.
403
+
404
+ ## Configuration
405
+
406
+ ### IAuthorizeOptions
407
+
408
+ Global authorization settings. Bound to the container before registering `AuthorizeComponent`.
409
+
410
+ | Option | Type | Default | Description |
411
+ |--------|------|---------|-------------|
412
+ | `defaultDecision` | `TAuthorizationDecision` | -- | **Required.** Decision when enforcer returns `ABSTAIN` (`'allow'`, `'deny'`, or `'abstain'`) |
413
+ | `alwaysAllowRoles` | `string[]` | `[]` | Roles that bypass all authorization checks (global) |
414
+
415
+ ```typescript
416
+ interface IAuthorizeOptions {
417
+ defaultDecision: TAuthorizationDecision;
418
+ alwaysAllowRoles?: string[];
419
+ }
420
+ ```
421
+
422
+ ### ICasbinEnforcerOptions
423
+
424
+ Casbin-specific options, provided per-enforcer via `AuthorizationEnforcerRegistry.register()`.
425
+
426
+ | Option | Type | Default | Description |
427
+ |--------|------|---------|-------------|
428
+ | `model` | `{ driver, definition }` | -- | **Required.** Casbin model definition (file path or inline text) |
429
+ | `cached` | `{ use: false } \| { use: true, driver, options }` | -- | **Required.** Caching configuration |
430
+ | `adapter` | `Adapter` | -- | Casbin adapter instance (e.g., `DrizzleCasbinAdapter`) |
431
+ | `normalizePayloadFn` | `(opts) => { subject, resource, action, domain? }` | -- | Normalize subject/resource/action before evaluation |
432
+
433
+ ```typescript
434
+ interface ICasbinEnforcerOptions<
435
+ E extends Env = Env,
436
+ TAction = string,
437
+ TResource = string,
438
+ TAdapter = Adapter,
439
+ > {
440
+ model:
441
+ | { driver: 'file'; definition: string }
442
+ | { driver: 'text'; definition: string };
443
+
444
+ cached:
445
+ | { use: false }
446
+ | { use: true; driver: 'in-memory'; options: { expiresIn: number } }
447
+ | { use: true; driver: 'redis'; options: {
448
+ connection: DefaultRedisHelper;
449
+ expiresIn: number;
450
+ keyFn: (opts: { user: { principalType: string } & IAuthUser }) => ValueOrPromise<string>;
451
+ } };
452
+
453
+ adapter?: TAdapter;
454
+
455
+ normalizePayloadFn?(opts: {
456
+ user: IAuthUser;
457
+ action: TAction;
458
+ resource: TResource;
459
+ context: TContext<E, string>;
460
+ }): {
461
+ subject: string;
462
+ resource: string;
463
+ action: string;
464
+ domain?: string;
465
+ };
466
+ }
467
+ ```
468
+
469
+ > [!NOTE]
470
+ > `cached.options.expiresIn` must be >= 10,000 ms (10 seconds). Values below this threshold cause a validation error (`MIN_EXPIRES_IN = 10_000`).
471
+
472
+ #### Cache Configuration Types
473
+
474
+ The `cached` field is a discriminated union:
475
+
476
+ ```typescript
477
+ // No caching
478
+ interface { use: false }
479
+
480
+ // In-memory cache (CachedEnforcer with periodic invalidation timer)
481
+ interface ICasbinEnforcerCachedMemory {
482
+ driver: 'in-memory';
483
+ options: { expiresIn: number };
484
+ }
485
+
486
+ // Redis cache (store/retrieve policy lines from Redis)
487
+ interface ICasbinEnforcerCachedRedis {
488
+ driver: 'redis';
489
+ options: {
490
+ connection: DefaultRedisHelper;
491
+ expiresIn: number;
492
+ keyFn: (opts: { user: { principalType: string } & IAuthUser }) => ValueOrPromise<string>;
493
+ };
494
+ }
495
+ ```
496
+
497
+ ### IAuthorizationSpec (Route-level)
498
+
499
+ | Option | Type | Default | Description |
500
+ |--------|------|---------|-------------|
501
+ | `action` | `TAction` | -- | **Required.** Action being performed (e.g., `'read'`, `'create'`) |
502
+ | `resource` | `TResource` | -- | **Required.** Resource being accessed (e.g., `'Article'`, `'User'`) |
503
+ | `conditions` | `TAuthorizationConditions` | -- | Key-value conditions for ABAC (strict equality) |
504
+ | `allowedRoles` | `string[]` | -- | Roles that bypass enforcer for this specific route |
505
+ | `voters` | `TAuthorizationVoter[]` | -- | Custom voter functions for this specific route |
506
+
507
+ ```typescript
508
+ interface IAuthorizationSpec<E extends Env = Env, TAction = string, TResource = string> {
509
+ action: TAction;
510
+ resource: TResource;
511
+ conditions?: TAuthorizationConditions;
512
+ allowedRoles?: string[];
513
+ voters?: TAuthorizationVoter<E, TAction, TResource>[];
514
+ }
515
+ ```
516
+
517
+ > [!NOTE]
518
+ > `TAction` and `TResource` default to `string` but can accept `IAuthorizationComparable` implementations (e.g., `StringAuthorizationAction`) for custom comparison logic.
519
+
520
+ ### TAuthorizationConditions
521
+
522
+ Key-value conditions for attribute-based access control. Values are compared with strict equality (`===`).
523
+
524
+ ```typescript
525
+ type TAuthorizationConditions<
526
+ KeyType extends string | symbol = string | symbol,
527
+ ValueType = string | number | boolean | null,
528
+ > = Record<KeyType, ValueType>;
529
+ ```
530
+
531
+ ### TAuthorizationVoter
532
+
533
+ Function type for voter callbacks:
534
+
535
+ ```typescript
536
+ type TAuthorizationVoter<
537
+ E extends Env = Env,
538
+ TAction = string,
539
+ TResource = string,
540
+ > = (opts: {
541
+ user: IAuthUser;
542
+ action: TAction;
543
+ resource: TResource;
544
+ context: TContext<E, string>;
545
+ }) => ValueOrPromise<TAuthorizationDecision>;
546
+ ```
547
+
548
+ ### TAuthorizeFn
549
+
550
+ Function type for the `authorize()` middleware factory:
551
+
552
+ ```typescript
553
+ type TAuthorizeFn<E extends Env = Env, TAction = string, TResource = string> = (opts: {
554
+ spec: IAuthorizationSpec<E, TAction, TResource>;
555
+ enforcerName?: string;
556
+ }) => MiddlewareHandler;
557
+ ```
558
+
559
+ ## Binding Keys
560
+
561
+ | Key | Constant | Type | Description |
562
+ |-----|----------|------|-------------|
563
+ | `@app/authorize/options` | `AuthorizeBindingKeys.OPTIONS` | `IAuthorizeOptions` | Global authorization options |
564
+ | `@app/authorize/always-allow-roles` | `AuthorizeBindingKeys.ALWAYS_ALLOW_ROLES` | `string[]` | Auto-bound by component if present in options |
565
+ | `@app/authorize/enforcers/{name}/options` | `AuthorizeBindingKeys.enforcerOptions(name)` | `ICasbinEnforcerOptions \| unknown` | Per-enforcer options, auto-bound by registry |
566
+
567
+ ```typescript
568
+ class AuthorizeBindingKeys {
569
+ static readonly OPTIONS = '@app/authorize/options';
570
+ static readonly ALWAYS_ALLOW_ROLES = '@app/authorize/always-allow-roles';
571
+
572
+ static enforcerOptions(name: string): string {
573
+ return `@app/authorize/enforcers/${name}/options`;
574
+ }
575
+ }
576
+ ```
577
+
578
+ > [!NOTE]
579
+ > `AuthorizeBindingKeys.enforcerOptions(name)` is called automatically by `AuthorizationEnforcerRegistry.register()` when `options` is provided. The `CasbinAuthorizationEnforcer` injects its options from `AuthorizeBindingKeys.enforcerOptions('casbin')`.
580
+
581
+ ## Context Variables
582
+
583
+ The authorization module extends Hono's `ContextVariableMap` for type-safe context access. The full augmentation is defined in `auth/context-variables.ts` and covers both authentication and authorization:
584
+
585
+ ```typescript
586
+ declare module 'hono' {
587
+ interface ContextVariableMap {
588
+ // Authentication
589
+ [Authentication.CURRENT_USER]: IAuthUser;
590
+ [Authentication.AUDIT_USER_ID]: IdType;
591
+ [Authentication.SKIP_AUTHENTICATION]: boolean;
592
+
593
+ // Authorization
594
+ [Authorization.RULES]: unknown;
595
+ [Authorization.SKIP_AUTHORIZATION]: boolean;
596
+ }
597
+ }
598
+ ```
599
+
600
+ ### Authorization-Specific Variables
601
+
602
+ | Key | Constant | Type | Description |
603
+ |-----|----------|------|-------------|
604
+ | `'authorization.rules'` | `Authorization.RULES` | `unknown` | Cached rules built by the enforcer. Type depends on enforcer implementation. |
605
+ | `'authorization.skip'` | `Authorization.SKIP_AUTHORIZATION` | `boolean` | Set to `true` to dynamically skip authorization for this request. |
606
+
607
+ ### Authentication Variables Used by Authorization
608
+
609
+ | Key | Constant | Type | Used for |
610
+ |-----|----------|------|----------|
611
+ | `'authentication.currentUser'` | `Authentication.CURRENT_USER` | `IAuthUser` | Read in step 2 to get authenticated user |
612
+ | `'authentication.auditUserId'` | `Authentication.AUDIT_USER_ID` | `IdType` | Available for audit logging |
613
+
614
+ ### IAuthUser Interface
615
+
616
+ The `IAuthUser` interface (from the authenticate module) is the user object available during authorization:
617
+
618
+ ```typescript
619
+ interface IAuthUser {
620
+ userId: IdType; // IdType = number | string | bigint
621
+ [extra: string | symbol]: any;
622
+ }
623
+ ```
624
+
625
+ The authorization middleware accesses `user.roles` (for role extraction) and `user.principalType` (for enforcer-based evaluation) via the index signature.
626
+
627
+ ### IJWTTokenPayload Interface
628
+
629
+ When using JWT authentication, the full token payload extends `IAuthUser`:
630
+
631
+ ```typescript
632
+ interface IJWTTokenPayload extends JWTPayload, IAuthUser {
633
+ userId: IdType;
634
+ roles: { id: IdType; identifier: string; priority: number }[];
635
+ clientId?: string;
636
+ provider?: string;
637
+ email?: string;
638
+ name?: string;
639
+ [extra: string | symbol]: any;
640
+ }
641
+ ```
642
+
643
+ > [!NOTE]
644
+ > `IdType = number | string | bigint` is defined in `@/base/models/common/types`.
645
+
646
+ ## Relationship with Authentication
647
+
648
+ Authorization runs **after** authentication in the middleware chain. The `AbstractController.getRouteConfigs()` method ensures the correct ordering:
649
+
650
+ ```mermaid
651
+ flowchart LR
652
+ Req([Request]) --> Auth["1. authenticate()<br/>JWT / Basic"]
653
+ Auth --> Authz["2. authorize()<br/>enforcer-based"]
654
+ Authz --> Custom["3. Custom middleware"]
655
+ Custom --> Handler([Route Handler])
656
+ ```
657
+
658
+ 1. Authentication middleware is injected first (from `authenticate` config)
659
+ 2. Authorization middleware is injected second (from `authorize` config)
660
+ 3. Custom middleware is injected last (from `middleware` config)
661
+
662
+ This means `Authentication.CURRENT_USER` is always available when the authorization middleware executes.
663
+
664
+ ### Per-Route Configuration in CRUD Factory
665
+
666
+ CRUD factory routes support both authentication and authorization configuration:
667
+
668
+ ```typescript
669
+ ControllerFactory.defineCrudController({
670
+ entity: Article,
671
+ repository: { name: 'ArticleRepository' },
672
+ controller: {
673
+ name: 'ArticleController',
674
+ basePath: '/articles',
675
+ },
676
+ authenticate: { strategies: [Authentication.STRATEGY_JWT], mode: AuthenticationModes.ANY },
677
+ authorize: { action: AuthorizationActions.READ, resource: 'Article' },
678
+ routes: {
679
+ // Skip both auth for public read
680
+ find: { authenticate: { skip: true } },
681
+ // Override authorization for delete
682
+ deleteById: {
683
+ authorize: { action: AuthorizationActions.DELETE, resource: 'Article' },
684
+ },
685
+ // Skip only authorization
686
+ count: { authorize: { skip: true } },
687
+ },
688
+ });
689
+ ```
690
+
691
+ **Priority rules:**
692
+ 1. `authenticate: { skip: true }` -- skips both authentication AND authorization
693
+ 2. `authorize: { skip: true }` -- skips only authorization (authentication still runs)
694
+ 3. Per-route `authorize` overrides controller-level `authorize`
695
+ 4. No per-route config -- inherits controller-level config
696
+
697
+ ## See Also
698
+
699
+ - [Usage & Examples](./usage) -- Securing routes, voters, patterns, and CRUD integration
700
+ - [API Reference](./api) -- Architecture, enforcer internals, provider, registry, and adapters
701
+ - [Error Reference](./errors) -- Error messages and troubleshooting
702
+
703
+ - **Related Components:**
704
+ - [Authentication](../authentication/) -- Authentication system (runs before authorization)
705
+ - [All Components](../index) -- Built-in components list
706
+
707
+ - **References:**
708
+ - [Controllers](/references/base/controllers) -- Route configuration with auth
709
+ - [Middlewares](/references/base/middlewares) -- Custom middleware integration
710
+
711
+ - **Best Practices:**
712
+ - [Security Guidelines](/best-practices/security-guidelines) -- Authorization best practices