@venizia/ignis-docs 0.2.0 → 0.2.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -1,437 +1,49 @@
1
- # Authorization -- Setup & Configuration
1
+ ---
2
+ title: Authorization
3
+ description: Enforcer-based RBAC/ABAC authorization with Casbin, a multi-tenant domain-scoped model, voters, and role shortcuts
4
+ difficulty: advanced
5
+ ---
2
6
 
3
- > Enforcer-based authorization with RBAC, voters, and Casbin integration
7
+ # Authorization
4
8
 
5
- ## Quick Reference
9
+ `AuthorizeComponent` decides whether an authenticated request is allowed to proceed. It evaluates role shortcuts, custom voters, and a Casbin RBAC enforcer, in that order, through the `authorize()` middleware. The middleware runs after authentication. Casbin's optional domain-scoped model adds multi-tenant grants on top.
6
10
 
7
- | Item | Value |
8
- |------|-------|
9
- | **Package** | `@venizia/ignis` |
10
- | **Class** | `AuthorizeComponent` |
11
- | **Runtimes** | Both |
11
+ > [!TIP]
12
+ > New to this component? Start with [Getting Started](./getting-started) - it builds the mental model (a graph of edges, not a rule table), then seeds one grant and protects one route end to end before you read the reference material below.
12
13
 
