@venizia/ignis-docs 0.2.1-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 (145) hide show
  1. package/content/best-practices/architectural-patterns.md +3 -3
  2. package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
  3. package/content/best-practices/contribution-workflow.md +2 -2
  4. package/content/best-practices/error-handling.md +94 -89
  5. package/content/best-practices/security-guidelines.md +5 -5
  6. package/content/extensions/components/api-reference.md +22 -21
  7. package/content/extensions/components/authentication/api.md +64 -28
  8. package/content/extensions/components/authentication/errors.md +19 -5
  9. package/content/extensions/components/authentication/index.md +19 -18
  10. package/content/extensions/components/authentication/usage.md +55 -30
  11. package/content/extensions/components/authorization/api.md +351 -84
  12. package/content/extensions/components/authorization/errors.md +51 -17
  13. package/content/extensions/components/authorization/getting-started.md +227 -0
  14. package/content/extensions/components/authorization/index.md +24 -16
  15. package/content/extensions/components/authorization/usage.md +45 -21
  16. package/content/extensions/components/health-check.md +15 -9
  17. package/content/extensions/components/index.md +24 -90
  18. package/content/extensions/components/mail/api.md +105 -54
  19. package/content/extensions/components/mail/errors.md +12 -10
  20. package/content/extensions/components/mail/index.md +32 -13
  21. package/content/extensions/components/mail/usage.md +26 -18
  22. package/content/extensions/components/request-tracker.md +18 -14
  23. package/content/extensions/components/socket-io/api.md +377 -882
  24. package/content/extensions/components/socket-io/errors.md +49 -51
  25. package/content/extensions/components/socket-io/index.md +72 -88
  26. package/content/extensions/components/socket-io/usage.md +107 -117
  27. package/content/extensions/components/static-asset/api.md +83 -31
  28. package/content/extensions/components/static-asset/errors.md +18 -7
  29. package/content/extensions/components/static-asset/index.md +18 -11
  30. package/content/extensions/components/static-asset/usage.md +11 -6
  31. package/content/extensions/components/template/index.md +3 -3
  32. package/content/extensions/components/websocket/api.md +58 -27
  33. package/content/extensions/components/websocket/errors.md +3 -3
  34. package/content/extensions/components/websocket/index.md +8 -7
  35. package/content/extensions/components/websocket/usage.md +21 -8
  36. package/content/extensions/helpers/cron/index.md +8 -7
  37. package/content/extensions/helpers/crypto/index.md +16 -8
  38. package/content/extensions/helpers/crypto/reference.md +96 -24
  39. package/content/extensions/helpers/env/index.md +14 -10
  40. package/content/extensions/helpers/error/index.md +99 -23
  41. package/content/extensions/helpers/index.md +61 -47
  42. package/content/extensions/helpers/inversion/index.md +23 -6
  43. package/content/extensions/helpers/inversion/reference.md +30 -22
  44. package/content/extensions/helpers/kafka/admin.md +3 -2
  45. package/content/extensions/helpers/kafka/compile-binary.md +69 -44
  46. package/content/extensions/helpers/kafka/consumer.md +24 -21
  47. package/content/extensions/helpers/kafka/examples.md +22 -234
  48. package/content/extensions/helpers/kafka/index.md +30 -62
  49. package/content/extensions/helpers/kafka/producer.md +31 -26
  50. package/content/extensions/helpers/kafka/schema-registry.md +19 -14
  51. package/content/extensions/helpers/logger/hf-logger.md +49 -22
  52. package/content/extensions/helpers/logger/index.md +37 -12
  53. package/content/extensions/helpers/logger/pino.md +31 -11
  54. package/content/extensions/helpers/logger/reference.md +243 -52
  55. package/content/extensions/helpers/network/api.md +65 -30
  56. package/content/extensions/helpers/network/index.md +32 -11
  57. package/content/extensions/helpers/queue/index.md +17 -6
  58. package/content/extensions/helpers/queue/reference.md +52 -25
  59. package/content/extensions/helpers/redis/index.md +29 -11
  60. package/content/extensions/helpers/redis/reference.md +85 -28
  61. package/content/extensions/helpers/secrets/index.md +82 -12
  62. package/content/extensions/helpers/socket-io/api.md +40 -21
  63. package/content/extensions/helpers/socket-io/index.md +26 -10
  64. package/content/extensions/helpers/storage/api.md +42 -24
  65. package/content/extensions/helpers/storage/index.md +10 -7
  66. package/content/extensions/helpers/types/index.md +20 -7
  67. package/content/extensions/helpers/types/reference.md +53 -14
  68. package/content/extensions/helpers/uid/index.md +166 -10
  69. package/content/extensions/helpers/websocket/api.md +52 -13
  70. package/content/extensions/helpers/websocket/index.md +7 -4
  71. package/content/extensions/helpers/worker-thread/index.md +9 -5
  72. package/content/extensions/helpers/worker-thread/reference.md +20 -20
  73. package/content/extensions/index.md +38 -39
  74. package/content/extensions/src-details/mcp-server.md +96 -548
  75. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  76. package/content/guides/core-concepts/persistent/index.md +5 -1
  77. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  78. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  79. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  80. package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
  81. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  82. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  83. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  84. package/content/guides/get-started/philosophy.md +135 -670
  85. package/content/guides/get-started/setup.md +53 -74
  86. package/content/guides/migrations/redis-helpers-migration.md +4 -3
  87. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  88. package/content/guides/migrations/unified-connectors-migration.md +7 -6
  89. package/content/guides/tutorials/realtime-chat.md +1 -1
  90. package/content/references/base/application.md +63 -21
  91. package/content/references/base/components.md +3 -3
  92. package/content/references/base/connectors.md +14 -14
  93. package/content/references/base/controllers.md +12 -12
  94. package/content/references/base/datasources-reference.md +32 -31
  95. package/content/references/base/datasources.md +6 -6
  96. package/content/references/base/dependency-injection.md +12 -11
  97. package/content/references/base/filter-system/application-usage.md +54 -30
  98. package/content/references/base/filter-system/array-operators.md +24 -46
  99. package/content/references/base/filter-system/comparison-operators.md +47 -67
  100. package/content/references/base/filter-system/default-filter.md +59 -53
  101. package/content/references/base/filter-system/fields-order-pagination.md +92 -146
  102. package/content/references/base/filter-system/index.md +25 -13
  103. package/content/references/base/filter-system/json-filtering.md +45 -184
  104. package/content/references/base/filter-system/list-operators.md +23 -53
  105. package/content/references/base/filter-system/logical-operators.md +63 -121
  106. package/content/references/base/filter-system/null-operators.md +34 -104
  107. package/content/references/base/filter-system/pattern-matching.md +40 -55
  108. package/content/references/base/filter-system/quick-reference.md +86 -198
  109. package/content/references/base/filter-system/range-operators.md +18 -46
  110. package/content/references/base/filter-system/tips.md +6 -6
  111. package/content/references/base/filter-system/use-cases.md +33 -15
  112. package/content/references/base/grpc-controllers.md +53 -17
  113. package/content/references/base/index.md +5 -3
  114. package/content/references/base/middlewares.md +11 -10
  115. package/content/references/base/models-reference.md +17 -17
  116. package/content/references/base/models.md +4 -3
  117. package/content/references/base/providers.md +8 -8
  118. package/content/references/base/repositories/advanced.md +224 -326
  119. package/content/references/base/repositories/index.md +19 -6
  120. package/content/references/base/repositories/mixins.md +5 -5
  121. package/content/references/base/repositories/relations.md +160 -293
  122. package/content/references/base/repositories/soft-deletable.md +16 -6
  123. package/content/references/base/secrets.md +17 -13
  124. package/content/references/base/services.md +6 -4
  125. package/content/references/configuration/environment-variables.md +31 -23
  126. package/content/references/configuration/index.md +6 -4
  127. package/content/references/index.md +1 -1
  128. package/content/references/utilities/duration.md +85 -0
  129. package/content/references/utilities/index.md +5 -1
  130. package/content/references/utilities/jsx-reference.md +11 -11
  131. package/content/references/utilities/jsx.md +2 -2
  132. package/content/references/utilities/module.md +78 -25
  133. package/content/references/utilities/request.md +2 -1
  134. package/content/references/utilities/retry.md +139 -0
  135. package/content/references/utilities/schema.md +2 -2
  136. package/content/references/utilities/statuses-reference.md +4 -4
  137. package/content/references/utilities/statuses.md +5 -5
  138. package/dist/mcp-server/index.js +0 -0
  139. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  140. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  141. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  142. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  143. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  144. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  145. package/package.json +17 -16
