@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.
- package/README.md +125 -388
- package/dist/mcp-server/common/config.d.ts +0 -21
- package/dist/mcp-server/common/config.d.ts.map +1 -1
- package/dist/mcp-server/common/config.js +1 -36
- package/dist/mcp-server/common/config.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts +0 -24
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +0 -25
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/helpers/github.helper.d.ts +0 -13
- package/dist/mcp-server/helpers/github.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/github.helper.js +3 -20
- package/dist/mcp-server/helpers/github.helper.js.map +1 -1
- package/dist/mcp-server/index.js +1 -20
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +4 -85
- package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/base.tool.js +1 -38
- package/dist/mcp-server/tools/base.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +8 -2
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +1 -10
- package/dist/mcp-server/tools/docs/get-document-content.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts +13 -2
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +1 -10
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +16 -8
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +2 -25
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/list-categories.tool.d.ts +5 -2
- package/dist/mcp-server/tools/docs/list-categories.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/list-categories.tool.js +1 -10
- package/dist/mcp-server/tools/docs/list-categories.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/list-documents.tool.d.ts +11 -2
- package/dist/mcp-server/tools/docs/list-documents.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/list-documents.tool.js +1 -10
- package/dist/mcp-server/tools/docs/list-documents.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +13 -2
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.js +1 -10
- package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +9 -2
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +1 -10
- package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +16 -2
- package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +2 -14
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts +19 -6
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +2 -19
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.d.ts +8 -2
- package/dist/mcp-server/tools/github/view-source-file.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -10
- package/dist/mcp-server/tools/github/view-source-file.tool.js.map +1 -1
- package/dist/mcp-server/tools/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/index.js +0 -2
- package/dist/mcp-server/tools/index.js.map +1 -1
- package/package.json +68 -54
- package/wiki/best-practices/api-usage-examples.md +7 -5
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/constants-configuration.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +1 -1
- package/wiki/best-practices/common-pitfalls.md +1 -1
- package/wiki/best-practices/data-modeling.md +33 -1
- package/wiki/best-practices/error-handling.md +7 -4
- package/wiki/best-practices/performance-optimization.md +1 -1
- package/wiki/best-practices/security-guidelines.md +5 -4
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/controllers.md +14 -8
- package/wiki/guides/core-concepts/persistent/models.md +32 -0
- package/wiki/guides/core-concepts/services.md +2 -1
- package/wiki/guides/get-started/5-minute-quickstart.md +1 -1
- package/wiki/guides/reference/mcp-docs-server.md +0 -134
- package/wiki/guides/tutorials/building-a-crud-api.md +2 -1
- package/wiki/guides/tutorials/complete-installation.md +2 -2
- package/wiki/guides/tutorials/ecommerce-api.md +3 -3
- package/wiki/guides/tutorials/realtime-chat.md +7 -6
- package/wiki/index.md +2 -1
- package/wiki/references/base/components.md +2 -1
- package/wiki/references/base/controllers.md +19 -12
- package/wiki/references/base/middlewares.md +2 -1
- package/wiki/references/base/models.md +11 -2
- package/wiki/references/base/services.md +2 -1
- package/wiki/references/components/authentication/api.md +525 -205
- package/wiki/references/components/authentication/errors.md +502 -105
- package/wiki/references/components/authentication/index.md +388 -75
- package/wiki/references/components/authentication/usage.md +428 -266
- package/wiki/references/components/authorization/api.md +1213 -0
- package/wiki/references/components/authorization/errors.md +387 -0
- package/wiki/references/components/authorization/index.md +712 -0
- package/wiki/references/components/authorization/usage.md +758 -0
- package/wiki/references/components/health-check.md +2 -1
- package/wiki/references/components/index.md +2 -0
- package/wiki/references/components/socket-io/index.md +9 -4
- package/wiki/references/components/socket-io/usage.md +1 -1
- package/wiki/references/components/static-asset/index.md +3 -5
- package/wiki/references/components/swagger.md +2 -1
- package/wiki/references/configuration/environment-variables.md +2 -1
- package/wiki/references/configuration/index.md +2 -1
- package/wiki/references/helpers/error/index.md +1 -1
- package/wiki/references/helpers/index.md +1 -0
- package/wiki/references/helpers/inversion/index.md +1 -1
- package/wiki/references/helpers/kafka/index.md +305 -0
- package/wiki/references/helpers/redis/index.md +2 -9
- package/wiki/references/quick-reference.md +3 -5
- package/wiki/references/utilities/crypto.md +2 -2
- package/wiki/references/utilities/date.md +5 -5
- package/wiki/references/utilities/index.md +3 -11
- package/wiki/references/utilities/jsx.md +4 -2
- package/wiki/references/utilities/module.md +1 -1
- package/wiki/references/utilities/parse.md +4 -4
- package/wiki/references/utilities/performance.md +2 -2
- package/wiki/references/utilities/promise.md +4 -4
- 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
|