13
- ### Key Components
14
+ ## In one example
14
15
 
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** | Thin abstract casbin `FilteredAdapter` (datasource plumbing + `loadLines`); subclasses implement only `loadFilteredPolicy` |
24
- | **ScopedCasbinAdapter** | Generic read-only `FilteredAdapter` for the scoped RBAC model - reads one principal's edges + the shared hierarchy from a single `PolicyDefinition` table |
25
- | **AbstractAuthRegistry** | Shared base class for authentication strategy registry and authorization enforcer registry |
26
-
27
- ### Authorization Flow (7 Steps)
28
-
29
- ```mermaid
30
- flowchart TD
31
- R([Request]) --> S1{"1. SKIP_AUTHORIZATION?"}
32
- S1 -->|Yes| Pass1([next])
33
- S1 -->|No| S2{"2. User on context?"}
34
- S2 -->|No| E401[/401/]
35
- S2 -->|Yes| S3{"3. Role shortcuts match?"}
36
- S3 -->|alwaysAllowRoles| Pass2([next])
37
- S3 -->|allowedRoles| Pass3([next])
38
- S3 -->|No match| S4{"4. Voters?"}
39
- S4 -->|DENY| E403a[/403/]
40
- S4 -->|ALLOW| Pass4([next])
41
- S4 -->|ABSTAIN / none| S5{"5. Enforcers registered?"}
42
- S5 -->|No enforcers| Pass5([next - skip])
43
- S5 -->|Yes| S5b["Resolve enforcer"]
44
- S5b --> S6["6. Build/cache rules"]
45
- S6 --> S7{"7. Evaluate"}
46
- S7 -->|ALLOW| Pass6([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 | Check if enforcers are registered; resolve enforcer (by name or default) | Yes -- skip all if no enforcers registered |
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
- > [!NOTE]
64
- > Step 5 has a safety fallback: if no enforcers are registered in the `AuthorizationEnforcerRegistry`, the middleware skips authorization entirely and calls `next()`. This prevents hard failures when authorization is configured on routes but no enforcer has been registered yet.
65
-
66
- ### Authorization Constants
67
-
68
- | Constant | Value | Description |
69
- |----------|-------|-------------|
70
- | `Authorization.RULES` | `'authorization.rules'` | Context key for cached rules |
71
- | `Authorization.SKIP_AUTHORIZATION` | `'authorization.skip'` | Context key to dynamically skip authorization |
72
- | `Authorization.ENFORCER` | `'authorization.enforcer'` | Binding key prefix for enforcers |
73
- | `Authorization.DOMAIN` | `'authorization.domain'` | Context key for the resolved request domain scope (set by the provider when domain scoping is in play) |
74
-
75
- ### Authorization Actions
76
-
77
- | Constant | Value |
78
- |----------|-------|
79
- | `AuthorizationActions.CREATE` | `'create'` |
80
- | `AuthorizationActions.READ` | `'read'` |
81
- | `AuthorizationActions.UPDATE` | `'update'` |
82
- | `AuthorizationActions.DELETE` | `'delete'` |
83
- | `AuthorizationActions.EXECUTE` | `'execute'` |
84
-
85
- `AuthorizationActions.SCHEME_SET` contains all valid actions. `AuthorizationActions.isValid(input)` checks membership.
86
-
87
- ### Authorization Decisions
88
-
89
- | Constant | Value | Description |
90
- |----------|-------|-------------|
91
- | `AuthorizationDecisions.ALLOW` | `'allow'` | Grant access |
92
- | `AuthorizationDecisions.DENY` | `'deny'` | Deny access |
93
- | `AuthorizationDecisions.ABSTAIN` | `'abstain'` | No opinion -- fall through to next check |
94
-
95
- `AuthorizationDecisions` also provides comparison helpers that accept both strings and numbers:
96
-
97
- | Method | String check | Number check |
98
- |--------|-------------|--------------|
99
- | `isAllow(input)` | `input.toLowerCase() === 'allow'` | `input > 0` |
100
- | `isDeny(input)` | `input.toLowerCase() === 'deny'` | `input < 0` |
101
- | `isAbstain(input)` | `input.toLowerCase() === 'abstain'` | `input === 0` |
102
-
103
- `AuthorizationDecisions.SCHEME_SET` contains all valid decisions. `AuthorizationDecisions.isValid(input)` checks membership.
104
-
105
- ### Authorization Enforcer Types
106
-
107
- | Constant | Value | Description |
108
- |----------|-------|-------------|
109
- | `AuthorizationEnforcerTypes.CASBIN` | `'casbin'` | Casbin-backed enforcer |
110
- | `AuthorizationEnforcerTypes.CUSTOM` | `'custom'` | Custom enforcer implementation |
111
-
112
- `AuthorizationEnforcerTypes.SCHEME_SET` contains all valid types. `AuthorizationEnforcerTypes.isValid(input)` checks membership.
113
-
114
- ### Casbin Enforcer Model Drivers
115
-
116
- | Constant | Value | Description |
117
- |----------|-------|-------------|
118
- | `CasbinEnforcerModelDrivers.FILE` | `'file'` | Load model from `.conf` file path |
119
- | `CasbinEnforcerModelDrivers.TEXT` | `'text'` | Load model from inline string |
120
-
121
- `CasbinEnforcerModelDrivers.SCHEME_SET` contains all valid drivers. `CasbinEnforcerModelDrivers.isValid(input)` checks membership.
122
-
123
- ### Casbin Enforcer Cached Drivers
124
-
125
- | Constant | Value | Description |
126
- |----------|-------|-------------|
127
- | `CasbinEnforcerCachedDrivers.REDIS` | `'redis'` | Redis-backed per-user line cache with TTL (the only cache driver) |
128
-
129
- `CasbinEnforcerCachedDrivers.SCHEME_SET` contains all valid drivers. `CasbinEnforcerCachedDrivers.isValid(input)` checks membership.
130
-
131
- > The in-memory cache driver was removed. Caching is **Redis-only**: set `cached` to
132
- > `{ use: true, driver: 'redis', options: { connection, expiresIn, keyFn } }`, or `{ use: false }`
133
- > to disable caching (every request rebuilds the user's policy from the datasource).
134
-
135
- ### Casbin Domain Matching Functions
136
-
137
- Built-in Casbin matching functions selectable for `ICasbinEnforcerOptions.domainMatching.fn`. Each value maps 1:1 to a Casbin `Util.*Func` export and is applied to the **domain slot** of a role definition (e.g. `g`).
138
-
139
- | Constant | Value | Description |
140
- |----------|-------|-------------|
141
- | `CasbinDomainMatchingFunctions.KEY_MATCH` | `'keyMatch'` | `*` is the only wildcard; exact compare otherwise. Recommended for `Merchant_<uuid>`-style domains |
142
- | `CasbinDomainMatchingFunctions.KEY_MATCH_2` | `'keyMatch2'` | Adds URL-path `:param` segment matching |
143
- | `CasbinDomainMatchingFunctions.KEY_MATCH_3` | `'keyMatch3'` | Adds `{param}` segment matching |
144
- | `CasbinDomainMatchingFunctions.KEY_MATCH_4` | `'keyMatch4'` | `{param}` with repeated-name equality checks |
145
- | `CasbinDomainMatchingFunctions.REGEX_MATCH` | `'regexMatch'` | Treats the stored/policy value as a full regular expression |
146
-
147
- `CasbinDomainMatchingFunctions.SCHEME_SET` contains all valid values. `CasbinDomainMatchingFunctions.isValid(input)` checks membership. Companion type: `TCasbinDomainMatchingFunction`.
148
-
149
- > [!IMPORTANT]
150
- > The function is applied as `fn(requestDomain, policyDomain)` - the wildcard must live on the **stored/policy** side. With `keyMatch`: `keyMatch("Merchant_X", "*") === true`, `keyMatch("Merchant_X", "Merchant_X") === true`, `keyMatch("Merchant_X", "Merchant_Y") === false`. Store only `*` or exact domain values (never partial patterns like `Merchant_*`) to keep tenant isolation guaranteed.
151
-
152
- ### Casbin Rule Variants
153
-
154
- `CasbinRuleVariants` holds **only the Casbin line prefixes** declared by the model, numbered in
155
- request-tuple order (`sub → dom → obj → act`):
156
-
157
- | Constant | Value | Relation |
158
- |----------|-------|----------|
159
- | `CasbinRuleVariants.P` | `'p'` | Permission policy line |
160
- | `CasbinRuleVariants.G` | `'g'` | Role membership + role inheritance (the `sub` axis) |
161
- | `CasbinRuleVariants.G2` | `'g2'` | User→domain membership (the `dom` axis) |
162
- | `CasbinRuleVariants.G3` | `'g3'` | Domain hierarchy (the `dom` axis) |
163
- | `CasbinRuleVariants.G4` | `'g4'` | Resource hierarchy (the `obj` axis, via `objectMatch`) |
164
- | `CasbinRuleVariants.G5` | `'g5'` | Action hierarchy (the `act` axis) |
165
-
166
- ### Authorization Policy Variants
167
-
168
- The DB `variant` discriminator (the kind of "edge" stored in `PolicyDefinition`) lives on
169
- `AuthorizationPolicyVariants`. Each entry carries `action` (the DB value) and `rule` (the Casbin prefix
170
- the adapter emits for that edge):
171
-
172
- | Variant | `action` (DB) | `rule` | Meaning |
173
- |---------|---------------|--------|---------|
174
- | `GRANT` | `'grant'` | `p` | Give a permission to a User or Role |
175
- | `ASSIGN_ROLE` | `'assign_role'` | `g` | Give a User a Role (optionally domain-scoped) |
176
- | `ROLE_INHERITS` | `'role_inherits'` | `g` | Role inherits another Role |
177
- | `JOIN_DOMAIN` | `'join_domain'` | `g2` | User is a member of a Domain |
178
- | `DOMAIN_INHERITS` | `'domain_inherits'` | `g3` | Domain nested under a parent Domain |
179
- | `RESOURCE_INHERITS` | `'resource_inherits'` | `g4` | Resource nested under a broader Resource |
180
- | `ACTION_INHERITS` | `'action_inherits'` | `g5` | Action implied by a broader Action |
181
-
182
- `AuthorizationPolicyVariants.isValidAction(input)` / `isValidRule(input)` check membership;
183
- `ACTION_SCHEME_SET` / `RULE_SCHEME_SET` hold the sets.
184
-
185
- ### Authorization Domain Scopes
186
-
187
- Sentinel domain values used on `grant` rows:
188
-
189
- | Constant | Value | Meaning |
190
- |----------|-------|---------|
191
- | `AuthorizationDomainScopes.ANY_MEMBER` | `'ANY_MEMBER'` | Applies in every domain the subject joined (checked via `g2`) |
192
- | `AuthorizationDomainScopes.SYSTEM_WIDE` | `'SYSTEM_WIDE'` | Applies system-wide, bypassing membership (super-admin) |
193
-
194
- > [!NOTE]
195
- > 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`).
196
-
197
- ### Built-in Roles
198
-
199
- | Constant | Identifier | Priority | Description |
200
- |----------|------------|----------|-------------|
201
- | `AuthorizationRoles.SUPER_ADMIN` | `'999_super-admin'` | 999 | Highest privilege |
202
- | `AuthorizationRoles.ADMIN` | `'900_admin'` | 900 | Administrator |
203
- | `AuthorizationRoles.USER` | `'010_user'` | 10 | Regular user |
204
- | `AuthorizationRoles.GUEST` | `'001_guest'` | 1 | Guest user |
205
- | `AuthorizationRoles.UNKNOWN_USER` | `'000_unknown-user'` | 0 | Unauthenticated fallback |
206
-
207
- Each built-in role is an `AuthorizationRole` instance. `AuthorizationRoles.SCHEME_SET` contains identifier strings. `AuthorizationRoles.isValid(input)` checks membership.
208
-
209
- ### Import Paths
210
-
211
- ```typescript
212
- // Classes & functions
213
- import {
214
- // Component & middleware
215
- AuthorizeComponent,
216
- AuthorizationProvider,
217
- authorize,
218
-
219
- // Registry
220
- AuthorizationEnforcerRegistry,
221
-
222
- // Enforcers
223
- CasbinAuthorizationEnforcer,
224
-
225
- // Adapters
226
- BaseFilteredAdapter,
227
- ScopedCasbinAdapter,
228
-
229
- // Scoped RBAC model
230
- CASBIN_RBAC_DOMAIN_SCOPED_MODEL,
231
-
232
- // Models
233
- AuthorizationRole,
234
-
235
- // Constants
236
- Authorization,
237
- AuthorizationActions,
238
- AuthorizationDecisions,
239
- AuthorizationDomainScopes,
240
- AuthorizationPolicyVariants,
241
- AuthorizationRoles,
242
- AuthorizationEnforcerTypes,
243
- CasbinEnforcerModelDrivers,
244
- CasbinEnforcerCachedDrivers,
245
- CasbinRuleVariants,
246
- CasbinDomainMatchingFunctions,
247
-
248
- // Binding keys
249
- AuthorizeBindingKeys,
250
- } from '@venizia/ignis';
251
-
252
- // Types & interfaces
253
- import type {
254
- // Core interfaces
255
- IAuthorizeOptions,
256
- IAuthorizationEnforcer,
257
- IAuthorizationSpec,
258
- IAuthorizationRequest,
259
- IAuthorizationRole,
260
-
261
- // Casbin options
262
- ICasbinEnforcerOptions,
263
- ICasbinEnforcerCachedRedis,
264
-
265
- // Adapter types
266
- ICasbinPolicyFilter,
267
- ICasbinPolicySource,
268
- IScopedCasbinEntities,
269
- IScopedCasbinPolicyFilter,
270
-
271
- // Function & utility types
272
- TAuthorizeFn,
273
- TAuthorizationVoter,
274
- TAuthorizationConditions,
275
- TRegistryDescriptor,
276
-
277
- // Value types (from TConstValue)
278
- TAuthorizationAction,
279
- TAuthorizationDecision,
280
- TAuthorizationEnforcerType,
281
- TCasbinEnforcerCachedDriver,
282
- TCasbinEnforcerModelDriver,
283
- TCasbinRuleVariant,
284
- TCasbinDomainMatchingFunction,
285
- } from '@venizia/ignis';
286
- ```
287
-
288
- ## Setup
289
-
290
- Authorization setup is a **three-step process**: bind global options, register the component, then register enforcers via the registry.
291
-
292
- ```mermaid
293
- flowchart LR
294
- subgraph Step1["Step 1"]
295
- A["bind IAuthorizeOptions<br/>to AuthorizeBindingKeys.OPTIONS"]
296
- end
297
- subgraph Step2["Step 2"]
298
- B["this.component(AuthorizeComponent)<br/>validates options + binds alwaysAllowRoles"]
299
- end
300
- subgraph Step3["Step 3"]
301
- C["AuthorizationEnforcerRegistry.register()<br/>class + name + type + options"]
302
- end
303
- A --> B --> C
304
- ```
305
-
306
- ### Step 1: Bind Global Options
307
-
308
- Bind `IAuthorizeOptions` to configure global authorization behavior. This interface is minimal -- it only contains global settings, not enforcer-specific configuration.
309
-
310
- ```typescript
311
- import {
312
- AuthorizeBindingKeys,
313
- AuthorizationDecisions,
314
- IAuthorizeOptions,
315
- } from '@venizia/ignis';
316
-
317
- this.bind<IAuthorizeOptions>({ key: AuthorizeBindingKeys.OPTIONS }).toValue({
318
- defaultDecision: AuthorizationDecisions.DENY,
319
- alwaysAllowRoles: ['999_super-admin'],
320
- });
321
- ```
322
-
323
- ### Step 2: Register the Component
324
-
325
- ```typescript
326
- import { AuthorizeComponent } from '@venizia/ignis';
327
-
328
- this.component(AuthorizeComponent);
329
- ```
330
-
331
- The component validates that `IAuthorizeOptions` is bound and extracts `alwaysAllowRoles` into a separate binding (`AuthorizeBindingKeys.ALWAYS_ALLOW_ROLES`) for downstream consumers.
332
-
333
- ### Step 3: Register Enforcers via Registry
334
-
335
- Enforcer registration is separate from global options. Each enforcer is registered with its **class**, **name**, **type**, and **options** -- all co-located in one call.
336
-
337
- #### Casbin Enforcer (Recommended)
338
-
339
- ```typescript
340
- import {
341
- AuthorizationEnforcerRegistry,
342
- AuthorizationEnforcerTypes,
343
- CasbinAuthorizationEnforcer,
344
- CasbinEnforcerModelDrivers,
345
- ScopedCasbinAdapter,
346
- CASBIN_RBAC_DOMAIN_SCOPED_MODEL,
347
- } from '@venizia/ignis';
348
-
349
- // The generic scoped adapter - reads one principal's edges + the shared hierarchy from a single
350
- // PolicyDefinition edge table. No subclassing; configure it with IScopedCasbinEntities.
351
- const adapter = new ScopedCasbinAdapter({
352
- dataSource,
353
- entities: {
354
- policyDefinition: { tableName: 'PolicyDefinition', schemaName: 'identity' },
355
- permission: { tableName: 'Permission', schemaName: 'identity' },
356
- principals: { user: 'User', role: 'Role' }, // casbin name prefixes
357
- domainTypes: ['Merchant', 'Organizer'], // domain types you scope on
358
- softDelete: { use: true, columnName: 'deleted_at' },
359
- },
360
- });
361
-
362
- AuthorizationEnforcerRegistry.getInstance().register({
363
- container: this,
364
- enforcers: [{
365
- enforcer: CasbinAuthorizationEnforcer,
366
- name: 'casbin',
367
- type: AuthorizationEnforcerTypes.CASBIN,
368
- options: {
369
- model: {
370
- driver: CasbinEnforcerModelDrivers.TEXT,
371
- definition: CASBIN_RBAC_DOMAIN_SCOPED_MODEL,
372
- },
373
- isScoped: true, // 4-token (sub, dom, obj, act); auto-registers keyMatch + objectMatch
374
- adapter,
375
- cached: {
376
- use: true,
377
- driver: 'redis',
378
- options: {
379
- connection: redisHelper,
380
- expiresIn: 5 * 60 * 1000, // 5 minutes
381
- keyFn: ({ user }) => `authz:policies:${user.principalType}:${user.userId}`,
382
- },
383
- },
384
- // poolSize / poolAcquireTimeoutMs are optional (defaults 16 / 5000ms).
385
- // In scoped mode you do NOT pass domainMatching or normalizePayloadFn - the request domain
386
- // is supplied by the provider's domain resolver (see "Domain scoping" in usage.md).
387
- },
388
- }],
389
- });
390
- ```
391
-
392
- #### Custom Enforcer
393
-
394
- ```typescript
395
- AuthorizationEnforcerRegistry.getInstance().register({
396
- container: this,
397
- enforcers: [{
398
- enforcer: MyCustomEnforcer,
399
- name: 'my-custom',
400
- type: AuthorizationEnforcerTypes.CUSTOM,
401
- options: { /* your enforcer-specific options */ },
402
- }],
403
- });
404
- ```
405
-
406
- ### Full Setup Example
16
+ The recommended setup: scoped Casbin RBAC backed by one `PolicyDefinition` edge table, then a protected route.
407
17
 