@@ -10,9 +10,21 @@ Every option, binding key, class, and method the Authentication component expose
10
10
 
11
11
  **Files:**
12
12
 
13
- - [`packages/core/src/components/auth/authenticate/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate) - component, services, strategies, controllers
14
- - [`packages/core/src/components/auth/models/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/models) - entity column helpers + request schemas
15
- - [`packages/core/src/components/auth/base/abstract-auth-registry.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/base/abstract-auth-registry.ts) - `AbstractAuthRegistry`
13
+ - [`packages/core-server/src/components/auth/authenticate/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate) - component, services, strategies, controllers
14
+ - [`packages/core-server/src/components/auth/models/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/models) - entity column helpers + request schemas
15
+ - [`packages/core-server/src/components/auth/base/abstract-auth-registry.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/base/abstract-auth-registry.ts) - `AbstractAuthRegistry`
16
+
17
+ ## Find what you need
18
+
19
+ | You want to | Go to |
20
+ |---|---|
21
+ | See how the pieces fit together | [Architecture](#architecture) |
22
+ | Look up a binding key or option field | [Binding keys](#binding-keys), [Option interfaces](#option-interfaces) |
23
+ | Check what `IAuthUser` / `IAuthService` require | [IAuthUser / IJWTTokenPayload](#iauthuser-ijwttokenpayload), [IAuthService](#iauthservice) |
24
+ | Understand the JWS/JWKS/Basic class hierarchy | [Service class hierarchy](#service-class-hierarchy) |
25
+ | Register or resolve a strategy programmatically | [Strategy registry](#strategy-registry) |
26
+ | See exactly what the built-in `/auth` controller builds | [Controller factory](#controller-factory) |
27
+ | Find a file on disk | [File structure](#file-structure) |
16
28
 
17
29
  ## Import paths
18
30
 
@@ -26,6 +38,7 @@ import {
26
38
  AuthenticationModes,
27
39
  AuthenticationTokenTypes,
28
40
  AuthenticationStrategyRegistry,
41
+ ServiceAssertion,
29
42
 
30
43
  // JOSE standards + constants
31
44
  JOSEStandards,
@@ -38,6 +51,7 @@ import {
38
51
  JWKSIssuerAuthenticationStrategy,
39
52
  JWKSVerifierAuthenticationStrategy,
40
53
  BasicAuthenticationStrategy,
54
+ ServiceAuthenticationStrategy,
41
55
 
42
56
  // Services
43
57
  AbstractBearerTokenService,
@@ -69,6 +83,9 @@ import type {
69
83
  IJWKSVerifierOptions,
70
84
  TJWKSTokenServiceOptions,
71
85
  TBasicTokenServiceOptions,
86
+ IServiceAuthOptions,
87
+ IServiceAssertionClaims,
88
+ TServiceCallerEntry,
72
89
  IAuthenticateOptions,
73
90
  IAuthUser,
74
91
  IJWTTokenPayload,
@@ -134,13 +151,14 @@ Bearer token service hierarchy:
134
151
  | Method | Purpose |
135
152
  |--------|---------|
136
153
  | `defineJWSAuth(opts)` | Validates `jwtSecret` and `getTokenExpiresFn`, binds `IJWSTokenServiceOptions` to `JWT_OPTIONS`, registers `JWSTokenService` |
137
- | `defineJWKSAuth(opts)` | Switches on `mode`. Issuer: validates keys/format/kid/getTokenExpiresFn, binds to `JWKS_OPTIONS`, registers `JWKSIssuerTokenService` + `JWKSController`. Verifier: validates `jwksUrl`, binds to `JWKS_OPTIONS`, registers `JWKSVerifierTokenService` |
154
+ | `defineJWKSAuth(opts)` | Switches on `mode`.<br>**Issuer:** validates keys/format/kid/getTokenExpiresFn, binds to `JWKS_OPTIONS`, registers `JWKSIssuerTokenService` + `JWKSController`.<br>**Verifier:** validates `jwksUrl`, binds to `JWKS_OPTIONS`, registers `JWKSVerifierTokenService` |
155
+ | `defineServiceAuth(opts)` | Validates `name` and `resolvePrincipal`, registers the verifier and the `service` strategy. With `keys` present it also registers the signer and mounts the certs route. Absent options: does nothing |
138
156
  | `defineBasicAuth(opts)` | Validates `verifyCredentials` presence, registers `BasicTokenService`. Skips (debug log) if `basicOptions` not bound |
139
157
  | `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` and registers the generated controller |
140
158
  | `defineOAuth2()` | Public stub, called during `binding()`, performs no action - not yet implemented |
141
159
 
142
160
  > [!NOTE]
143
- > The component reads the discriminated union from `JWT_OPTIONS`, then re-binds just the inner options object (`IJWSTokenServiceOptions` or `IJWKSIssuerOptions`/`IJWKSVerifierOptions`) to `JWKS_OPTIONS` or back to `JWT_OPTIONS`, so the token service can resolve it via a plain `@inject`.
161
+ > The component reads the discriminated union from `JWT_OPTIONS`. It then re-binds only the inner options object - `IJWSTokenServiceOptions`, or `IJWKSIssuerOptions`/`IJWKSVerifierOptions` - to `JWKS_OPTIONS` or back to `JWT_OPTIONS`. The token service resolves it with a plain `@inject`.
144
162
 
145
163
  ## Binding keys
146
164
 
@@ -150,9 +168,11 @@ Bearer token service hierarchy:
150
168
  | `AuthenticateBindingKeys.JWT_OPTIONS` | `@app/authenticate/jwt-options` | `TJWTTokenServiceOptions` | Conditional | -- |
151
169
  | `AuthenticateBindingKeys.JWKS_OPTIONS` | `@app/authenticate/jwks-options` | `IJWKSIssuerOptions \| IJWKSVerifierOptions` | Internal | Bound by the component from `JWT_OPTIONS` |
152
170
  | `AuthenticateBindingKeys.BASIC_OPTIONS` | `@app/authenticate/basic-options` | `TBasicTokenServiceOptions` | Conditional | -- |
171
+ | `AuthenticateBindingKeys.SERVICE_OPTIONS` | `@app/authenticate/service-options` | `IServiceAuthOptions` | Conditional | -- |
172
+ | `AuthenticateBindingKeys.SERVICE_CERTS_PATH` | `@app/authenticate/service-certs-path` | `string` | Internal | Bound by the component from `serviceOptions.rest.path` |
153
173
 
154
174
  > [!IMPORTANT]
155
- > At least one of `JWT_OPTIONS` or `BASIC_OPTIONS` must be bound, or `AuthenticateComponent.binding()` throws.
175
+ > At least one of `JWT_OPTIONS`, `BASIC_OPTIONS` or `SERVICE_OPTIONS` must be bound, or `AuthenticateComponent.binding()` throws. A service that only VERIFIES assertions needs neither jwt nor basic.
156
176
 
157
177
  ## Option interfaces
158
178
 
@@ -178,9 +198,11 @@ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // di
178
198
  | `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
179
199
  | `headerAlgorithm` | `string` | `'HS256'` | No | JWT signing algorithm |
180
200
  | `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
201
+ | `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
202
+ | `sign` | `{ issuer?, audience? }` | -- | No | Claims stamped on every issued token. A configured value WINS over one supplied in the payload |
181
203
 
182
204
  > [!WARNING]
183
- > `jwtSecret` is mandatory - the component throws if it is missing or equals the placeholder `'unknown_secret'`. `applicationSecret` is optional: when omitted, the JWT payload is standard plaintext; standard fields (`iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat`) are never encrypted either way.
205
+ > `jwtSecret` is mandatory - the component throws if it's missing or equals the placeholder `'unknown_secret'`. `applicationSecret` is optional. When you omit it, the JWT payload stays standard plaintext. Standard fields (`iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat`) are never encrypted either way.
184
206
 
185
207
  ### IJWKSIssuerOptions
186
208
 
@@ -198,6 +220,8 @@ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // di
198
220
  | `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
199
221
  | `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
200
222
  | `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
223
+ | `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
224
+ | `sign` | `{ issuer?, audience? }` | -- | No | Claims stamped on every issued token. A configured value WINS over one supplied in the payload |
201
225
 
202
226
  ### IJWKSVerifierOptions
203
227
 
@@ -210,6 +234,7 @@ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // di
210
234
  | `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload decryption |
211
235
  | `applicationSecret` | `string` | -- | No | Must match the issuer's secret to decrypt payloads |
212
236
  | `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Must match the issuer's codecs to decrypt custom fields |
237
+ | `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
213
238
 
214
239
  > [!IMPORTANT]
215
240
  > `JWKSVerifierTokenService` cannot sign tokens - `getSigner()`, `getSigningKey()`, and `getDefaultTokenExpiresFn()` all throw. Only `verify()` and `extractCredentials()` are functional.
@@ -254,7 +279,7 @@ type TRouteAuthenticateConfig =
254
279
 
255
280
  | Field | Type | Default | Description |
256
281
  |-------|------|---------|-------------|
257
- | `authenticate.strategies` | `TAuthStrategy[]` | -- | Strategy names to try, e.g. `['jwt']`, `['jwt', 'basic']` |
282
+ | `authenticate.strategies` | `TAuthStrategy[]` | -- | Strategy names to try - for example, `['jwt']` or `['jwt', 'basic']`. Any name you register works too; `'jwt'` and `'basic'` are the ones the framework ships |
258
283
  | `authenticate.mode` | `'any' \| 'all'` | `'any'` | `'any'`: first success wins. `'all'`: every strategy must pass |
259
284
  | `authenticate.skip` | `true` | -- | Skips authentication for this route entirely |
260
285
 
@@ -400,7 +425,7 @@ export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) =
400
425
  5. On failure, throws `401`
401
426
 
402
427
  > [!NOTE]
403
- > `'any'` mode discards each failing strategy's error (logs at debug) and only throws once every strategy is exhausted. `'all'` mode uses the **first** strategy's user payload as the identity source; if that payload has no `userId`, it throws `401` even though every strategy technically passed.
428
+ > `'any'` mode logs each failing strategy at debug and discards the error. It only throws once every strategy is exhausted. `'all'` mode uses the **first** strategy's user payload as the identity source. If that payload has no `userId`, it throws `401` even though every strategy technically passed.
404
429
 
405
430
  ## Service class hierarchy
406
431
 
@@ -430,7 +455,7 @@ AbstractBearerTokenService<E> (extends BaseService)
430
455
 
431
456
  ### AbstractBearerTokenService
432
457
 
433
- **File:** [`packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts)
458
+ **File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/abstract.service.ts)
434
459
 
435
460
  | Method | Signature | Description |
436
461
  |--------|-----------|-------------|
@@ -446,21 +471,26 @@ Static: `JWT_COMMON_FIELDS: Set<'iss'|'sub'|'aud'|'jti'|'nbf'|'exp'|'iat'>` - ne
446
471
 
447
472
  ### JWSTokenService
448
473
 
449
- **File:** [`packages/core/src/components/auth/authenticate/services/bearer/jws.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jws.service.ts)
474
+ **File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jws.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jws.service.ts)
450
475
 
451
- Constructor validates `jwtSecret` and `getTokenExpiresFn` (throws `500` if missing), encodes the secret to `Uint8Array`, calls `configurePayloadEncryption()`. `doVerify` calls `jose.jwtVerify()` with the shared secret; `getSigner` signs with header `HS256` (or `headerAlgorithm` override).
476
+ The constructor validates `jwtSecret` and `getTokenExpiresFn`, throwing `500` if either is missing. It encodes the secret to `Uint8Array` and calls `configurePayloadEncryption()`. `doVerify` calls `jose.jwtVerify()` with the shared secret. `getSigner` signs with header `HS256`, or `headerAlgorithm` if you set one.
452
477
 
453
478
  ### AbstractJWKSTokenService
454
479
 
455
- **File:** [`packages/core/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts)
480
+ **File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts)
456
481
 
457
- `ensureInitialized()` lazily runs `initialize()` on first call; concurrent callers share the pending promise. If `initialize()` rejects, the promise is reset so the next call retries instead of caching the failure.
482
+ `ensureInitialized()` lazily runs `initialize()` on the first call. Concurrent callers share the pending promise. If `initialize()` rejects, the promise resets - the next call retries instead of caching the failure.
458
483
 
459
484
  ### JWKSIssuerTokenService
460
485
 
461
- **File:** [`packages/core/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts)
486
+ **File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts)
462
487
 
463
- `initialize()`: reads key content (file via `readFile` or inline text, per `keys.driver`) → imports it (`importPKCS8`/`importSPKI` for PEM, `importJWK` for JWK) → exports the public JWK with `kid`/`alg`/`use: 'sig'` → caches `{ keys: [publicJWK] }`.
488
+ `initialize()` runs in order:
489
+
490
+ 1. Reads the key content - `readFile` for `keys.driver: 'file'`, inline text for `'text'`.
491
+ 2. Imports it - `importPKCS8`/`importSPKI` for PEM, `importJWK` for JWK.
492
+ 3. Exports the public JWK with `kid`, `alg`, and `use: 'sig'` set.
493
+ 4. Caches the result as `{ keys: [publicJWK] }`.
464
494
 
465
495
  | Method | Signature | Description |
466
496
  |--------|-----------|-------------|
@@ -469,13 +499,13 @@ Constructor validates `jwtSecret` and `getTokenExpiresFn` (throws `500` if missi
469
499
 
470
500
  ### JWKSVerifierTokenService
471
501
 
472
- **File:** [`packages/core/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts)
502
+ **File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts)
473
503
 
474
504
  `initialize()` calls `createRemoteJWKSet(jwksUrl, { cacheMaxAge: cacheTtlMs ?? 43_200_000, cooldownDuration: cooldownMs ?? 30_000 })`. `getSigner`/`getSigningKey`/`getDefaultTokenExpiresFn` all throw - this service is verify-only.
475
505
 
476
506
  ### BasicTokenService
477
507
 
478
- **File:** [`packages/core/src/components/auth/authenticate/services/basic/service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/basic/service.ts)
508
+ **File:** [`packages/core-server/src/components/auth/authenticate/services/basic/service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/basic/service.ts)
479
509
 
480
510
  | Method | Signature | Description |
481
511
  |--------|-----------|-------------|
@@ -486,23 +516,28 @@ Constructor throws `500` if `verifyCredentials` is missing from the injected opt
486
516
 
487
517
  ## Strategy classes
488
518
 
489
- All four strategies extend `BaseHelper`, implement `IAuthenticationStrategy<E>`, and follow the same shape: a `name` field, a `standard` field (Bearer strategies only), one injected token service, and an `authenticate(context)` method that calls `extractCredentials()` then `verify()`.
519
+ All four strategies extend `BaseHelper` and implement `IAuthenticationStrategy<E>`. Each one carries:
520
+
521
+ - A `name` field.
522
+ - A `standard` field (Bearer strategies only).
523
+ - One injected token service.
524
+ - An `authenticate(context)` method that calls `extractCredentials()`, then `verify()`.
490
525
 
491
526
  | Strategy | `name` | Injects | File |
492
527
  |----------|--------|---------|------|
493
- | `JWSAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWSTokenService` | [`strategies/jws.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/strategies/jws.strategy.ts) |
494
- | `JWKSIssuerAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSIssuerTokenService` | [`strategies/jwks.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/strategies/jwks.strategy.ts) |
528
+ | `JWSAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWSTokenService` | [`strategies/jws.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/jws.strategy.ts) |
529
+ | `JWKSIssuerAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSIssuerTokenService` | [`strategies/jwks.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/jwks.strategy.ts) |
495
530
  | `JWKSVerifierAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSVerifierTokenService` | same file |
496
- | `BasicAuthenticationStrategy` | `Authentication.STRATEGY_BASIC` | `BasicTokenService` | [`strategies/basic.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/strategies/basic.strategy.ts) |
531
+ | `BasicAuthenticationStrategy` | `Authentication.STRATEGY_BASIC` | `BasicTokenService` | [`strategies/basic.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/basic.strategy.ts) |
497
532
 
498
533
  > [!NOTE]
499
- > Choose the strategy class matching your JOSE standard - `JWKSIssuerAuthenticationStrategy` and `JWKSVerifierAuthenticationStrategy` both register under the same `'jwt'` name, so a JWKS issuer service uses only one of the two.
534
+ > Choose the strategy class that matches your JOSE standard. `JWKSIssuerAuthenticationStrategy` and `JWKSVerifierAuthenticationStrategy` both register under the same `'jwt'` name - use only one of the two per service.
500
535
 
501
536
  ## JWKSController
502
537
 
503
538
  Serves the JWKS endpoint (default path `/certs`, configurable via `rest.path`). Intentionally unauthenticated - it serves the public keys external verifiers need.
504
539
 
505
- **File:** [`packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts)
540
+ **File:** [`packages/core-server/src/components/auth/authenticate/controllers/jwks/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/controllers/jwks/controller.ts)
506
541
 
507
542
  ```typescript
508
543
  class JWKSController extends BaseRestController {
@@ -529,16 +564,16 @@ The component applies `@controller({ path })` to `JWKSController` dynamically at
529
564
 
530
565
  `defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController` builds a `BaseRestController` subclass at runtime.
531
566
 
532
- **File:** [`packages/core/src/components/auth/authenticate/controllers/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/controllers/factory.ts)
567
+ **File:** [`packages/core-server/src/components/auth/authenticate/controllers/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/controllers/factory.ts)
533
568
 
534
569
  **How it works:**
535
570
 
536
571
  1. **Class creation.** `class AuthController extends BaseRestController {}` inside the factory closure, decorated with `@controller({ path: restPath })`, `isStrict: true`.
537
572
  2. **Service injection.** `inject({ key: serviceKey })(AuthController, undefined, 0)` is applied *after* class definition - the programmatic equivalent of decorating constructor parameter 0. The constructor throws `400` if the resolved service is falsy.
538
573
  3. **Routes.** Defined in `binding()` via `this.defineRoute()` - see the endpoint table in [Usage & Examples](./usage#api-endpoints).
539
- 4. **Schemas.** Each endpoint uses `payload.<name>.{request,response}.schema` if provided, else a built-in default (or `AnyObjectSchema` for responses with no built-in schema).
574
+ 4. **Schemas.** Each endpoint uses `payload.<name>.{request,response}.schema` if you provide it. Otherwise it falls back to a built-in default, or `AnyObjectSchema` where there's no built-in schema for that response.
540
575
 
541
- Also exports `JWTTokenPayloadSchema`, the Zod schema backing `/who-am-i`'s response - extended at runtime with an optional `userInformation` field typed from `payload.getUserInformation.response.schema` (or `AnyObjectSchema`).
576
+ Also exports `JWTTokenPayloadSchema`, the Zod schema backing `/who-am-i`'s response. It's extended at runtime with an optional `userInformation` field, typed from `payload.getUserInformation.response.schema` (or `AnyObjectSchema` if you don't provide one).
542
577
 
543
578
  ## Entity column helper types
544
579
 
@@ -564,13 +599,14 @@ type TPolicyDefinitionCommonColumns = {
564
599
  action: ReturnType<typeof text>;
565
600
  effect: ReturnType<typeof text>;
566
601
  domain: ReturnType<typeof text>;
602
+ metadata: ReturnType<typeof jsonb>;
567
603
  };
568
604
  ```
569
605
 
570
606
  ## File structure
571
607
 
572
608
  ```
573
- packages/core/src/components/auth/
609
+ packages/core-server/src/components/auth/
574
610
  ├── authenticate/
575
611
  │ ├── common/
576
612
  │ │ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
@@ -8,10 +8,24 @@ difficulty: intermediate
8
8
 
9
9
  Every error message the Authentication component and its services throw, with cause and fix. See the [Overview](./) for initial setup.
10
10
 
11
+ ## Find what you need
12
+
13
+ | Error is thrown by | Go to |
14
+ |---|---|
15
+ | Startup, while wiring the component | [AuthenticateComponent (startup)](#authenticatecomponent-startup) |
16
+ | `verify`/`generate`/`extractCredentials`, shared by JWS, JWKS, and Basic | [Bearer token services (runtime)](#bearer-token-services-runtime) |
17
+ | The `JWSTokenService` constructor | [JWSTokenService (constructor)](#jwstokenservice-constructor) |
18
+ | `JWKSIssuerTokenService` init or key parsing | [JWKSIssuerTokenService (init + runtime)](#jwksissuertokenservice-init-runtime) |
19
+ | `JWKSVerifierTokenService` signing calls | [JWKSVerifierTokenService (runtime)](#jwksverifiertokenservice-runtime) |
20
+ | Basic auth credential decoding | [BasicTokenService (runtime)](#basictokenservice-runtime) |
21
+ | "Descriptor not found" or a strategy registration issue | [AuthenticationStrategyRegistry (startup + runtime)](#authenticationstrategyregistry-startup-runtime) |
22
+ | "Authentication failed" at request time | [AuthenticationProvider (runtime)](#authenticationprovider-runtime) |
23
+ | The built-in `/auth` controller | [Auth controller factory](#auth-controller-factory) |
24
+
11
25
  - **Startup errors (400 unless noted)** come from `AuthenticateComponent.binding()` and stop the application before it serves traffic.
12
26
  - **Runtime errors (401)** come from request-time credential extraction, verification, or strategy exhaustion.
13
- - **Structural errors (500)** come from calling an unsupported operation (e.g. signing on a verify-only service) or accessing uninitialized state.
14
- - All of them go through `getError()`, which defaults `statusCode` to `400` when not given explicitly - so every "startup" error below is a 400 unless the table says otherwise.
27
+ - **Structural errors (500)** come from calling an unsupported operation - signing on a verify-only service, for example - or accessing uninitialized state.
28
+ - All of them go through `getError()`. It defaults `statusCode` to `400` when you don't set one - so every "startup" error below is a 400 unless the table says otherwise.
15
29
 
16
30
  ```mermaid
17
31
  flowchart LR
@@ -38,7 +52,7 @@ Thrown during `binding()` while validating options and wiring services.
38
52
  |---------|--------|-------|-----|
39
53
  | `[AuthenticateComponent] At least one of jwtOptions or basicOptions must be provided` | 400 | Neither `JWT_OPTIONS` nor `BASIC_OPTIONS` bound before `this.component(AuthenticateComponent)` | Bind at least one before registering the component - see [Setup](./#common-tasks) |
40
54
  | `[AuthenticateComponent] Unknown JOSE standard: {standard}` | 400 | `jwtOptions.standard` is not `'JWS'` or `'JWKS'` | Use `JOSEStandards.JWS` or `JOSEStandards.JWKS` |
41
- | `[defineJWSAuth] Invalid jwtSecret \| Provided: {jwtSecret}` | 400 | `jwtSecret` falsy or equals placeholder `'unknown_secret'` | Set a real secret, e.g. `APP_ENV_JWT_SECRET` |
55
+ | `[defineJWSAuth] Invalid jwtSecret \| Provided: {jwtSecret}` | 400 | `jwtSecret` falsy or equals placeholder `'unknown_secret'` | Set a real secret - `APP_ENV_JWT_SECRET`, for example |
42
56
  | `[defineJWSAuth] getTokenExpiresFn is required` | 400 | `getTokenExpiresFn` missing from JWS options | Provide `() => Number(process.env.APP_ENV_JWT_EXPIRES_IN \|\| 86400)` |
43
57
  | `[defineJWKSAuth] keys.private and keys.public are required for issuer mode` | 400 | Issuer mode missing one or both keys | Provide `keys.private` and `keys.public` |
44
58
  | `[defineJWKSAuth] keys.format is required and must be one of: pem, jwk` | 400 | `keys.format` missing or invalid | Use `JWKSKeyFormats.PEM` or `JWKSKeyFormats.JWK` |
@@ -50,7 +64,7 @@ Thrown during `binding()` while validating options and wiring services.
50
64
  | `[defineControllers] Auth controller requires jwtOptions to be configured` | 400 | `useAuthController: true` but no `jwtOptions` bound | Bind `JWT_OPTIONS` before enabling the auth controller |
51
65
 
52
66
  > [!NOTE]
53
- > `applicationSecret` is optional and not validated. The component and every token service treat it as optional - omitting it simply disables AES payload encryption. It is never checked for presence.
67
+ > `applicationSecret` is optional and not validated. Every token service treats it as optional - omitting it disables AES payload encryption, nothing more. Its presence is never checked.
54
68
 
55
69
  ## Bearer token services (runtime)
56
70
 
@@ -145,7 +159,7 @@ The middleware that executes strategies in the configured mode.
145
159
  | `Failed to identify authenticated user!` | 401 | `executeAllMode` | All strategies passed in `'all'` mode, but the first strategy's `userId` is falsy |
146
160
  | `Invalid authentication mode \| mode: {mode}` | 500 | `createAuthenticateMiddleware` | `mode` is not `'any'` or `'all'` |
147
161
 
148
- **Fix for "Authentication failed":** verify the client sends the right header (`Bearer <token>` or `Basic <base64>`); common causes are an expired token, a token signed with a different key, or the wrong strategy name in the route config.
162
+ **Fix for "Authentication failed":** verify the client sends the right header - `Bearer <token>` or `Basic <base64>`. Common causes: an expired token, a token signed with a different key, or the wrong strategy name in the route config.
149
163
 
150
164
  ## Auth controller factory
151
165
 
@@ -6,7 +6,7 @@ difficulty: intermediate
6
6
 
7
7
  # Authentication
8
8
 
9
- `AuthenticateComponent` wires up JWT and/or Basic HTTP authentication - token services, route-level strategies, and an optional built-in `/auth` controller for sign-in, sign-up, and password change.
9
+ `AuthenticateComponent` wires up JWT and/or Basic HTTP authentication. It configures token services, route-level strategies, and an optional built-in `/auth` controller for sign-in, sign-up, and password change.
10
10
 
11
11
  ## In one example
12
12
 
@@ -57,19 +57,20 @@ const SECURE_ROUTE = {
57
57
 
58
58
  ## How it works
59
59
 
60
- - **One component, three auth mechanisms.** `AuthenticateComponent.binding()` reads `JWT_OPTIONS` (JWS or JWKS, via a discriminated `standard` field) and `BASIC_OPTIONS` from the DI container and registers whichever token services their presence implies. At least one of the two must be bound, or the component throws at startup.
61
- - **Strategies are manual, on purpose.** The component registers *token services* (JWS/JWKS/Basic), not *strategies*. You register strategies yourself via `AuthenticationStrategyRegistry.getInstance().register(...)` after `this.component(AuthenticateComponent)` - this is what gives routes a `Authentication.STRATEGY_JWT` / `'basic'` name to reference.
62
- - **The registry is a DI-backed singleton.** `AuthenticationStrategyRegistry` binds each registered strategy into the container as a singleton under `authentication.strategy.<name>` and resolves it by name when a route's `authenticate.strategies` list is checked.
63
- - **`authenticate()` is the middleware entry point.** Route-level `authenticate: { strategies, mode }` config and the standalone `authenticate()` function both go through the same `AuthenticationProvider`, which tries strategies in `'any'` (first success wins) or `'all'` (every strategy must pass) mode and sets `Authentication.CURRENT_USER` on the Hono context.
64
- - **The auth controller is optional and generated.** Setting `REST_OPTIONS.useAuthController: true` calls `defineAuthController()`, which builds a `BaseRestController` subclass at runtime with `/sign-in`, `/sign-up`, `/change-password`, `/token/refresh`, `/who-am-i`, and `/me` routes, backed by your own `IAuthService` implementation.
60
+ - **One component, three auth mechanisms.** `AuthenticateComponent.binding()` reads `JWT_OPTIONS` (JWS or JWKS) and `BASIC_OPTIONS` from the DI container, and registers the token services their presence implies.
61
+ - Bind at least one of the two - the component throws at startup if neither is set.
62
+ - **Strategies are manual, on purpose.** The component registers *token services* - JWS, JWKS, Basic - never *strategies*. Register strategies yourself after the component, via `AuthenticationStrategyRegistry.getInstance().register(...)`.
63
+ - **The registry is a DI-backed singleton.** `AuthenticationStrategyRegistry` binds each strategy into the container as a singleton, under the key `authentication.strategy.<name>`. A route's `authenticate.strategies` list references strategies by that same name.
64
+ - **`authenticate()` is the middleware entry point.** Route-level `authenticate: { strategies, mode }` config and the standalone `authenticate()` function both run through the same `AuthenticationProvider`, which sets `Authentication.CURRENT_USER` on success. See [Multi-strategy authentication](./usage#multi-strategy-authentication) for what `'any'` and `'all'` mode do.
65
+ - **The auth controller is optional and generated.** Set `REST_OPTIONS.useAuthController: true` to call `defineAuthController()`, which builds a `BaseRestController` subclass at runtime - routes for `/sign-in`, `/sign-up`, `/change-password`, `/token/refresh`, `/who-am-i`, and `/me`. You back it with your own `IAuthService` implementation.
65
66
 
66
67
  **JOSE standards**
67
68
 
68
69
  | Standard | Class | Keying | Use case |
69
70
  |----------|-------|--------|----------|
70
71
  | JWS | `JWSTokenService` | Shared secret (HS256) | Single service signs and verifies |
71
- | JWKS Issuer | `JWKSIssuerTokenService` | Private + public key (ES256/RS256/EdDSA) | This service issues tokens and serves `/certs` |
72
- | JWKS Verifier | `JWKSVerifierTokenService` | Remote JWKS URL | This service only verifies tokens from another issuer |
72
+ | JWKS Issuer | `JWKSIssuerTokenService` | Private + public key (ES256/RS256/EdDSA) | Issues tokens, serves `/certs` |
73
+ | JWKS Verifier | `JWKSVerifierTokenService` | Remote JWKS URL | Verifies tokens from another issuer, only |
73
74
 
74
75
  ## Common tasks
75
76
 
@@ -100,7 +101,7 @@ this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS })
100
101
  });
101
102
  ```
102
103
 
103
- **Add Basic auth.** Provide a `verifyCredentials` callback; it becomes the `'basic'` strategy's source of truth.
104
+ **Add Basic auth.** Provide a `verifyCredentials` callback. It becomes the `'basic'` strategy's source of truth.
104
105
 
105
106
  ```typescript
106
107
  this.bind<TBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
@@ -128,7 +129,7 @@ this.bind<TAuthenticationRestOptions>({ key: AuthenticateBindingKeys.REST_OPTION
128
129
  });
129
130
  ```
130
131
 
131
- **Secure a route with multiple strategies.** `mode: 'any'` (default) falls back through strategies; `mode: 'all'` requires every one to pass.
132
+ **Secure a route with multiple strategies.** `mode: 'any'` (default) falls back through strategies. `mode: 'all'` requires every one to pass.
132
133
 
133
134
  ```typescript
134
135
  const FALLBACK_AUTH_CONFIG = {
@@ -142,11 +143,11 @@ const FALLBACK_AUTH_CONFIG = {
142
143
  **Add auth entity columns to a Drizzle table.** Spread helper functions into `pgTable()` for User/Role/Permission/PolicyDefinition columns.
143
144
 
144
145
  ```typescript
145
- import { extraUserColumns } from '@venizia/ignis';
146
+ import { extraUserColumns, generateIdColumnDefs, generateTzColumnDefs } from '@venizia/ignis';
146
147
 
147
148
  export const users = pgTable('users', {
148
- ...withSerialId(),
149
- ...withTimestamps(),
149
+ ...generateIdColumnDefs(),
150
+ ...generateTzColumnDefs(),
150
151
  ...extraUserColumns(),
151
152
  username: text('username').unique().notNull(),
152
153
  });
@@ -163,8 +164,8 @@ export const users = pgTable('users', {
163
164
 
164
165
  **Files:**
165
166
 
166
- - [`packages/core/src/components/auth/authenticate/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/component.ts) - `AuthenticateComponent`
167
- - [`packages/core/src/components/auth/authenticate/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/types.ts) - all option interfaces
168
- - [`packages/core/src/components/auth/authenticate/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/keys.ts) - `AuthenticateBindingKeys`
169
- - [`packages/core/src/components/auth/authenticate/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/constants.ts) - `Authentication`, `JOSEStandards`, `JWKSModes`, `JWKSKeyDrivers`, `JWKSKeyFormats`
170
- - [`packages/core/src/components/auth/models/entities`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/models/entities) - `extraUserColumns`, `extraRoleColumns`, `extraPermissionColumns`, `extraPolicyDefinitionColumns`
167
+ - [`packages/core-server/src/components/auth/authenticate/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/component.ts) - `AuthenticateComponent`
168
+ - [`packages/core-server/src/components/auth/authenticate/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/common/types.ts) - all option interfaces
169
+ - [`packages/core-server/src/components/auth/authenticate/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/common/keys.ts) - `AuthenticateBindingKeys`
170
+ - [`packages/core-server/src/components/auth/authenticate/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/common/constants.ts) - `Authentication`, `JOSEStandards`, `JWKSModes`, `JWKSKeyDrivers`, `JWKSKeyFormats`
171
+ - [`packages/core-server/src/components/auth/models/entities`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/models/entities) - `extraUserColumns`, `extraRoleColumns`, `extraPermissionColumns`, `extraPolicyDefinitionColumns`
@@ -8,6 +8,20 @@ difficulty: intermediate
8
8
 
9
9
  Task-oriented examples for the Authentication component. See the [Overview](./) for initial setup and the [API Reference](./api) for every option and class.
10
10
 
11
+ ## Find what you need
12
+
13
+ | You want to | Go to |
14
+ |---|---|
15
+ | Require auth on a route, or make one public | [Securing routes](#securing-routes) |
16
+ | Implement sign-in, sign-up, or change-password | [Implementing IAuthService](#implementing-iauthservice) |
17
+ | Split issuer and verifier across services | [JWKS microservice patterns](#jwks-microservice-patterns) |
18
+ | See what happens on each request, step by step | [Auth flows](#auth-flows) |
19
+ | Accept both JWT and Basic on one route | [Multi-strategy authentication](#multi-strategy-authentication) |
20
+ | Encrypt JWT payload fields | [Token encryption (optional AES)](#token-encryption-optional-aes) |
21
+ | Read `CURRENT_USER` in a handler, with types | [Hono context extension](#hono-context-extension) |
22
+ | Call the built-in `/auth` endpoints | [API endpoints](#api-endpoints) |
23
+ | Add auth columns to a Drizzle table | [Entity column helpers](#entity-column-helpers) |
24
+
11
25
  ## Securing routes
12
26
 
13
27
  **Require one strategy.** Add `authenticate` to the route config.
@@ -21,7 +35,7 @@ const SECURE_ROUTE_CONFIG = {
21
35
  } as const;
22
36
  ```
23
37
 
24
- **Accept multiple strategies with fallback.** `mode: 'any'` (default) tries each in order; the first success wins.
38
+ **Accept multiple strategies with fallback.** `mode: 'any'` (default) tries each strategy in order. The first success wins.
25
39
 
26
40
  ```typescript
27
41
  const FALLBACK_AUTH_CONFIG = {
@@ -43,7 +57,7 @@ const PUBLIC_ROUTE_CONFIG = {
43
57
  } as const;
44
58
  ```
45
59
 
46
- **Use `authenticate()` as raw Hono middleware.** Outside of route configs - e.g. for a plain Hono sub-app.
60
+ **Use `authenticate()` as raw Hono middleware.** Use this outside route configs - for a plain Hono sub-app, for example.
47
61
 
48
62
  ```typescript
49
63
  import { authenticate, Authentication, AuthenticationModes } from '@venizia/ignis';
@@ -83,7 +97,7 @@ const conditionalAuthMiddleware = createMiddleware(async (c, next) => {
83
97
 
84
98
  ## Implementing IAuthService
85
99
 
86
- The built-in auth controller (`useAuthController: true`) delegates every route to a service you provide, implementing `IAuthService`.
100
+ The built-in auth controller (`useAuthController: true`) delegates every route to a service you provide. That service implements `IAuthService`.
87
101
 
88
102
  **JWS-backed service.**
89
103
 
@@ -130,7 +144,7 @@ constructor(
130
144
  ) { super({ scope: AuthenticationService.name }); }
131
145
  ```
132
146
 
133
- **Implement `refreshToken` (optional).** Re-issues a token from the currently valid one - there is no separate refresh token.
147
+ **Implement `refreshToken` (optional).** It re-issues a token from the caller's currently valid one. IGNIS has no separate refresh token.
134
148
 
135
149
  ```typescript
136
150
  async refreshToken(context: TContext<Env>): Promise<{ token: string }> {
@@ -257,8 +271,8 @@ sequenceDiagram
257
271
 
258
272
  | Mode | Behavior | Use case |
259
273
  |------|----------|----------|
260
- | `'any'` (default) | Strategies tried in order; first success wins; failures discarded (debug log); `401` with the tried-strategy list only if all fail | Fallback auth (JWT primary, Basic for legacy clients) |
261
- | `'all'` | Every strategy must pass; first failure rejects immediately; the **first** strategy's user payload is the identity source | Multi-factor authentication |
274
+ | `'any'` (default) | Tried in order.<br>First success wins.<br>Failures are logged at debug, not returned.<br>All fail -> `401` listing the tried strategies. | Fallback auth (JWT primary, Basic for legacy clients) |
275
+ | `'all'` | Every strategy must pass.<br>First failure rejects immediately.<br>The **first** strategy's user payload is the identity source. | Multi-factor authentication |
262
276
 
263
277
  ```mermaid
264
278
  flowchart TD
@@ -279,11 +293,16 @@ flowchart TD
279
293
 
280
294
  ## Token encryption (optional AES)
281
295
 
282
- - **Off by default.** AES payload encryption only activates when `applicationSecret` is set on the JWS/JWKS options - otherwise payloads are standard plaintext JWT.
283
- - **Standard fields untouched.** `iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat` are never encrypted, on either side.
284
- - **Everything else, key and value.** Every other payload field has both its key and its value AES-encrypted; `null`/`undefined` values are skipped.
285
- - **Serialization is `JSON.stringify` unless you supply a codec.** `AuthenticationFieldCodecs.ROLES_CODEC` is a ready-made codec that serializes `roles` as pipe-separated `id|identifier|priority` strings - it is opt-in, not automatic. Pass it via `fieldCodecs: [AuthenticationFieldCodecs.ROLES_CODEC]` if you want that format instead of the default JSON array.
286
- - **Secret must stay constant.** Changing `applicationSecret` invalidates every existing token. Issuer and verifier must share the identical secret (and identical `fieldCodecs`, if used).
296
+ AES payload encryption is off by default. It only activates when you set `applicationSecret` on the JWS/JWKS options.
297
+
298
+ | Aspect | Behavior |
299
+ |---|---|
300
+ | Default | Off. Without `applicationSecret`, payloads stay standard plaintext JWT. |
301
+ | Standard fields | `iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat` are never encrypted, on either side. |
302
+ | Other fields | Both the key and the value are AES-encrypted. `null` and `undefined` values are skipped. |
303
+ | Serialization | `JSON.stringify` by default. Opt in to `AuthenticationFieldCodecs.ROLES_CODEC` for `roles` - it serializes as pipe-separated `id\|identifier\|priority` strings instead. |
304
+ | Secret | Must stay constant. Changing `applicationSecret` invalidates every existing token. |
305
+ | Issuer/verifier match | Both sides need the identical secret, and identical `fieldCodecs` if you use them. |
287
306
 
288
307
  ```typescript
289
308
  this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
@@ -331,7 +350,7 @@ The built-in auth controller exists only when `REST_OPTIONS.useAuthController: t
331
350
  | `GET` | `/certs` | No | JWKS endpoint (Issuer mode only) |
332
351
 
333
352
  > [!NOTE]
334
- > `/auth` is configurable via `controllerOpts.restPath`; `/certs` via `rest.path` in `IJWKSIssuerOptions`. `/certs` is intentionally unauthenticated.
353
+ > `/auth` is configurable via `controllerOpts.restPath`. `/certs` is configurable via `rest.path` in `IJWKSIssuerOptions`, and is intentionally unauthenticated.
335
354
 
336
355
  **`POST /auth/sign-in`** - body defaults to `SignInRequestSchema` (nested `identifier`/`credential`), overridable via `payload.signIn`.
337
356
 
@@ -339,22 +358,22 @@ The built-in auth controller exists only when `REST_OPTIONS.useAuthController: t
339
358
  { "token": "eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleS1pZC0xIn0..." }
340
359
  ```
341
360
 
342
- **`POST /auth/sign-up`** - public unless `requireAuthenticatedSignUp: true`. Body defaults to a flat `SignUpRequestSchema` (`username`, `credential` - unlike sign-in's nested shape).
361
+ **`POST /auth/sign-up`** - public unless `requireAuthenticatedSignUp: true`. Body defaults to a flat `SignUpRequestSchema` (`username`, `credential`) - unlike sign-in, the shape isn't nested.
343
362
 
344
363
  **`POST /auth/change-password`** - always requires JWT. Body defaults to `ChangePasswordRequestSchema` (`scheme`, `oldCredential`, `newCredential`, `userId`).
345
364
 
346
365
  **`POST /auth/token/refresh`** - always requires JWT, no request body. Returns `501` if `IAuthService.refreshToken` isn't implemented.
347
366
 
348
- **`GET /auth/who-am-i`** - always requires JWT. Query param `withUserInformation` (`true`/`false`/`1`/`0`, default `false`) attaches a `userInformation` field from `getUserInformation`; returns `501` if that method is truthy-requested but not implemented.
367
+ **`GET /auth/who-am-i`** - always requires JWT. The `withUserInformation` query param (`true`, `false`, `1`, `0`; default `false`) attaches a `userInformation` field from `getUserInformation`. Returns `501` if you request that field without implementing `getUserInformation`.
349
368
 
350
369
  ```json
351
370
  { "userId": "123", "roles": [{ "id": "1", "identifier": "admin", "priority": 0 }] }
352
371
  ```
353
372
 
354
- **`GET /auth/me`** - always requires JWT. Delegates entirely to `getUserInformation(context, {})` - the response is not merged with the JWT payload. Returns `501` if not implemented.
373
+ **`GET /auth/me`** - always requires JWT, delegating entirely to `getUserInformation(context, {})`. The response is not merged with the JWT payload. Returns `501` if `getUserInformation` isn't implemented.
355
374
 
356
375
  > [!TIP]
357
- > One `getUserInformation` implementation backs both routes: use `GET /me` for the raw profile, `GET /who-am-i?withUserInformation=true` to get it merged with the principal in one round-trip.
376
+ > One `getUserInformation` implementation backs both routes. Use `GET /me` for the raw profile. Use `GET /who-am-i?withUserInformation=true` to get it merged with the principal in one round-trip.
358
377
 
359
378
  **`GET /certs`** (Issuer mode only) - public, returns the JSON Web Key Set with `Cache-Control: public, max-age=3600, stale-while-revalidate=86400`.
360
379
 
@@ -368,19 +387,22 @@ Column helper functions return pre-configured Drizzle columns for auth-related t
368
387
 
369
388
  ```typescript
370
389
  import { pgTable, text } from 'drizzle-orm/pg-core';
371
- import { extraUserColumns, extraRoleColumns, extraPermissionColumns, extraPolicyDefinitionColumns, withSerialId, withTimestamps } from '@venizia/ignis';
390
+ import {
391
+ extraUserColumns, extraRoleColumns, extraPermissionColumns, extraPolicyDefinitionColumns,
392
+ generateIdColumnDefs, generateTzColumnDefs,
393
+ } from '@venizia/ignis';
372
394
 
373
395
  export const users = pgTable('users', {
374
- ...withSerialId(),
375
- ...withTimestamps(),
396
+ ...generateIdColumnDefs(),
397
+ ...generateTzColumnDefs(),
376
398
  ...extraUserColumns(),
377
399
  username: text('username').unique().notNull(),
378
400
  passwordHash: text('password_hash').notNull(),
379
401
  });
380
402
 
381
- export const roles = pgTable('roles', { ...withSerialId(), ...withTimestamps(), ...extraRoleColumns() });
382
- export const permissions = pgTable('permissions', { ...withSerialId(), ...withTimestamps(), ...extraPermissionColumns() });
383
- export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId(), ...withTimestamps(), ...extraPolicyDefinitionColumns() });
403
+ export const roles = pgTable('roles', { ...generateIdColumnDefs(), ...generateTzColumnDefs(), ...extraRoleColumns() });
404
+ export const permissions = pgTable('permissions', { ...generateIdColumnDefs(), ...generateTzColumnDefs(), ...extraPermissionColumns() });
405
+ export const policyDefinitions = pgTable('policy_definitions', { ...generateIdColumnDefs(), ...generateTzColumnDefs(), ...extraPolicyDefinitionColumns() });
384
406
  ```
385
407
 
386
408
  **`extraUserColumns(opts?: { idType })`**
@@ -398,7 +420,7 @@ export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId
398
420
 
399
421
  | Column | DB column | Type | Default | Description |
400
422
  |--------|-----------|------|---------|-------------|
401
- | `identifier` | `identifier` | `text` (unique) | -- | e.g. `'admin'`, `'editor'` |
423
+ | `identifier` | `identifier` | `text` (unique) | -- | For example, `'admin'`, `'editor'` |
402
424
  | `name` | `name` | `text` | -- | Human-readable name |
403
425
  | `description` | `description` | `text` | `null` | Optional |
404
426
  | `priority` | `priority` | `integer` | -- | Lower = higher priority |
@@ -410,9 +432,9 @@ export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId
410
432
  |--------|-----------|------|---------|-------------|
411
433
  | `code` | `code` | `text` (unique) | -- | Unique permission code |
412
434
  | `name` | `name` | `text` | -- | Display name |
413
- | `subject` | `subject` | `text` | -- | e.g. `'User'`, `'Order'` |
414
- | `method` | `method` | `text` | -- | e.g. `'GET'`, `'POST'` |
415
- | `action` | `action` | `text` | -- | e.g. `'read'`, `'write'` |
435
+ | `subject` | `subject` | `text` | -- | For example, `'User'`, `'Order'` |
436
+ | `method` | `method` | `text` | -- | For example, `'GET'`, `'POST'` |
437
+ | `action` | `action` | `text` | -- | For example, `'read'`, `'write'` |
416
438
  | `scope` | `scope` | `text` | -- | Permission scope |
417
439
  | `description` | `description` | `text` | `null` | Optional |
418
440
  | `parentId` | `parent_id` | `text` or `integer` | `null` | Depends on `idType` |
@@ -421,16 +443,19 @@ export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId
421
443
 
422
444
  | Column | DB column | Type | Nullable | Description |
423
445
  |--------|-----------|------|----------|-------------|
424
- | `variant` | `variant` | `text` | No | `'p'` (policy) or `'g'` (grouping) |
425
- | `subjectType` | `subject_type` | `text` | No | e.g. `'user'`, `'role'` |
426
- | `targetType` | `target_type` | `text` | No | e.g. `'permission'`, `'role'` |
446
+ | `variant` | `variant` | `text` | No | One of the seven `AuthorizationPolicyVariants` edge kinds: `grant`, `assign_role`, `role_inherits`, `join_domain`, `domain_inherits`, `resource_inherits`, `action_inherits` |
447
+ | `subjectType` | `subject_type` | `text` | No | For example, `'user'`, `'Role'` |
448
+ | `targetType` | `target_type` | `text` | No | For example, `'Permission'`, `'Role'` |
427
449
  | `action` | `action` | `text` | Yes | Policy action |
428
450
  | `effect` | `effect` | `text` | Yes | `'allow'` / `'deny'` |
429
451
  | `domain` | `domain` | `text` | Yes | Multi-tenancy domain |
430
452
  | `subjectId` | `subject_id` | `text` or `integer` | No | Depends on `idType` |
431
453
  | `targetId` | `target_id` | `text` or `integer` | No | Depends on `idType` |
454
+ | `metadata` | `metadata` | `jsonb` | Yes | Free-form metadata. Only some grants populate it |
455
+
456
+ All `idType` options default to `'number'` (`integer` columns). Pass `'string'` for `text` columns - UUID primary keys, for example.
432
457
 
433
- All `idType` options default to `'number'` (`integer` columns); pass `'string'` for `text` (e.g. UUID) columns.
458
+ `'p'` and `'g'` are Casbin rule prefixes, not `variant` values - each `variant` maps to one of them internally. See the [Authorization component](../authorization/) to build these tables end to end. Its [Usage guide](../authorization/usage) covers policy definitions, domain scoping, and the adapter that reads this table.
434
459
 
435
460
  ## See also
436
461