408
18
  ```typescript
409
19
  import {
410
- AuthorizeComponent,
411
- AuthorizeBindingKeys,
412
- AuthorizationEnforcerRegistry,
413
- AuthorizationEnforcerTypes,
414
- CasbinAuthorizationEnforcer,
415
- CasbinEnforcerModelDrivers,
416
- ScopedCasbinAdapter,
417
- CASBIN_RBAC_DOMAIN_SCOPED_MODEL,
418
- BaseApplication,
419
- IAuthorizeOptions,
20
+ AuthorizeBindingKeys, AuthorizeComponent, AuthorizationDecisions, AuthorizationEnforcerRegistry,
21
+ AuthorizationEnforcerTypes, CasbinAuthorizationEnforcer, CasbinEnforcerModelDrivers,
22
+ ScopedCasbinAdapter, CASBIN_RBAC_DOMAIN_SCOPED_MODEL, BaseApplication, IAuthorizeOptions,
420
23
  } from '@venizia/ignis';
421
24
 
422
25
  export class Application extends BaseApplication {
423
- async registerAuthorization() {
424
- // Step 1: Global options
26
+ preConfigure() {
27
+ // 1. Global options
425
28
  this.bind<IAuthorizeOptions>({ key: AuthorizeBindingKeys.OPTIONS }).toValue({
426
- defaultDecision: 'deny',
29
+ defaultDecision: AuthorizationDecisions.DENY,
427
30
  alwaysAllowRoles: ['999_super-admin'],
428
31
  });
429
32
 
430
- // Step 2: Register component
33
+ // 2. Component (validates options, binds alwaysAllowRoles)
431
34
  this.component(AuthorizeComponent);
432
35
 
433
- // Step 3: Register enforcer(s) with co-located options
434
- const adapter = new ScopedCasbinAdapter({ dataSource, entities: { /* IScopedCasbinEntities */ } });
36
+ // 3. Enforcer, registered by class + name + type + co-located options
37
+ const adapter = new ScopedCasbinAdapter({
38
+ dataSource, // a BasePostgresDataSource instance
39
+ entities: {
40
+ policyDefinition: { tableName: 'PolicyDefinition', schemaName: 'identity' },
41
+ permission: { tableName: 'Permission', schemaName: 'identity' },
42
+ principals: { user: 'User', role: 'Role' },
43
+ domainTypes: ['Merchant'],
44
+ softDelete: { use: true, columnName: 'deleted_at' },
45
+ },
46
+ });
435
47
 
436
48
  AuthorizationEnforcerRegistry.getInstance().register({
437
49
  container: this,
@@ -440,10 +52,7 @@ export class Application extends BaseApplication {
440
52
  name: 'casbin',
441
53
  type: AuthorizationEnforcerTypes.CASBIN,
442
54
  options: {
443
- model: {
444
- driver: CasbinEnforcerModelDrivers.TEXT,
445
- definition: CASBIN_RBAC_DOMAIN_SCOPED_MODEL,
446
- },
55
+ model: { driver: CasbinEnforcerModelDrivers.TEXT, definition: CASBIN_RBAC_DOMAIN_SCOPED_MODEL },
447
56
  isScoped: true,
448
57
  adapter,
449
58
  cached: { use: false },
@@ -454,409 +63,93 @@ export class Application extends BaseApplication {
454
63
  }
455
64
  ```
456
65
 
457
- > [!IMPORTANT]
458
- > Authorization depends on authentication. Register `AuthenticateComponent` **before** `AuthorizeComponent` so that `Authentication.CURRENT_USER` is populated before authorization checks run.
459
-
460
- > [!NOTE]
461
- > 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.
462
-
463
- ## Configuration
464
-
465
- ### IAuthorizeOptions
466
-
467
- Global authorization settings. Bound to the container before registering `AuthorizeComponent`.
468
-
469
- | Option | Type | Default | Description |
470
- |--------|------|---------|-------------|
471
- | `defaultDecision` | `TAuthorizationDecision` | -- | **Required.** Decision when enforcer returns `ABSTAIN` (`'allow'`, `'deny'`, or `'abstain'`) |
472
- | `alwaysAllowRoles` | `string[]` | `[]` | Roles that bypass all authorization checks (global) |
473
- | `domainResolver` | `TAuthorizationDomainResolver` | -- | Fallback domain resolver used when a route's `spec.domain` is not set. Returns `{ type, id }` or `null` (→ `SYSTEM_WIDE`) |
474
-
475
- ```typescript
476
- interface IAuthorizeOptions {
477
- defaultDecision: TAuthorizationDecision;
478
- alwaysAllowRoles?: string[];
479
- /** Fallback domain resolver used when a route's spec has no `domain`. */
480
- domainResolver?: TAuthorizationDomainResolver;
481
- }
482
- ```
483
-
484
- ### ICasbinEnforcerOptions
485
-
486
- Casbin-specific options, provided per-enforcer via `AuthorizationEnforcerRegistry.register()`.
487
-
488
- | Option | Type | Default | Description |
489
- |--------|------|---------|-------------|
490
- | `model` | `{ driver, definition }` | -- | **Required.** Casbin model definition (file path or inline text). For scoped RBAC, use `CASBIN_RBAC_DOMAIN_SCOPED_MODEL` |
491
- | `cached` | `{ use: false } \| { use: true, driver: 'redis', options }` | -- | **Required.** Caching configuration (Redis-only) |
492
- | `adapter` | `Adapter` | -- | Casbin adapter instance (e.g., `ScopedCasbinAdapter`) |
493
- | `isScoped` | `boolean` | `false` | Enable the scoped model: 4-token `(sub, dom, obj, act)` requests; auto-registers `keyMatch` on `g` + `objectMatch` on the resource relation |
494
- | `poolSize` | `number` | `16` | Number of pooled enforcers (each request enforces on its own) |
495
- | `poolAcquireTimeoutMs` | `number` | `5000` | Max ms to wait for a free pooled enforcer before failing closed |
496
- | `normalizePayloadFn` | `(opts) => { subject, resource, action, domain? }` | -- | (Non-scoped/custom) normalize subject/resource/action before evaluation |
497
- | `domainMatching` | `{ roleDefinition: string; fn: TCasbinDomainMatchingFunction }` | -- | (Non-scoped) opt-in domain matching function on a role definition. **Not needed when `isScoped: true`** - the scoped model registers its matchers automatically |
498
-
499
- ```typescript
500
- interface ICasbinEnforcerOptions<
501
- E extends Env = Env,
502
- TAction = string,
503
- TResource = string,
504
- TAdapter = Adapter,
505
- > {
506
- model:
507
- | { driver: 'file'; definition: string }
508
- | { driver: 'text'; definition: string };
509
-
510
- cached:
511
- | { use: false }
512
- | (ICasbinEnforcerCachedRedis & { use: true });
513
-
514
- adapter?: TAdapter;
515
-
516
- // Enable the scoped RBAC model (4-token requests + auto-registered matchers).
517
- isScoped?: boolean;
518
-
519
- // Per-request enforcer pool (concurrency-safe; fail-closed on error).
520
- poolSize?: number; // default 16
521
- poolAcquireTimeoutMs?: number; // default 5000
522
-
523
- normalizePayloadFn?(opts: {
524
- user: IAuthUser;
525
- action: TAction;
526
- resource: TResource;
527
- context: TContext<E, string>;
528
- }): {
529
- subject: string;
530
- resource: string;
531
- action: string;
532
- domain?: string;
533
- };
534
-
535
- // Non-scoped only. Registers a Casbin domain matching function on the named role definition.
536
- // When isScoped is true, the scoped model registers its own matchers - do not set this.
537
- domainMatching?: {
538
- roleDefinition: string; // e.g. 'g'
539
- fn: TCasbinDomainMatchingFunction;
540
- };
541
- }
542
- ```
543
-
544
- > [!NOTE]
545
- > `cached.options.expiresIn` must be >= 10,000 ms (10 seconds). Values below this threshold cause a validation error (`MIN_EXPIRES_IN = 10_000`).
546
-
547
- #### Cache Configuration Types
548
-
549
- The `cached` field is a discriminated union. **Caching is Redis-only** (the in-memory driver was removed):
550
-
551
- ```typescript
552
- // No caching - every request rebuilds the user's policy from the datasource.
553
- interface { use: false }
554
-
555
- // Redis cache (store/retrieve the user's policy lines from Redis, TTL via PX).
556
- interface ICasbinEnforcerCachedRedis {
557
- driver: 'redis';
558
- options: {
559
- connection: IRedisHelper;
560
- expiresIn: number;
561
- keyFn: (opts: { user: IAuthorizationUser }) => ValueOrPromise<string>;
562
- };
563
- }
564
- ```
565
-
566
- ### IAuthorizationSpec (Route-level)
567
-
568
- | Option | Type | Default | Description |
569
- |--------|------|---------|-------------|
570
- | `action` | `TAction` | -- | **Required.** Action being performed (e.g., `'read'`, `'create'`) |
571
- | `resource` | `TResource` | -- | **Required.** Resource being accessed (e.g., `'Article'`, `'User'`) |
572
- | `conditions` | `TAuthorizationConditions` | -- | Key-value conditions for ABAC (strict equality) |
573
- | `allowedRoles` | `string[]` | -- | Roles that bypass enforcer for this specific route |
574
- | `voters` | `TAuthorizationVoter[]` | -- | Custom voter functions for this specific route |
575
-
576
- ```typescript
577
- interface IAuthorizationSpec<E extends Env = Env, TAction = string, TResource = string> {
578
- action: TAction;
579
- resource: TResource;
580
- conditions?: TAuthorizationConditions;
581
- allowedRoles?: string[];
582
- voters?: TAuthorizationVoter<E, TAction, TResource>[];
583
- }
584
- ```
585
-
586
- ### TAuthorizationConditions
587
-
588
- Key-value conditions for attribute-based access control. Values are compared with strict equality (`===`).
589
-
590
- ```typescript
591
- type TAuthorizationConditions<
592
- KeyType extends string | symbol = string | symbol,
593
- ValueType = string | number | boolean | null,
594
- > = Record<KeyType, ValueType>;
595
- ```
596
-
597
- ### TAuthorizationVoter
598
-
599
- Function type for voter callbacks:
600
-
601
- ```typescript
602
- type TAuthorizationVoter<
603
- E extends Env = Env,
604
- TAction = string,
605
- TResource = string,
606
- > = (opts: {
607
- user: IAuthUser;
608
- action: TAction;
609
- resource: TResource;
610
- context: TContext<E, string>;
611
- }) => ValueOrPromise<TAuthorizationDecision>;
612
- ```
613
-
614
- ### TAuthorizeFn
615
-
616
- Function type for the `authorize()` middleware factory:
617
-
618
- ```typescript
619
- type TAuthorizeFn<E extends Env = Env, TAction = string, TResource = string> = (opts: {
620
- spec: IAuthorizationSpec<E, TAction, TResource>;
621
- enforcerName?: string;
622
- }) => MiddlewareHandler;
623
- ```
624
-
625
- ## Binding Keys
626
-
627
- | Key | Constant | Type | Description |
628
- |-----|----------|------|-------------|
629
- | `@app/authorize/options` | `AuthorizeBindingKeys.OPTIONS` | `IAuthorizeOptions` | Global authorization options |
630
- | `@app/authorize/always-allow-roles` | `AuthorizeBindingKeys.ALWAYS_ALLOW_ROLES` | `string[]` | Auto-bound by component if present in options |
631
- | `@app/authorize/enforcers/{name}/options` | `AuthorizeBindingKeys.enforcerOptions(name)` | `ICasbinEnforcerOptions \| unknown` | Per-enforcer options, auto-bound by registry |
632
-
633
- ```typescript
634
- class AuthorizeBindingKeys {
635
- static readonly OPTIONS = '@app/authorize/options';
636
- static readonly ALWAYS_ALLOW_ROLES = '@app/authorize/always-allow-roles';
637
-
638
- static enforcerOptions(name: string): string {
639
- return `@app/authorize/enforcers/${name}/options`;
640
- }
641
- }
642
- ```
643
-
644
- > [!NOTE]
645
- > `AuthorizeBindingKeys.enforcerOptions(name)` is called automatically by `AuthorizationEnforcerRegistry.register()` when `options` is provided. The `CasbinAuthorizationEnforcer` injects its options from `AuthorizeBindingKeys.enforcerOptions('casbin')`.
646
-
647
- ## Context Variables
648
-
649
- 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:
650
-
651
- ```typescript
652
- declare module 'hono' {
653
- interface ContextVariableMap {
654
- // Authentication
655
- [Authentication.CURRENT_USER]: IAuthUser;
656
- [Authentication.AUDIT_USER_ID]: IdType;
657
- [Authentication.SKIP_AUTHENTICATION]: boolean;
658
-
659
- // Authorization
660
- [Authorization.RULES]: unknown;
661
- [Authorization.SKIP_AUTHORIZATION]: boolean;
662
- [Authorization.DOMAIN]: string;
663
- }
664
- }
665
- ```
666
-
667
- ### Authorization-Specific Variables
668
-
669
- | Key | Constant | Type | Description |
670
- |-----|----------|------|-------------|
671
- | `'authorization.rules'` | `Authorization.RULES` | `unknown` | Cached rules built by the enforcer. Type depends on enforcer implementation. |
672
- | `'authorization.skip'` | `Authorization.SKIP_AUTHORIZATION` | `boolean` | Set to `true` to dynamically skip authorization for this request. |
673
- | `'authorization.domain'` | `Authorization.DOMAIN` | `string` | Resolved request domain scope (`"<Type>_<id>"` or `SYSTEM_WIDE`); set by the provider when `spec.domain` or a global `domainResolver` is in play, and read by the enforcer. |
674
-
675
- ### Authentication Variables Used by Authorization
676
-
677
- | Key | Constant | Type | Used for |
678
- |-----|----------|------|----------|
679
- | `'authentication.currentUser'` | `Authentication.CURRENT_USER` | `IAuthUser` | Read in step 2 to get authenticated user |
680
- | `'authentication.auditUserId'` | `Authentication.AUDIT_USER_ID` | `IdType` | Available for audit logging |
681
-
682
- ### IAuthUser Interface
683
-
684
- The `IAuthUser` interface (from the authenticate module) is the user object available during authorization:
685
-
686
- ```typescript
687
- interface IAuthUser {
688
- userId: IdType; // IdType = number | string | bigint
689
- [extra: string | symbol]: any;
690
- }
691
- ```
692
-
693
- The authorization middleware accesses `user.roles` (for role extraction) and `user.principalType` (for enforcer-based evaluation) via the index signature.
694
-
695
- ### IJWTTokenPayload Interface
696
-
697
- When using JWT authentication, the full token payload extends `IAuthUser`:
698
-
699
66
  ```typescript
700
- interface IJWTTokenPayload extends JWTPayload, IAuthUser {
701
- userId: IdType;
702
- roles: { id: IdType; identifier: string; priority: number }[];
703
- clientId?: string;
704
- provider?: string;
705
- email?: string;
706
- name?: string;
707
- [extra: string | symbol]: any;
708
- }
67
+ // Inside a controller's binding() - a route protected by authentication + authorization
68
+ const DELETE_ARTICLE_CONFIG = {
69
+ path: '/articles/{id}',
70
+ method: 'delete',
71
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
72
+ authorize: { action: AuthorizationActions.DELETE, resource: 'Article' },
73
+ responses: jsonResponse({ description: 'Deleted article', schema: ArticleSchema }),
74
+ } as const;
709
75
  ```
710
76
 
711
- > [!NOTE]
712
- > `IdType = number | string | bigint` is defined in `@/base/models/common/types`.
77
+ ## How it works
713
78
 
714
- ## Relationship with Authentication
715
-
716
- Authorization runs **after** authentication in the middleware chain. Both REST and gRPC controllers ensure the correct ordering:
717
-
718
- ```mermaid
719
- flowchart LR
720
- Req([Request]) --> Auth["1. authenticate()<br/>JWT / Basic"]
721
- Auth --> Authz["2. authorize()<br/>enforcer-based"]
722
- Authz --> Custom["3. Custom middleware"]
723
- Custom --> Handler([Route Handler])
724
- ```
79
+ - **Enforcer-based and pluggable.** `authorize({ spec })` returns Hono middleware built by `AuthorizationProvider`, which resolves an `IAuthorizationEnforcer` from `AuthorizationEnforcerRegistry` by name (default: the first registered). Swap `CasbinAuthorizationEnforcer` for a custom class without touching route configs.
80
+ - **Runs after authentication.** The middleware reads `Authentication.CURRENT_USER` from the Hono context. `AuthenticateComponent` must run first, and the route needs an `authenticate` config alongside `authorize`.
81
+ - **No enforcers registered = deny, unless you opt into allow.** If `AuthorizationEnforcerRegistry.hasEnforcers()` is `false`, the middleware throws a 403 naming the missing enforcer. Set `defaultDecision: 'allow'` on `IAuthorizeOptions` to proceed instead - useful during incremental rollout - and the middleware logs a warning each time it does.
82
+ - **Casbin's scoped RBAC model is the recommended engine.** Combine `CASBIN_RBAC_DOMAIN_SCOPED_MODEL`, `isScoped: true`, and `ScopedCasbinAdapter` to read one principal's policy edges from a single `PolicyDefinition` table. See [RBAC with domains](./usage#rbac-with-domains-multi-tenant) for multi-tenant grant scoping.
83
+ - **Per-request enforcers, cached lines.** Each Casbin evaluation borrows an isolated enforcer from an internal pool, loads that user's policy lines into it, then evaluates. The datasource query runs only on a cache miss, or every time if `cached.use: false`.
725
84
 
726
- 1. Authentication middleware is injected first (from `authenticate` config)
727
- 2. Authorization middleware is injected second (from `authorize` config)
728
- 3. Custom middleware is injected last (from `middleware` config -- REST only)
85
+ **Pipeline (7 steps, short-circuits marked)**
729
86
 
730
- This means `Authentication.CURRENT_USER` is always available when the authorization middleware executes.
87
+ | # | Step | Short-circuits when |
88
+ |---|------|----------------------|
89
+ | 1 | `Authorization.SKIP_AUTHORIZATION` check | Set -> `next()` |
90
+ | 2 | Read `Authentication.CURRENT_USER` | Missing -> 401 |
91
+ | 3 | Role shortcuts (`alwaysAllowRoles` + `allowedRoles`) | Match -> `next()` |
92
+ | 4 | Voters (per-route) | `ALLOW`/`DENY` -> `next()` / 403 |
93
+ | 5 | Resolve enforcer | None registered -> 403, or `next()` if `defaultDecision: 'allow'` |
94
+ | 6 | Build/cache rules (+ resolve domain, if any) | - |
95
+ | 7 | `enforcer.evaluate()` | `DENY`/`ABSTAIN`-as-deny -> 403 |
731
96
 
732
- ### REST Controllers
97
+ ## Common tasks
733
98
 
734
- The `AbstractRestController.buildRouteMiddlewares()` method builds the middleware array from route config. The `getRouteConfigs()` method calls `buildRouteMiddlewares()` internally and wraps it into a Hono route definition:
99
+ **Secure a route.** Add `authorize: { action, resource }` next to `authenticate` in a route config or `@get`/`@post` decorator.
735
100
 
736
101
  ```typescript
737
- // In AbstractRestController
738
- buildRouteMiddlewares<RouteConfig extends IAuthRouteConfig>(opts: { configs: RouteConfig }) {
739
- const { authenticate = {}, authorize, ...restConfig } = opts.configs;
740
- const mws = [];
741
-
742
- // 1. Authenticate middleware (first)
743
- if (strategies.length > 0) {
744
- mws.push(authenticateFn({ strategies, mode }));
745
- }
746
-
747
- // 2. Authorize middleware (second) - supports single or array
748
- if (authorize) {
749
- const specs = Array.isArray(authorize) ? authorize : [authorize];
750
- for (const spec of specs) {
751
- mws.push(authorizeFn({ spec }));
752
- }
753
- }
754
-
755
- // 3. Custom middleware (last)
756
- if (restConfig.middleware) { ... }
757
-
758
- return { restConfig, security, mws };
759
- }
102
+ authorize: { action: AuthorizationActions.READ, resource: 'Article' }
760
103
  ```
761
104
 
762
- ### gRPC Controllers
105
+ **Bypass the enforcer for trusted roles.** Both skip straight to `next()` once a matching role is found.
763
106
 
764
- The `AbstractGrpcController.buildRpcMiddlewares()` method provides symmetric authorization support for gRPC routes. Authorization specs are applied the same way as REST:
107
+ | Option | Scope |
108
+ |---|---|
109
+ | `alwaysAllowRoles` | Global, set in `IAuthorizeOptions` |
110
+ | `allowedRoles` | Per-route, set on the spec |
765
111
 
766
112
  ```typescript
767
- // In AbstractGrpcController
768
- buildRpcMiddlewares(opts: { configs: IRpcMetadata }): TRpcMiddleware[] {
769
- const { configs } = opts;
770
- const mws = [];
771
-
772
- // 1. Authenticate middleware
773
- if (configs.authenticate) { ... }
774
-
775
- // 2. Authorize middleware - same pattern as REST
776
- if (configs.authorize) {
777
- const specs = Array.isArray(configs.authorize) ? configs.authorize : [configs.authorize];
778
- for (const spec of specs) {
779
- const authzMw = authorizeFn({ spec });
780
- mws.push((context, next) => authzMw(context, next));
781
- }
782
- }
783
-
784
- return mws;
785
- }
113
+ authorize: { action: AuthorizationActions.DELETE, resource: 'Article', allowedRoles: [AuthorizationRoles.ADMIN.identifier] }
786
114
  ```
787
115
 
788
- ### IAuthRouteConfig
789
-
790
- Extended route config that supports both authentication and authorization:
116
+ **Add custom logic before the enforcer.** A voter returns `ALLOW`/`DENY`/`ABSTAIN`; the first non-`ABSTAIN` decision wins.
791
117
 
792
118
  ```typescript
793
- interface IAuthRouteConfig extends HonoRouteConfig {
794
- authenticate?: { strategies?: TAuthStrategy[]; mode?: TAuthMode };
795
- authorize?: IAuthorizationSpec | IAuthorizationSpec[];
796
- }
119
+ const ownerVoter: TAuthorizationVoter = async ({ user, context }) =>
120
+ (await articleService.findById({ id: context.req.param('id') }))?.authorId === user.userId
121
+ ? AuthorizationDecisions.ALLOW
122
+ : AuthorizationDecisions.ABSTAIN;
797
123
  ```
798
124
 
799
- When `authorize` is an array, each spec creates a separate middleware. All must pass for the handler to execute.
800
-
801
- ### Per-Route Configuration in CRUD Factory
802
-
803
- CRUD factory routes support both authentication and authorization configuration:
125
+ **Scope a grant to a tenant.** Declare where the domain comes from per route (or globally via `IAuthorizeOptions.domainResolver`).
804
126
 
805
127
  ```typescript
806
- ControllerFactory.defineCrudController({
807
- entity: Article,
808
- repository: { name: 'ArticleRepository' },
809
- controller: {
810
- name: 'ArticleController',
811
- basePath: '/articles',
812
- },
813
- authenticate: { strategies: [Authentication.STRATEGY_JWT], mode: AuthenticationModes.ANY },
814
- authorize: { action: AuthorizationActions.READ, resource: 'Article' },
815
- routes: {
816
- // Skip both auth for public read
817
- find: { authenticate: { skip: true } },
818
- // Override authorization for delete
819
- deleteById: {
820
- authorize: { action: AuthorizationActions.DELETE, resource: 'Article' },
821
- },
822
- // Skip only authorization
823
- count: { authorize: { skip: true } },
824
- },
825
- });
128
+ authorize: { action: 'read', resource: 'Order', domain: { from: 'param', key: 'merchantId', type: 'Merchant' } }
826
129
  ```
827
130
 
828
- **Priority rules:**
829
- 1. `authenticate: { skip: true }` -- skips both authentication AND authorization
830
- 2. `authorize: { skip: true }` -- skips only authorization (authentication still runs)
831
- 3. Per-route `authorize` overrides controller-level `authorize`
832
- 4. No per-route config -- inherits controller-level config
833
-
834
- ### Per-Route Auth Types
131
+ **Reference a model instead of a hardcoded string.** `@model({ settings: { authorize: { principal } } })` auto-populates `Model.AUTHORIZATION_SUBJECT`.
835
132
 
836
133
  ```typescript
837
- /** Per-route authorization config: { skip: true }, single spec, or array of specs. */
838
- type TRouteAuthorizeConfig = { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
839
-
840
- /** Per-route auth config. Endpoint config takes precedence over controller-level config. */
841
- type TRouteAuthConfig = {
842
- authenticate?: TRouteAuthenticateConfig;
843
- authorize?: TRouteAuthorizeConfig;
844
- };
134
+ authorize: { action: AuthorizationActions.READ, resource: Article.AUTHORIZATION_SUBJECT }
845
135
  ```
846
136
 
847
- ## See Also
848
-
849
- - [Usage & Examples](./usage) -- Securing routes, voters, patterns, and CRUD integration
850
- - [API Reference](./api) -- Architecture, enforcer internals, provider, registry, and adapters
851
- - [Error Reference](./errors) -- Error messages and troubleshooting
137
+ ## See also
852
138
 
853
- - **Related Components:**
854
- - [Authentication](../authentication/) -- Authentication system (runs before authorization)
855
- - [All Components](../index) -- Built-in components list
139
+ - [Usage & Examples](./usage) - securing routes, voters, CRUD factory integration, domain scoping in depth
140
+ - [API Reference](./api) - architecture, enforcer internals, provider pipeline, registry, adapters
141
+ - [Error Reference](./errors) - every error message and how to fix it
142
+ - [Authentication](../authentication/) - runs before authorization, populates `Authentication.CURRENT_USER`
143
+ - [Components Overview](/guides/core-concepts/components) - component system basics
144
+ - [Persistent Models](/guides/core-concepts/persistent/models#authorization-settings) - declaring `AUTHORIZATION_SUBJECT` on a model
145
+ - [Security Guidelines](/best-practices/security-guidelines) - authorization best practices
856
146
 
857
- - **References:**
858
- - [Controllers](/references/base/controllers) -- Route configuration with auth
859
- - [Middlewares](/references/base/middlewares) -- Custom middleware integration
147
+ **Files:**
860
148
 
861
- - **Best Practices:**
862
- - [Security Guidelines](/best-practices/security-guidelines) -- Authorization best practices
149
+ - [`packages/core-server/src/components/auth/authorize/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/component.ts) - `AuthorizeComponent`
150
+ - [`packages/core-server/src/components/auth/authorize/common/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/common) - constants, binding keys, types, policy/permission builders
151
+ - [`packages/core-server/src/components/auth/authorize/providers/authorization.provider.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/providers/authorization.provider.ts) - `AuthorizationProvider` (the 7-step pipeline)
152
+ - [`packages/core-server/src/components/auth/authorize/enforcers/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/enforcers) - `CasbinAuthorizationEnforcer`, `AuthorizationEnforcerRegistry`, `CASBIN_RBAC_DOMAIN_SCOPED_MODEL`
153
+ - [`packages/core-server/src/components/auth/authorize/adapters/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/adapters) - `BaseFilteredAdapter`, `ScopedCasbinAdapter`
154
+ - [`packages/core-server/src/components/auth/authorize/models/authorization-role.model.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/models/authorization-role.model.ts) - `AuthorizationRole`
155
+ - [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts) - `@model` auto-populating `AUTHORIZATION_SUBJECT` from `settings.authorize.principal`